summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTakashi Iwai <tiwai@suse.de>2026-10-19 10:39:44 +0200
committerTakashi Iwai <tiwai@suse.de>2026-10-19 10:40:00 +0200
commit7e246ac8bed799402b743550d283bdd202048fa7 (patch)
treed6dc95e8d6f96a581ec2c37fcaea09911a4099a5
parent8e8cd9a4c6dc006e9bd51b8ad0079fb4576b48d3 (diff)
parent40409fa23d32c8ed51e832031daa6decd9d6fd11 (diff)
Merge branch 'for-next' into for-linus
Pull 7.4 merge-window stuff. Signed-off-by: Takashi Iwai <tiwai@suse.de>
-rw-r--r--Documentation/sound/alsa-configuration.rst6
-rw-r--r--Documentation/sound/cards/hdspm.rst109
-rw-r--r--MAINTAINERS13
-rw-r--r--drivers/acpi/scan.c1
-rw-r--r--drivers/platform/x86/serial-multi-instantiate.c1
-rw-r--r--include/sound/control.h4
-rw-r--r--include/sound/core.h5
-rw-r--r--include/sound/emu10k1.h4
-rw-r--r--include/sound/emux_legacy.h2
-rw-r--r--include/sound/hda_codec.h6
-rw-r--r--include/sound/hdaudio.h12
-rw-r--r--include/sound/jack.h2
-rw-r--r--include/sound/pcm.h2
-rw-r--r--include/sound/pcm_oss.h1
-rw-r--r--include/sound/rawmidi.h1
-rw-r--r--include/uapi/sound/asound.h3
-rw-r--r--sound/core/control.c2
-rw-r--r--sound/core/control_led.c62
-rw-r--r--sound/core/ctljack.c1
-rw-r--r--sound/core/info.c3
-rw-r--r--sound/core/init.c95
-rw-r--r--sound/core/jack.c61
-rw-r--r--sound/core/misc.c5
-rw-r--r--sound/core/oss/mixer_oss.c37
-rw-r--r--sound/core/oss/pcm_oss.c12
-rw-r--r--sound/core/pcm.c7
-rw-r--r--sound/core/pcm_compat.c6
-rw-r--r--sound/core/pcm_lib.c16
-rw-r--r--sound/core/pcm_native.c277
-rw-r--r--sound/core/rawmidi.c88
-rw-r--r--sound/core/seq/Kconfig7
-rw-r--r--sound/core/seq/oss/seq_oss.c3
-rw-r--r--sound/core/seq/oss/seq_oss_device.h1
-rw-r--r--sound/core/seq/oss/seq_oss_event.c9
-rw-r--r--sound/core/seq/oss/seq_oss_init.c4
-rw-r--r--sound/core/seq/oss/seq_oss_midi.c5
-rw-r--r--sound/core/seq/seq_clientmgr.c27
-rw-r--r--sound/core/seq/seq_compat.c15
-rw-r--r--sound/core/seq/seq_ports.c41
-rw-r--r--sound/core/seq/seq_ports.h1
-rw-r--r--sound/core/timer.c2
-rw-r--r--sound/core/timer_compat.c1
-rw-r--r--sound/core/ump.c2
-rw-r--r--sound/core/ump_convert.c2
-rw-r--r--sound/core/vmaster.c10
-rw-r--r--sound/drivers/aloop.c31
-rw-r--r--sound/drivers/dummy.c10
-rw-r--r--sound/drivers/mpu401/mpu401_uart.c8
-rw-r--r--sound/drivers/pcmtest.c132
-rw-r--r--sound/drivers/serial-generic.c10
-rw-r--r--sound/firewire/bebob/bebob_hwdep.c2
-rw-r--r--sound/hda/codecs/ca0132.c391
-rw-r--r--sound/hda/codecs/cirrus/cs8409-tables.c9
-rw-r--r--sound/hda/codecs/cirrus/cs8409.c52
-rw-r--r--sound/hda/codecs/cirrus/cs8409.h4
-rw-r--r--sound/hda/codecs/conexant.c80
-rw-r--r--sound/hda/codecs/hdmi/eld.c6
-rw-r--r--sound/hda/codecs/hdmi/hdmi.c5
-rw-r--r--sound/hda/codecs/realtek/alc269.c405
-rw-r--r--sound/hda/codecs/realtek/alc662.c24
-rw-r--r--sound/hda/codecs/realtek/alc882.c2
-rw-r--r--sound/hda/codecs/side-codecs/Kconfig1
-rw-r--r--sound/hda/codecs/side-codecs/cs35l41_hda.c12
-rw-r--r--sound/hda/codecs/side-codecs/cs35l41_hda_i2c.c3
-rw-r--r--sound/hda/codecs/side-codecs/cs35l41_hda_property.c36
-rw-r--r--sound/hda/codecs/side-codecs/tas2781_hda_i2c.c19
-rw-r--r--sound/hda/codecs/sigmatel.c6
-rw-r--r--sound/hda/codecs/via.c1
-rw-r--r--sound/hda/common/bind.c15
-rw-r--r--sound/hda/common/codec.c18
-rw-r--r--sound/hda/common/hda_jack.h1
-rw-r--r--sound/hda/common/jack.c88
-rw-r--r--sound/hda/controllers/acpi.c41
-rw-r--r--sound/hda/controllers/intel.c33
-rw-r--r--sound/hda/core/bus.c38
-rw-r--r--sound/hda/core/component.c5
-rw-r--r--sound/hda/core/controller.c24
-rw-r--r--sound/hda/core/device.c13
-rw-r--r--sound/hda/core/local.h2
-rw-r--r--sound/i2c/other/ak4113.c2
-rw-r--r--sound/i2c/other/ak4114.c2
-rw-r--r--sound/i2c/other/ak4xxx-adda.c2
-rw-r--r--sound/isa/galaxy/galaxy.c10
-rw-r--r--sound/isa/opti9xx/opti92x-ad1848.c2
-rw-r--r--sound/isa/sc6000.c10
-rw-r--r--sound/mips/hal2.c34
-rw-r--r--sound/mips/sgio2audio.c43
-rw-r--r--sound/mips/snd-n64.c63
-rw-r--r--sound/oss/dmasound/dmasound_q40.c65
-rw-r--r--sound/parisc/harmony.c24
-rw-r--r--sound/pci/ac97/ac97_codec.c2
-rw-r--r--sound/pci/ac97/ac97_patch.c2
-rw-r--r--sound/pci/ad1889.c13
-rw-r--r--sound/pci/ali5451/ali5451.c13
-rw-r--r--sound/pci/als300.c11
-rw-r--r--sound/pci/als4000.c13
-rw-r--r--sound/pci/atiixp.c13
-rw-r--r--sound/pci/atiixp_modem.c13
-rw-r--r--sound/pci/au88x0/au88x0.c11
-rw-r--r--sound/pci/au88x0/au88x0.h2
-rw-r--r--sound/pci/au88x0/au88x0_mpu401.c2
-rw-r--r--sound/pci/aw2/aw2-alsa.c11
-rw-r--r--sound/pci/aw2/aw2-saa7146.c4
-rw-r--r--sound/pci/azt3328.c11
-rw-r--r--sound/pci/bt87x.c13
-rw-r--r--sound/pci/ca0106/ca0106_main.c11
-rw-r--r--sound/pci/cmipci.c11
-rw-r--r--sound/pci/cs4281.c15
-rw-r--r--sound/pci/cs46xx/cs46xx.c25
-rw-r--r--sound/pci/cs46xx/cs46xx_lib.c6
-rw-r--r--sound/pci/cs46xx/dsp_spos.c2
-rw-r--r--sound/pci/cs46xx/dsp_spos_scb_lib.c8
-rw-r--r--sound/pci/cs5535audio/cs5535audio.c13
-rw-r--r--sound/pci/ctxfi/xfi.c23
-rw-r--r--sound/pci/echoaudio/echoaudio.c18
-rw-r--r--sound/pci/echoaudio/echoaudio.h4
-rw-r--r--sound/pci/emu10k1/emu10k1x.c13
-rw-r--r--sound/pci/ens1370.c13
-rw-r--r--sound/pci/es1938.c13
-rw-r--r--sound/pci/es1968.c13
-rw-r--r--sound/pci/fm801.c13
-rw-r--r--sound/pci/ice1712/ice1724.c13
-rw-r--r--sound/pci/intel8x0.c14
-rw-r--r--sound/pci/intel8x0m.c13
-rw-r--r--sound/pci/korg1212/korg1212.c11
-rw-r--r--sound/pci/lola/lola.c13
-rw-r--r--sound/pci/lx6464es/lx6464es.c11
-rw-r--r--sound/pci/lx6464es/lx_core.c2
-rw-r--r--sound/pci/lx6464es/lx_defs.h2
-rw-r--r--sound/pci/maestro3.c11
-rw-r--r--sound/pci/oxygen/oxygen_lib.c15
-rw-r--r--sound/pci/oxygen/oxygen_pcm.c37
-rw-r--r--sound/pci/pcxhr/pcxhr_core.c4
-rw-r--r--sound/pci/riptide/riptide.c15
-rw-r--r--sound/pci/rme32.c13
-rw-r--r--sound/pci/rme96.c13
-rw-r--r--sound/pci/rme9652/hdsp.c13
-rw-r--r--sound/pci/rme9652/hdspm.c12
-rw-r--r--sound/pci/rme9652/rme9652.c11
-rw-r--r--sound/pci/sis7019.c13
-rw-r--r--sound/pci/sonicvibes.c13
-rw-r--r--sound/pci/trident/trident_memory.c2
-rw-r--r--sound/pci/via82xx.c24
-rw-r--r--sound/pci/via82xx_modem.c13
-rw-r--r--sound/pci/vx222/vx222_ops.c2
-rw-r--r--sound/pci/ymfpci/ymfpci.c13
-rw-r--r--sound/pcmcia/vx/vxp_ops.c2
-rw-r--r--sound/ppc/powermac.c35
-rw-r--r--sound/ppc/tumbler.c2
-rw-r--r--sound/sh/sh_dac_audio.c22
-rw-r--r--sound/soc/codecs/hda.c7
-rw-r--r--sound/soc/codecs/tas675x.c10
-rw-r--r--sound/soc/fsl/fsl_asrc_m2m.c2
-rw-r--r--sound/soc/sof/intel/hda-codec.c4
-rw-r--r--sound/sparc/cs4231.c49
-rw-r--r--sound/sparc/dbri.c37
-rw-r--r--sound/synth/emux/emux_oss.c14
-rw-r--r--sound/usb/6fire/control.c32
-rw-r--r--sound/usb/6fire/pcm.c334
-rw-r--r--sound/usb/6fire/pcm.h7
-rw-r--r--sound/usb/Makefile1
-rw-r--r--sound/usb/caiaq/Makefile2
-rw-r--r--sound/usb/caiaq/device.c22
-rw-r--r--sound/usb/caiaq/device.h16
-rw-r--r--sound/usb/caiaq/input.c29
-rw-r--r--sound/usb/caiaq/lcd.c258
-rw-r--r--sound/usb/caiaq/lcd.h7
-rw-r--r--sound/usb/caiaq/midi.c2
-rw-r--r--sound/usb/card.h1
-rw-r--r--sound/usb/clock.c27
-rw-r--r--sound/usb/endpoint.c16
-rw-r--r--sound/usb/line6/capture.c3
-rw-r--r--sound/usb/line6/pcm.c16
-rw-r--r--sound/usb/midi.c69
-rw-r--r--sound/usb/mixer.c388
-rw-r--r--sound/usb/mixer.h10
-rw-r--r--sound/usb/mixer_evo.c505
-rw-r--r--sound/usb/mixer_evo.h12
-rw-r--r--sound/usb/mixer_maps.c13
-rw-r--r--sound/usb/mixer_quirks.c7
-rw-r--r--sound/usb/mixer_scarlett.c18
-rw-r--r--sound/usb/mixer_us16x08.c39
-rw-r--r--sound/usb/mixer_us16x08.h2
-rw-r--r--sound/usb/quirks-table.h253
-rw-r--r--sound/usb/quirks.c281
-rw-r--r--sound/usb/usbaudio.h6
-rw-r--r--sound/virtio/virtio_pcm.h2
-rw-r--r--sound/virtio/virtio_pcm_msg.c19
-rw-r--r--sound/virtio/virtio_pcm_ops.c1
-rw-r--r--sound/x86/intel_hdmi_audio.c19
-rw-r--r--tools/testing/selftests/alsa/.gitignore1
-rw-r--r--tools/testing/selftests/alsa/Makefile2
-rw-r--r--tools/testing/selftests/alsa/aloop-test.c345
-rw-r--r--tools/testing/selftests/alsa/mixer-test.c490
-rw-r--r--tools/testing/selftests/alsa/utimer-test.c6
195 files changed, 5300 insertions, 1541 deletions
diff --git a/Documentation/sound/alsa-configuration.rst b/Documentation/sound/alsa-configuration.rst
index e500ec84e3ef..689eccf6a592 100644
--- a/Documentation/sound/alsa-configuration.rst
+++ b/Documentation/sound/alsa-configuration.rst
@@ -2407,6 +2407,12 @@ quirk_flags
the requested rate. A device advertising a single rate is otherwise
never sent the request at all, and some require it before streaming
will start.
+ * bit 33: ``swap_rates``
+ The clock source applies a requested rate to the other base-rate
+ family, i.e. asking for 44100 makes the device run at 48000 and vice
+ versa (same for 88200 <-> 96000 and 176400 <-> 192000). The driver
+ sends the partner rate in SET_CUR so the device ends up running at the
+ requested one.
This module supports multiple devices, autoprobe and hotplugging.
diff --git a/Documentation/sound/cards/hdspm.rst b/Documentation/sound/cards/hdspm.rst
index 5373e51ed076..deba92267942 100644
--- a/Documentation/sound/cards/hdspm.rst
+++ b/Documentation/sound/cards/hdspm.rst
@@ -6,9 +6,11 @@ Software Interface ALSA-DSP MADI Driver
2004 - winfried ritsch
+driver name - snd-hdspm
+
Full functionality has been added to the driver. Since some of
-the Controls and startup-options are ALSA-Standard and only the
+the Controls and startup options are ALSA-Standard, only the
special Controls are described and discussed below.
@@ -18,7 +20,7 @@ Hardware functionality
Audio transmission
------------------
-* number of channels -- depends on transmission mode
+* number of channels -- depends on transmission mode
The number of channels chosen is from 1..Nmax. The reason to
use for a lower number of channels is only resource allocation,
@@ -29,29 +31,29 @@ Audio transmission
* Single Speed -- 1..64 channels
.. note::
- (Note: Choosing the 56channel mode for transmission or as
+ Choosing the 56-channel mode for transmission or as
receiver, only 56 are transmitted/received over the MADI, but
all 64 channels are available for the mixer, so channel count
- for the driver)
+ for the driver.
* Double Speed -- 1..32 channels
.. note::
- Note: Choosing the 56-channel mode for
- transmission/receive-mode , only 28 are transmitted/received
+ Choosing the 56-channel mode for
+ transmission/receive-mode, only 28 are transmitted/received
over the MADI, but all 32 channels are available for the mixer,
- so channel count for the driver
+ so channel count for the driver.
* Quad Speed -- 1..16 channels
.. note::
Choosing the 56-channel mode for
- transmission/receive-mode , only 14 are transmitted/received
+ transmission/receive-mode, only 14 are transmitted/received
over the MADI, but all 16 channels are available for the mixer,
- so channel count for the driver
+ so channel count for the driver.
-* Format -- signed 32 Bit Little Endian (SNDRV_PCM_FMTBIT_S32_LE)
+* Format -- signed 32-bit Little Endian (SNDRV_PCM_FMTBIT_S32_LE)
* Sample Rates --
@@ -67,30 +69,6 @@ Audio transmission
* fragments -- 2
-* Hardware-pointer -- 2 Modi
-
-
- The Card supports the readout of the actual Buffer-pointer,
- where DMA reads/writes. Since of the bulk mode of PCI it is only
- 64 Byte accurate. SO it is not really usable for the
- ALSA-mid-level functions (here the buffer-ID gives a better
- result), but if MMAP is used by the application. Therefore it
- can be configured at load-time with the parameter
- precise-pointer.
-
-
-.. hint::
- (Hint: Experimenting I found that the pointer is maximum 64 to
- large never to small. So if you subtract 64 you always have a
- safe pointer for writing, which is used on this mode inside
- ALSA. In theory now you can get now a latency as low as 16
- Samples, which is a quarter of the interrupt possibilities.)
-
- * Precise Pointer -- off
- interrupt used for pointer-calculation
-
- * Precise Pointer -- on
- hardware pointer used.
Controller
----------
@@ -104,7 +82,7 @@ hwdep-interface.
Also all 128+256 Peak and RMS-Meter can be accessed via the
hwdep-interface. Since it could be a performance problem always
copying and converting Peak and RMS-Levels even if you just need
-one, I decided to export the hardware structure, so that of
+one, I decided to export the hardware structure, so that if
needed some driver-guru can implement a memory-mapping of mixer
or peak-meters over ioctl, or also to do only copying and no
conversion. A test-application shows the usage of the controller.
@@ -112,9 +90,9 @@ conversion. A test-application shows the usage of the controller.
* Latency Controls --- not implemented !!!
.. note::
- Note: Within the windows-driver the latency is accessible of a
- control-panel, but buffer-sizes are controlled with ALSA from
- hwparams-calls and should not be changed in run-state, I did not
+ Within the Windows driver the latency is accessible via
+ Control Panel, but buffer sizes are controlled with ALSA from
+ hwparams calls and should not be changed in run-state. I did not
implement it here.
@@ -127,12 +105,12 @@ conversion. A test-application shows the usage of the controller.
* Values -- "Master" "Slave"
.. note::
- !!!! This is a hardware-function but is in conflict with the
- Clock-source controller, which is a kind of ALSA-standard. I
+ !!!! This is a hardware function but is in conflict with the
+ Clock-source controller, which is a kind of ALSA-standard. It
makes sense to set the card to a special mode (master at some
- frequency or slave), since even not using an Audio-application
+ frequency or slave), since even not using an Audio application
a studio should have working synchronisations setup. So use
- Clock-source-controller instead !!!!
+ Clock-source controller instead !!!!
* Clock Source
@@ -145,11 +123,11 @@ conversion. A test-application shows the usage of the controller.
"Internal 96.0 kHz"
Choose between Master at a specific Frequency and so also the
- Speed-mode or Slave (Autosync). Also see "Preferred Sync Ref"
+ Speed-mode or Slave (Autosync). Also see "Preferred Sync Ref".
.. warning::
- !!!! This is no pure hardware function but was implemented by
- ALSA by some ALSA-drivers before, so I use it also. !!!
+ !!!! This is not a pure hardware function but was implemented by
+ ALSA by some ALSA drivers before, so I use it also. !!!
* Preferred Sync Ref
@@ -165,7 +143,7 @@ conversion. A test-application shows the usage of the controller.
chosen. If it is not available another is used if possible.
.. note::
- Note: Since MADI has a much higher bit-rate than word-clock, the
+ Since MADI has a much higher bit-rate than word-clock, the
card should synchronise better in MADI Mode. But since the
RME-PLL is very good, there are almost no problems with
word-clock too. I never found a difference.
@@ -179,12 +157,12 @@ conversion. A test-application shows the usage of the controller.
* Values -- 0 1
- Using 64-channel-modus (1) or 56-channel-modus for
+ Using 64-channel mode (1) or 56-channel mode for
MADI-transmission (0).
.. note::
- Note: This control is for output only. Input-mode is detected
+ This control is for output only. Input mode is detected
automatically from hardware sending MADI.
@@ -200,7 +178,7 @@ conversion. A test-application shows the usage of the controller.
Don't use to lower 5 Audio-bits on AES as additional Bits.
-* Safe Mode oder Auto Input
+* Safe Mode or Auto Input
* Name -- "Safe Mode"
@@ -222,7 +200,7 @@ conversion. A test-application shows the usage of the controller.
* Values -- optical coaxial
- Choosing the Input, optical or coaxial. If Safe-mode is active,
+ Choosing the Input, optical or coaxial. If Safe mode is active,
this is the preferred Input.
Mixer
@@ -240,7 +218,7 @@ Mixer
Here as a first value the channel-index is taken to get/set the
corresponding mixer channel, where 0-63 are the input to output
fader and 64-127 the playback to outputs fader. Value 0
- is channel muted 0 and 32768 an amplification of 1.
+ is channel muted 0 and 32768 an amplification of 1.
* Chn 1-64
@@ -250,14 +228,14 @@ Mixer
* Line Out
- * Name -- "Line Out"
+ * Name -- "Line Out"
* Access -- Read Write
* Values -- 0 1
Switching on and off the analog out, which has nothing to do
- with mixing or routing. the analog outs reflects channel 63,64.
+ with mixing or routing. The analog outs reflects channel 63,64.
Information (only read access)
@@ -280,7 +258,7 @@ Information (only read access)
Should be "Autosync Rate", but Name used is
- ALSA-Scheme. External Sample frequency liked used on Autosync is
+ ALSA-Scheme. External Sample frequency like used on Autosync is
reported.
@@ -315,19 +293,19 @@ Information (only read access)
Sync-Reference is either "WordClock", "MADI" or none.
-* RX 64ch --- noch nicht implementiert
+* RX 64ch --- not yet implemented
- MADI-Receiver is in 64 channel mode oder 56 channel mode.
+ MADI-Receiver is in 64-channel mode or 56-channel mode.
-* AB_inp --- not tested
+* AB_inp --- not tested
Used input for Auto-Input.
* actual Buffer Position --- not implemented
- !!! this is a ALSA internal function, so no control is used !!!
+ !!! This is an ALSA internal function, so no control is used !!!
@@ -352,22 +330,15 @@ Calling Parameter
note: ALSA-standard
-* precise_ptr int array (min = 1, max = 8)
-
- Enable precise pointer, or disable.
-
-.. note::
- note: Use only when the application supports this (which is a special case).
-
* line_outs_monitor int array (min = 1, max = 8)
Send playback streams to analog outs by default.
.. note::
- note: each playback channel is mixed to the same numbered output
+ Each playback channel is mixed to the same numbered output
channel (routed). This is against the ALSA-convention, where all
channels have to be muted on after loading the driver, but was
- used before on other cards, so i historically use it again)
+ used before on other cards, so I historically use it again.
@@ -375,5 +346,5 @@ Calling Parameter
Enable Analog Out on Channel 63/64 by default.
-.. note ::
- note: here the analog output is enabled (but not routed).
+.. note::
+ Here the analog output is enabled (but not routed).
diff --git a/MAINTAINERS b/MAINTAINERS
index f53047aa8d3c..8883889b25c8 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4346,6 +4346,13 @@ F: drivers/net/ieee802154/at86rf230.h
F: drivers/net/ieee802154/atusb.c
F: drivers/net/ieee802154/atusb.h
+AUDIENT EVO MIXER DRIVER
+M: Christian Ruppert <arc@gmx.li>
+L: linux-sound@vger.kernel.org
+S: Maintained
+F: sound/usb/mixer_evo.c
+F: sound/usb/mixer_evo.h
+
AUDIT SUBSYSTEM
M: Paul Moore <paul@paul-moore.com>
M: Eric Paris <eparis@redhat.com>
@@ -4419,6 +4426,12 @@ L: linux-media@vger.kernel.org
S: Maintained
F: drivers/media/pci/hws/
+AWINIC AW88399 HDA SIDE CODEC DRIVER
+M: Marco Giunta <marco_giunta@outlook.it>
+L: linux-sound@vger.kernel.org
+S: Maintained
+F: sound/hda/codecs/side-codecs/aw88399*
+
AWINIC AW99706 WLED BACKLIGHT DRIVER
M: Junjie Cao <junjie.cao@linux.dev>
S: Maintained
diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c
index 163a3cccf197..41a4756f0ea9 100644
--- a/drivers/acpi/scan.c
+++ b/drivers/acpi/scan.c
@@ -1764,6 +1764,7 @@ static bool acpi_device_enumeration_by_parent(struct acpi_device *device)
/* Non-conforming _HID for Cirrus Logic already released */
{"CLSA0100", },
{"CLSA0101", },
+ {"CLSA0102", },
/*
* Some ACPI devs contain SerialBus resources even though they are not
* attached to a serial bus at all.
diff --git a/drivers/platform/x86/serial-multi-instantiate.c b/drivers/platform/x86/serial-multi-instantiate.c
index 3e89fcdd45f7..d535025a3f73 100644
--- a/drivers/platform/x86/serial-multi-instantiate.c
+++ b/drivers/platform/x86/serial-multi-instantiate.c
@@ -426,6 +426,7 @@ static const struct acpi_device_id smi_acpi_ids[] = {
/* Non-conforming _HID for Cirrus Logic already released */
{ "CLSA0100", (unsigned long)&cs35l41_hda },
{ "CLSA0101", (unsigned long)&cs35l41_hda },
+ { "CLSA0102", (unsigned long)&cs35l41_hda },
{ }
};
MODULE_DEVICE_TABLE(acpi, smi_acpi_ids);
diff --git a/include/sound/control.h b/include/sound/control.h
index 909db0d0485d..8e24b668d796 100644
--- a/include/sound/control.h
+++ b/include/sound/control.h
@@ -244,7 +244,7 @@ int _snd_ctl_add_follower(struct snd_kcontrol *master,
* @follower: follower element to add
*
* Add a virtual follower control to the given master element created via
- * snd_ctl_create_virtual_master() beforehand.
+ * snd_ctl_make_virtual_master() beforehand.
*
* All followers must be the same type (returning the same information
* via info callback). The function doesn't check it, so it's your
@@ -289,7 +289,7 @@ snd_ctl_add_follower_uncached(struct snd_kcontrol *master,
return _snd_ctl_add_follower(master, follower, SND_CTL_FOLLOWER_NEED_UPDATE);
}
-int snd_ctl_add_vmaster_hook(struct snd_kcontrol *kctl,
+int snd_ctl_add_vmaster_hook(struct snd_card *card, struct snd_kcontrol *kctl,
void (*hook)(void *private_data, int),
void *private_data);
void snd_ctl_sync_vmaster(struct snd_kcontrol *kctl, bool hook_only);
diff --git a/include/sound/core.h b/include/sound/core.h
index 15be947a730e..d6584967fd49 100644
--- a/include/sound/core.h
+++ b/include/sound/core.h
@@ -84,6 +84,7 @@ struct snd_device {
struct snd_refcount {
atomic_t count;
wait_queue_head_t waiter;
+ spinlock_t lock;
};
void snd_refcount_init(struct snd_refcount *ref);
@@ -300,7 +301,8 @@ int snd_card_locked(int card);
#define SND_MIXER_OSS_NOTIFY_REGISTER 0
#define SND_MIXER_OSS_NOTIFY_DISCONNECT 1
#define SND_MIXER_OSS_NOTIFY_FREE 2
-extern int (*snd_mixer_oss_notify_callback)(struct snd_card *card, int cmd);
+void snd_mixer_oss_set_notify(int (*callback)(struct snd_card *card, int cmd));
+void snd_mixer_oss_notify(struct snd_card *card, int cmd);
#endif
int snd_card_new(struct device *parent, int idx, const char *xid,
@@ -314,7 +316,6 @@ void snd_card_disconnect(struct snd_card *card);
void snd_card_disconnect_sync(struct snd_card *card);
void snd_card_free(struct snd_card *card);
void snd_card_free_when_closed(struct snd_card *card);
-int snd_card_free_on_error(struct device *dev, int ret);
void snd_card_set_id(struct snd_card *card, const char *id);
int snd_card_register(struct snd_card *card);
int snd_card_info_init(void);
diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h
index 4f94565c9d15..6474239a11c1 100644
--- a/include/sound/emu10k1.h
+++ b/include/sound/emu10k1.h
@@ -633,8 +633,8 @@ SUB_REG(PEFE, FILTERAMOUNT, 0x000000ff) /* Filter envlope amount */
#define A_ADCCR_RCHANENABLE 0x00000020
#define A_ADCCR_LCHANENABLE 0x00000010
-#define A_ADCCR_SAMPLERATE_MASK 0x0000000F /* Audigy sample rate convertor output rate */
-#define ADCCR_SAMPLERATE_MASK 0x00000007 /* Sample rate convertor output rate */
+#define A_ADCCR_SAMPLERATE_MASK 0x0000000F /* Audigy sample rate converter output rate */
+#define ADCCR_SAMPLERATE_MASK 0x00000007 /* Sample rate converter output rate */
#define ADCCR_SAMPLERATE_48 0x00000000 /* 48kHz sample rate */
#define ADCCR_SAMPLERATE_44 0x00000001 /* 44.1kHz sample rate */
#define ADCCR_SAMPLERATE_32 0x00000002 /* 32kHz sample rate */
diff --git a/include/sound/emux_legacy.h b/include/sound/emux_legacy.h
index 1127e30d33bb..55a7fbfb9bfa 100644
--- a/include/sound/emux_legacy.h
+++ b/include/sound/emux_legacy.h
@@ -5,7 +5,7 @@
/*
* Copyright (c) 1999-2000 Takashi Iwai <tiwai@suse.de>
*
- * Definitions of OSS compatible headers for Emu8000 device informations
+ * Definitions of OSS compatible headers for Emu8000 device information
*/
#include <sound/seq_oss_legacy.h>
diff --git a/include/sound/hda_codec.h b/include/sound/hda_codec.h
index 1d05f991f2ce..c7f29ad64e02 100644
--- a/include/sound/hda_codec.h
+++ b/include/sound/hda_codec.h
@@ -28,6 +28,7 @@ struct hda_codec;
struct hda_pcm;
struct hda_pcm_stream;
struct hda_codec_ops;
+struct hda_jack_tbl;
/*
* codec bus
@@ -274,7 +275,8 @@ struct hda_codec {
struct hda_codec *codec, hda_nid_t nid);
/* jack detection */
- struct snd_array jacktbl;
+ struct hda_jack_tbl *jacktbl, *jacktbl_last; /* linked list head/tail */
+ int jacktbl_used; /* number of jacktbl elements */
unsigned long jackpoll_interval; /* In jiffies. Zero means no poll, rely on unsol events */
struct delayed_work jackpoll_work;
@@ -519,7 +521,7 @@ void snd_hda_update_power_acct(struct hda_codec *codec);
static inline bool hda_codec_need_resume(struct hda_codec *codec)
{
- return !codec->relaxed_resume && codec->jacktbl.used;
+ return !codec->relaxed_resume && codec->jacktbl_used;
}
/*
diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h
index aa994d6e6d35..04fd4794bcee 100644
--- a/include/sound/hdaudio.h
+++ b/include/sound/hdaudio.h
@@ -96,7 +96,8 @@ struct hdac_device {
bool lazy_cache:1; /* don't wake up for writes */
bool caps_overwriting:1; /* caps overwrite being in process */
bool cache_coef:1; /* cache COEF read/write too */
- unsigned int registered:1; /* codec was registered */
+ bool registered; /* codec was registered */
+ bool unsol_disabled; /* unsol events blocked; protected by bus->reg_lock */
};
/* device/driver type used for matching */
@@ -123,6 +124,7 @@ int snd_hdac_device_init(struct hdac_device *dev, struct hdac_bus *bus,
const char *name, unsigned int addr);
void snd_hdac_device_exit(struct hdac_device *dev);
int snd_hdac_device_register(struct hdac_device *codec);
+void snd_hdac_device_disable_unsol(struct hdac_device *codec);
void snd_hdac_device_unregister(struct hdac_device *codec);
int snd_hdac_device_set_chip_name(struct hdac_device *codec, const char *name);
int snd_hdac_codec_modalias(const struct hdac_device *hdac, char *buf, size_t size);
@@ -179,9 +181,9 @@ static inline int snd_hdac_read_parm(struct hdac_device *codec, hda_nid_t nid,
#ifdef CONFIG_PM
int snd_hdac_power_up(struct hdac_device *codec);
-int snd_hdac_power_down(struct hdac_device *codec);
+void snd_hdac_power_down(struct hdac_device *codec);
int snd_hdac_power_up_pm(struct hdac_device *codec);
-int snd_hdac_power_down_pm(struct hdac_device *codec);
+void snd_hdac_power_down_pm(struct hdac_device *codec);
int snd_hdac_keep_power_up(struct hdac_device *codec);
/* call this at entering into suspend/resume callbacks in codec driver */
@@ -207,9 +209,9 @@ static inline bool snd_hdac_is_power_on(struct hdac_device *codec)
}
#else
static inline int snd_hdac_power_up(struct hdac_device *codec) { return 0; }
-static inline int snd_hdac_power_down(struct hdac_device *codec) { return 0; }
+static inline void snd_hdac_power_down(struct hdac_device *codec) {}
static inline int snd_hdac_power_up_pm(struct hdac_device *codec) { return 0; }
-static inline int snd_hdac_power_down_pm(struct hdac_device *codec) { return 0; }
+static inline void snd_hdac_power_down_pm(struct hdac_device *codec) {}
static inline int snd_hdac_keep_power_up(struct hdac_device *codec) { return 0; }
static inline void snd_hdac_enter_pm(struct hdac_device *codec) {}
static inline void snd_hdac_leave_pm(struct hdac_device *codec) {}
diff --git a/include/sound/jack.h b/include/sound/jack.h
index 715b95983188..cf19c333e2eb 100644
--- a/include/sound/jack.h
+++ b/include/sound/jack.h
@@ -62,10 +62,10 @@ struct snd_jack {
struct list_head kctl_list;
struct snd_card *card;
const char *id;
+ bool registered;
#ifdef CONFIG_SND_JACK_INPUT_DEV
struct input_dev *input_dev;
struct mutex input_dev_lock;
- int registered;
int type;
char name[100];
unsigned int key[6]; /* Keep in sync with definitions above */
diff --git a/include/sound/pcm.h b/include/sound/pcm.h
index 02d8689354c4..1cf511f8d880 100644
--- a/include/sound/pcm.h
+++ b/include/sound/pcm.h
@@ -8,6 +8,7 @@
* Abramo Bagnara <abramo@alsa-project.org>
*/
+#include <sound/core.h>
#include <sound/asound.h>
#include <sound/memalloc.h>
#include <sound/minors.h>
@@ -414,6 +415,7 @@ struct snd_pcm_runtime {
snd_pcm_uframes_t twake; /* do transfer (!poll) wakeup if non-zero */
wait_queue_head_t sleep; /* poll sleep */
wait_queue_head_t tsleep; /* transfer sleep */
+ struct snd_refcount refcnt; /* refcount for draining, etc */
struct snd_fasync *fasync;
bool stop_operating; /* sync_stop will be called */
struct mutex buffer_mutex; /* protect for buffer changes */
diff --git a/include/sound/pcm_oss.h b/include/sound/pcm_oss.h
index c96f23c7755b..43f765300031 100644
--- a/include/sound/pcm_oss.h
+++ b/include/sound/pcm_oss.h
@@ -70,6 +70,7 @@ struct snd_pcm_oss_stream {
struct snd_pcm_oss {
int reg;
unsigned int reg_mask;
+ bool registered;
};
#endif /* __SOUND_PCM_OSS_H */
diff --git a/include/sound/rawmidi.h b/include/sound/rawmidi.h
index 4154035af414..d812f70725bd 100644
--- a/include/sound/rawmidi.h
+++ b/include/sound/rawmidi.h
@@ -86,6 +86,7 @@ struct snd_rawmidi_substream {
bool opened; /* open flag */
bool append; /* append flag (merge more streams) */
bool active_sensing; /* send active sensing when close */
+ bool disconnected; /* already disconnected */
unsigned int framing; /* whether to frame input data */
unsigned int clock_type; /* clock source to use for input framing */
int use_count; /* use counter (for output) */
diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h
index c11da9656e38..767c96273245 100644
--- a/include/uapi/sound/asound.h
+++ b/include/uapi/sound/asound.h
@@ -99,9 +99,10 @@ enum {
SNDRV_HWDEP_IFACE_LINE6, /* Line6 USB processors */
SNDRV_HWDEP_IFACE_FW_MOTU, /* MOTU FireWire series */
SNDRV_HWDEP_IFACE_FW_FIREFACE, /* RME Fireface series */
+ SNDRV_HWDEP_IFACE_CAIAQ, /* Native Instruments caiaq USB devices */
/* Don't forget to change the following: */
- SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_FIREFACE
+ SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_CAIAQ
};
struct snd_hwdep_info {
diff --git a/sound/core/control.c b/sound/core/control.c
index 4199342d4ffe..535ceba294ac 100644
--- a/sound/core/control.c
+++ b/sound/core/control.c
@@ -1802,6 +1802,8 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file,
alloc_size = compute_user_elem_size(private_size, count);
guard(rwsem_write)(&card->controls_rwsem);
+ if (card->shutdown)
+ return -ENODEV;
if (check_user_elem_overflow(card, alloc_size))
return -ENOMEM;
diff --git a/sound/core/control_led.c b/sound/core/control_led.c
index ec0e6c7ad657..501387287ae6 100644
--- a/sound/core/control_led.c
+++ b/sound/core/control_led.c
@@ -563,12 +563,43 @@ static ssize_t set_led_id(struct snd_ctl_led_card *led_card, const char *buf, si
return count;
}
+/*
+ * snd_ctl_led_disconnect() removes these files with card->controls_rwsem
+ * held. so don't let the removal wait for a callback that takes it.
+ */
+static struct kernfs_node *snd_ctl_led_sysfs_enter(struct device *dev,
+ struct device_attribute *attr)
+{
+ struct kernfs_node *kn;
+
+ if (!try_module_get(THIS_MODULE))
+ return NULL;
+ kn = sysfs_break_active_protection(&dev->kobj, &attr->attr);
+ if (!kn)
+ module_put(THIS_MODULE);
+ return kn;
+}
+
+static void snd_ctl_led_sysfs_exit(struct kernfs_node *kn)
+{
+ sysfs_unbreak_active_protection(kn);
+ module_put(THIS_MODULE);
+}
+
static ssize_t attach_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev);
- return set_led_id(led_card, buf, count, true);
+ struct kernfs_node *kn;
+ ssize_t ret;
+
+ kn = snd_ctl_led_sysfs_enter(dev, attr);
+ if (!kn)
+ return -ENODEV;
+ ret = set_led_id(led_card, buf, count, true);
+ snd_ctl_led_sysfs_exit(kn);
+ return ret;
}
static ssize_t detach_store(struct device *dev,
@@ -576,7 +607,15 @@ static ssize_t detach_store(struct device *dev,
const char *buf, size_t count)
{
struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev);
- return set_led_id(led_card, buf, count, false);
+ struct kernfs_node *kn;
+ ssize_t ret;
+
+ kn = snd_ctl_led_sysfs_enter(dev, attr);
+ if (!kn)
+ return -ENODEV;
+ ret = set_led_id(led_card, buf, count, false);
+ snd_ctl_led_sysfs_exit(kn);
+ return ret;
}
static ssize_t reset_store(struct device *dev,
@@ -594,10 +633,8 @@ static ssize_t reset_store(struct device *dev,
return count;
}
-static ssize_t list_show(struct device *dev,
- struct device_attribute *attr, char *buf)
+static ssize_t snd_ctl_led_list(struct snd_ctl_led_card *led_card, char *buf)
{
- struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev);
struct snd_ctl_led_ctl *lctl;
size_t l = 0;
struct snd_card *card __free(snd_card_unref) =
@@ -620,6 +657,21 @@ static ssize_t list_show(struct device *dev,
return l;
}
+static ssize_t list_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev);
+ struct kernfs_node *kn;
+ ssize_t ret;
+
+ kn = snd_ctl_led_sysfs_enter(dev, attr);
+ if (!kn)
+ return -ENODEV;
+ ret = snd_ctl_led_list(led_card, buf);
+ snd_ctl_led_sysfs_exit(kn);
+ return ret;
+}
+
static DEVICE_ATTR_WO(attach);
static DEVICE_ATTR_WO(detach);
static DEVICE_ATTR_WO(reset);
diff --git a/sound/core/ctljack.c b/sound/core/ctljack.c
index 6b6ab34fbde8..36700fda79f3 100644
--- a/sound/core/ctljack.c
+++ b/sound/core/ctljack.c
@@ -38,6 +38,7 @@ static int get_available_index(struct snd_card *card, const char *name)
sid.iface = SNDRV_CTL_ELEM_IFACE_CARD;
strscpy(sid.name, name, sizeof(sid.name));
+ guard(rwsem_read)(&card->controls_rwsem);
while (snd_ctl_find_id(card, &sid)) {
sid.index++;
/* reset numid; otherwise snd_ctl_find_id() hits this again */
diff --git a/sound/core/info.c b/sound/core/info.c
index 3b8ccda04e1a..c4d57d0809ad 100644
--- a/sound/core/info.c
+++ b/sound/core/info.c
@@ -528,6 +528,8 @@ int snd_info_card_register(struct snd_card *card)
void snd_info_card_id_change(struct snd_card *card)
{
guard(mutex)(&info_mutex);
+ if (card->shutdown)
+ return;
if (card->proc_root_link) {
proc_remove(card->proc_root_link);
card->proc_root_link = NULL;
@@ -555,7 +557,6 @@ void snd_info_card_disconnect(struct snd_card *card)
if (card->proc_root)
snd_info_clear_entries(card->proc_root);
card->proc_root_link = NULL;
- card->proc_root = NULL;
}
/*
diff --git a/sound/core/init.c b/sound/core/init.c
index 1bcb6a2e7550..d518cb838777 100644
--- a/sound/core/init.c
+++ b/sound/core/init.c
@@ -82,8 +82,24 @@ static int module_slot_match(struct module *module, int idx)
}
#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);
+static DEFINE_MUTEX(mixer_oss_notify_mutex);
+static int (*mixer_oss_notify_callback)(struct snd_card *card, int cmd);
+
+void snd_mixer_oss_notify(struct snd_card *card, int cmd)
+{
+ guard(mutex)(&mixer_oss_notify_mutex);
+ if (mixer_oss_notify_callback)
+ mixer_oss_notify_callback(card, cmd);
+}
+EXPORT_SYMBOL(snd_mixer_oss_notify);
+
+void snd_mixer_oss_set_notify(int (*callback)(struct snd_card *, int))
+{
+ /* Wait for callbacks, including a final module_put(), to return. */
+ guard(mutex)(&mixer_oss_notify_mutex);
+ mixer_oss_notify_callback = callback;
+}
+EXPORT_SYMBOL(snd_mixer_oss_set_notify);
#endif
static int check_empty_slot(struct module *module, int slot)
@@ -216,11 +232,13 @@ static void __snd_card_release(struct device *dev, void *data)
* is added automatically. In that way, the resource disconnection is assured
* at first, then released in the expected order.
*
- * If an error happens at the probe before snd_card_register() is called and
- * there have been other devres resources, you'd need to free the card manually
- * via snd_card_free() call in the error; otherwise it may lead to UAF due to
- * devres call orders. You can use snd_card_free_on_error() helper for
- * handling it more easily.
+ * Caveat: if an error happens at the probe before snd_card_register() is
+ * called and there have been other devres resources that are referred at the
+ * card-level destructor call, you'd still need to free the card manually;
+ * otherwise it may lead to UAF due to devres call orders. Instead of a manual
+ * snd_card_free() call, you can use the automatic clean-up for the card
+ * pointer via __free(snd_card_free), too. In this case, don't forget to do
+ * NULL-clear the card pointer before the successful return!
*
* Return: zero if successful, or a negative error code
*/
@@ -250,30 +268,6 @@ int snd_devm_card_new(struct device *parent, int idx, const char *xid,
}
EXPORT_SYMBOL_GPL(snd_devm_card_new);
-/**
- * snd_card_free_on_error - a small helper for handling devm probe errors
- * @dev: the managed device object
- * @ret: the return code from the probe callback
- *
- * This function handles the explicit snd_card_free() call at the error from
- * the probe callback. It's just a small helper for simplifying the error
- * handling with the managed devices.
- *
- * Return: zero if successful, or a negative error code
- */
-int snd_card_free_on_error(struct device *dev, int ret)
-{
- struct snd_card *card;
-
- if (!ret)
- return 0;
- card = devres_find(dev, __snd_card_release, NULL, NULL);
- if (card)
- snd_card_free(card);
- return ret;
-}
-EXPORT_SYMBOL_GPL(snd_card_free_on_error);
-
static int snd_card_init(struct snd_card *card, struct device *parent,
int idx, const char *xid, struct module *module,
size_t extra_size)
@@ -356,7 +350,7 @@ static int snd_card_init(struct snd_card *card, struct device *parent,
err = snd_info_card_create(card);
if (err < 0) {
dev_err(parent, "unable to create card info\n");
- goto __error_ctl;
+ goto __error;
}
#ifdef CONFIG_SND_DEBUG
@@ -365,16 +359,16 @@ static int snd_card_init(struct snd_card *card, struct device *parent,
#endif
#ifdef CONFIG_SND_CTL_DEBUG
card->value_buf = kmalloc_obj(*card->value_buf);
- if (!card->value_buf)
- return -ENOMEM;
+ if (!card->value_buf) {
+ err = -ENOMEM;
+ goto __error;
+ }
#endif
return 0;
- __error_ctl:
- snd_device_free_all(card);
__error:
- put_device(&card->card_dev);
- return err;
+ snd_card_free(card);
+ return err;
}
/**
@@ -530,8 +524,7 @@ void snd_card_disconnect(struct snd_card *card)
/* 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);
+ snd_mixer_oss_notify(card, SND_MIXER_OSS_NOTIFY_DISCONNECT);
#endif
/* notify all devices that we are disconnected */
@@ -557,6 +550,8 @@ void snd_card_disconnect(struct snd_card *card)
clear_bit(card->number, snd_cards_lock);
}
+ /* order card->shutdown store against power_ref read */
+ smp_mb();
snd_power_sync_ref(card);
}
EXPORT_SYMBOL(snd_card_disconnect);
@@ -587,8 +582,7 @@ static int snd_card_do_free(struct snd_card *card)
card->releasing = true;
#if IS_ENABLED(CONFIG_SND_MIXER_OSS)
- if (snd_mixer_oss_notify_callback)
- snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_FREE);
+ snd_mixer_oss_notify(card, SND_MIXER_OSS_NOTIFY_FREE);
#endif
snd_device_free_all(card);
kfree(card->components);
@@ -932,8 +926,7 @@ int snd_card_register(struct snd_card *card)
return err;
#if IS_ENABLED(CONFIG_SND_MIXER_OSS)
- if (snd_mixer_oss_notify_callback)
- snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_REGISTER);
+ snd_mixer_oss_notify(card, SND_MIXER_OSS_NOTIFY_REGISTER);
#endif
return 0;
}
@@ -1174,12 +1167,14 @@ EXPORT_SYMBOL(snd_card_file_remove);
int snd_power_ref_and_wait(struct snd_card *card)
{
snd_power_ref(card);
- if (snd_power_get_state(card) == SNDRV_CTL_POWER_D0)
- return 0;
- wait_event_cmd(card->power_sleep,
- card->shutdown ||
- snd_power_get_state(card) == SNDRV_CTL_POWER_D0,
- snd_power_unref(card), snd_power_ref(card));
+ /* order power_ref increment against card->shutdown read */
+ smp_mb__after_atomic();
+ if (snd_power_get_state(card) != SNDRV_CTL_POWER_D0) {
+ wait_event_cmd(card->power_sleep,
+ card->shutdown ||
+ snd_power_get_state(card) == SNDRV_CTL_POWER_D0,
+ snd_power_unref(card), snd_power_ref(card));
+ }
if (card->shutdown) {
snd_power_unref(card);
return -ENODEV;
diff --git a/sound/core/jack.c b/sound/core/jack.c
index 0f04018181ac..a81a64b3bfe7 100644
--- a/sound/core/jack.c
+++ b/sound/core/jack.c
@@ -38,6 +38,7 @@ static const int jack_switch_types[SND_JACK_SWITCH_TYPES] = {
};
#endif /* CONFIG_SND_JACK_INPUT_DEV */
+static void snd_jack_add_debugfs(struct snd_jack *jack);
static void snd_jack_remove_debugfs(struct snd_jack *jack);
static int snd_jack_dev_disconnect(struct snd_device *device)
@@ -68,6 +69,9 @@ static int snd_jack_dev_free(struct snd_device *device)
struct snd_card *card = device->card;
struct snd_jack_kctl *jack_kctl, *tmp_jack_kctl;
+ /* Remove debugfs while the controls are still linked. */
+ snd_jack_remove_debugfs(jack);
+
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);
@@ -85,11 +89,10 @@ static int snd_jack_dev_free(struct snd_device *device)
}
#ifdef CONFIG_SND_JACK_INPUT_DEV
-static int snd_jack_dev_register(struct snd_device *device)
+static int snd_jack_input_dev_register(struct snd_card *card,
+ struct snd_jack *jack)
{
- struct snd_jack *jack = device->device_data;
- struct snd_card *card = device->card;
- int err, i;
+ int i;
snprintf(jack->name, sizeof(jack->name), "%s %s",
card->shortname, jack->id);
@@ -117,14 +120,32 @@ static int snd_jack_dev_register(struct snd_device *device)
input_set_capability(jack->input_dev, EV_KEY, jack->key[i]);
}
- err = input_register_device(jack->input_dev);
- if (err == 0)
- jack->registered = 1;
-
- return err;
+ return input_register_device(jack->input_dev);
+}
+#else
+static inline int snd_jack_input_dev_register(struct snd_card *card,
+ struct snd_jack *jack)
+{
+ return 0;
}
#endif /* CONFIG_SND_JACK_INPUT_DEV */
+static int snd_jack_dev_register(struct snd_device *device)
+{
+ struct snd_card *card = device->card;
+ struct snd_jack *jack = device->device_data;
+ int err;
+
+ err = snd_jack_input_dev_register(card, jack);
+ if (err < 0)
+ return err;
+
+ snd_jack_add_debugfs(jack);
+
+ jack->registered = true;
+ return 0;
+}
+
#ifdef CONFIG_SND_JACK_INJECTION_DEBUG
static void snd_jack_inject_report(struct snd_jack_kctl *jack_kctl, int status)
{
@@ -346,6 +367,9 @@ static int snd_jack_debugfs_add_inject_node(struct snd_jack *jack,
char *tname;
int i;
+ if (jack_kctl->jack_debugfs_root)
+ return 0;
+
/* Don't create injection interface for Phantom jacks */
if (strstr(jack_kctl->kctl->id.name, "Phantom"))
return 0;
@@ -384,6 +408,14 @@ static int snd_jack_debugfs_add_inject_node(struct snd_jack *jack,
return 0;
}
+static void snd_jack_add_debugfs(struct snd_jack *jack)
+{
+ struct snd_jack_kctl *jack_kctl;
+
+ list_for_each_entry(jack_kctl, &jack->kctl_list, list)
+ snd_jack_debugfs_add_inject_node(jack, jack_kctl);
+}
+
static void snd_jack_remove_debugfs(struct snd_jack *jack)
{
struct snd_jack_kctl *jack_kctl;
@@ -400,6 +432,10 @@ static int snd_jack_debugfs_add_inject_node(struct snd_jack *jack,
return 0;
}
+static void snd_jack_add_debugfs(struct snd_jack *jack)
+{
+}
+
static void snd_jack_remove_debugfs(struct snd_jack *jack)
{
}
@@ -420,7 +456,8 @@ static void snd_jack_kctl_add(struct snd_jack *jack, struct snd_jack_kctl *jack_
{
jack_kctl->jack = jack;
list_add_tail(&jack_kctl->list, &jack->kctl_list);
- snd_jack_debugfs_add_inject_node(jack, jack_kctl);
+ if (jack->registered)
+ snd_jack_debugfs_add_inject_node(jack, jack_kctl);
}
static struct snd_jack_kctl * snd_jack_kctl_new(struct snd_card *card, const char *name, unsigned int mask)
@@ -450,7 +487,7 @@ static struct snd_jack_kctl * snd_jack_kctl_new(struct snd_card *card, const cha
return jack_kctl;
error:
- snd_ctl_free_one(kctl);
+ snd_ctl_remove(card, kctl);
return NULL;
}
@@ -501,9 +538,7 @@ int snd_jack_new(struct snd_card *card, const char *id, int type,
int err;
static const struct snd_device_ops ops = {
.dev_free = snd_jack_dev_free,
-#ifdef CONFIG_SND_JACK_INPUT_DEV
.dev_register = snd_jack_dev_register,
-#endif /* CONFIG_SND_JACK_INPUT_DEV */
.dev_disconnect = snd_jack_dev_disconnect,
};
diff --git a/sound/core/misc.c b/sound/core/misc.c
index 066fb9ecdcdc..6fe199e76e83 100644
--- a/sound/core/misc.c
+++ b/sound/core/misc.c
@@ -183,11 +183,13 @@ void snd_refcount_init(struct snd_refcount *ref)
{
atomic_set(&ref->count, 0);
init_waitqueue_head(&ref->waiter);
+ spin_lock_init(&ref->lock);
}
EXPORT_SYMBOL_GPL(snd_refcount_init);
void snd_refcount_put(struct snd_refcount *ref)
{
+ guard(spinlock_irqsave)(&ref->lock);
if (atomic_dec_and_test(&ref->count))
wake_up(&ref->waiter);
}
@@ -195,6 +197,7 @@ EXPORT_SYMBOL_GPL(snd_refcount_put);
void snd_refcount_sync(struct snd_refcount *ref)
{
- wait_event(ref->waiter, !atomic_read(&ref->count));
+ guard(spinlock_irq)(&ref->lock);
+ wait_event_lock_irq(ref->waiter, !atomic_read(&ref->count), ref->lock);
}
EXPORT_SYMBOL_GPL(snd_refcount_sync);
diff --git a/sound/core/oss/mixer_oss.c b/sound/core/oss/mixer_oss.c
index c533d767c29a..a51bc68473e6 100644
--- a/sound/core/oss/mixer_oss.c
+++ b/sound/core/oss/mixer_oss.c
@@ -1272,6 +1272,7 @@ static void snd_mixer_oss_build(struct snd_mixer_oss *mixer)
};
unsigned int idx;
+ guard(mutex)(&mixer->reg_mutex);
for (idx = 0; idx < ARRAY_SIZE(table); idx++)
snd_mixer_oss_build_input(mixer, &table[idx], 0, 0);
if (mixer->mask_recsrc) {
@@ -1308,13 +1309,22 @@ static int snd_mixer_oss_free1(void *private)
static int snd_mixer_oss_notify_handler(struct snd_card *card, int cmd)
{
struct snd_mixer_oss *mixer;
+ int err;
if (cmd == SND_MIXER_OSS_NOTIFY_REGISTER) {
- int idx, err;
+ int idx;
+
+ /* Module loading and card registration can both notify us. */
+ if (card->mixer_oss)
+ return 0;
mixer = kzalloc_objs(*mixer, 2);
if (mixer == NULL)
return -ENOMEM;
+ if (!try_module_get(THIS_MODULE)) {
+ kfree(mixer);
+ return -ENODEV;
+ }
mutex_init(&mixer->reg_mutex);
err = snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_MIXER,
card, 0,
@@ -1324,6 +1334,7 @@ static int snd_mixer_oss_notify_handler(struct snd_card *card, int cmd)
"unable to register OSS mixer device %i:%i\n",
card->number, 0);
kfree(mixer);
+ module_put(THIS_MODULE);
return err;
}
mixer->oss_dev_alloc = 1;
@@ -1357,7 +1368,10 @@ static int snd_mixer_oss_notify_handler(struct snd_card *card, int cmd)
if (cmd == SND_MIXER_OSS_NOTIFY_DISCONNECT)
return 0;
snd_mixer_oss_proc_done(mixer);
- return snd_mixer_oss_free1(mixer);
+ err = snd_mixer_oss_free1(mixer);
+ if (!err)
+ module_put(THIS_MODULE);
+ return err;
}
return 0;
}
@@ -1366,12 +1380,12 @@ static int __init alsa_mixer_oss_init(void)
{
struct snd_card *card;
int idx;
-
- snd_mixer_oss_notify_callback = snd_mixer_oss_notify_handler;
+
+ snd_mixer_oss_set_notify(snd_mixer_oss_notify_handler);
for (idx = 0; idx < SNDRV_CARDS; idx++) {
card = snd_card_ref(idx);
if (card) {
- snd_mixer_oss_notify_handler(card, SND_MIXER_OSS_NOTIFY_REGISTER);
+ snd_mixer_oss_notify(card, SND_MIXER_OSS_NOTIFY_REGISTER);
snd_card_unref(card);
}
}
@@ -1380,17 +1394,8 @@ static int __init alsa_mixer_oss_init(void)
static void __exit alsa_mixer_oss_exit(void)
{
- struct snd_card *card;
- int idx;
-
- snd_mixer_oss_notify_callback = NULL;
- for (idx = 0; idx < SNDRV_CARDS; idx++) {
- card = snd_card_ref(idx);
- if (card) {
- snd_mixer_oss_notify_handler(card, SND_MIXER_OSS_NOTIFY_FREE);
- snd_card_unref(card);
- }
- }
+ /* Live adapters hold module references and prevent normal unload. */
+ snd_mixer_oss_set_notify(NULL);
}
module_init(alsa_mixer_oss_init)
diff --git a/sound/core/oss/pcm_oss.c b/sound/core/oss/pcm_oss.c
index bd19328dca9e..e8df27d09b97 100644
--- a/sound/core/oss/pcm_oss.c
+++ b/sound/core/oss/pcm_oss.c
@@ -3151,6 +3151,12 @@ static void register_oss_dsp(struct snd_pcm *pcm, int index)
static int snd_pcm_oss_register_minor(struct snd_pcm *pcm)
{
+ if (pcm->oss.registered)
+ return 0;
+ if (!try_module_get(THIS_MODULE))
+ return -ENODEV;
+ pcm->oss.registered = true;
+
pcm->oss.reg = 0;
if (dsp_map[pcm->card->number] == (int)pcm->device) {
char name[128];
@@ -3205,8 +3211,14 @@ static int snd_pcm_oss_disconnect_minor(struct snd_pcm *pcm)
static int snd_pcm_oss_unregister_minor(struct snd_pcm *pcm)
{
+ /* BUILD-state and creation-error cleanup need not have registered us. */
+ if (!pcm->oss.registered)
+ return 0;
+
snd_pcm_oss_disconnect_minor(pcm);
snd_pcm_oss_proc_done(pcm);
+ pcm->oss.registered = false;
+ module_put(THIS_MODULE);
return 0;
}
diff --git a/sound/core/pcm.c b/sound/core/pcm.c
index 41c2cab7a52c..35ece386d1a2 100644
--- a/sound/core/pcm.c
+++ b/sound/core/pcm.c
@@ -853,8 +853,10 @@ static int snd_pcm_free(struct snd_pcm *pcm)
{
if (!pcm)
return 0;
- if (!pcm->internal)
- pcm_call_notify(pcm, n_unregister);
+ if (!pcm->internal) {
+ scoped_guard(mutex, &register_mutex)
+ pcm_call_notify(pcm, n_unregister);
+ }
if (pcm->private_free)
pcm->private_free(pcm);
snd_pcm_lib_preallocate_free_for_all(pcm);
@@ -957,6 +959,7 @@ int snd_pcm_attach_substream(struct snd_pcm *pcm, int stream,
init_waitqueue_head(&runtime->sleep);
init_waitqueue_head(&runtime->tsleep);
+ snd_refcount_init(&runtime->refcnt);
__snd_pcm_set_state(runtime, SNDRV_PCM_STATE_OPEN);
mutex_init(&runtime->buffer_mutex);
diff --git a/sound/core/pcm_compat.c b/sound/core/pcm_compat.c
index 55ecf87586c4..c54cb07304ad 100644
--- a/sound/core/pcm_compat.c
+++ b/sound/core/pcm_compat.c
@@ -219,8 +219,8 @@ static int snd_pcm_status_user_compat64(struct snd_pcm_substream *substream,
.audio_tstamp_data = status.audio_tstamp_data,
.audio_tstamp_sec = status.audio_tstamp_sec,
.audio_tstamp_nsec = status.audio_tstamp_nsec,
- .driver_tstamp_sec = status.audio_tstamp_sec,
- .driver_tstamp_nsec = status.audio_tstamp_nsec,
+ .driver_tstamp_sec = status.driver_tstamp_sec,
+ .driver_tstamp_nsec = status.driver_tstamp_nsec,
.audio_tstamp_accuracy = status.audio_tstamp_accuracy,
};
@@ -252,7 +252,7 @@ static int snd_pcm_ioctl_hw_params_compat(struct snd_pcm_substream *substream,
return -EFAULT;
if (refine) {
- err = snd_pcm_hw_refine(substream, data);
+ err = snd_pcm_hw_refine_with_lock(substream, data);
if (err < 0)
return err;
err = fixup_unreferenced_params(substream, data);
diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c
index 4d665b4148d7..e8f6a9da4a4c 100644
--- a/sound/core/pcm_lib.c
+++ b/sound/core/pcm_lib.c
@@ -2009,8 +2009,8 @@ static int wait_for_avail(struct snd_pcm_substream *substream,
case SNDRV_PCM_STATE_DRAINING:
if (is_playback)
err = -EPIPE;
- else
- avail = 0; /* indicate draining */
+ else
+ avail = snd_pcm_avail(substream);
goto _endloop;
case SNDRV_PCM_STATE_OPEN:
case SNDRV_PCM_STATE_SETUP:
@@ -2208,13 +2208,17 @@ static int pcm_sanity_check(struct snd_pcm_substream *substream)
return 0;
}
-static int pcm_accessible_state(struct snd_pcm_runtime *runtime)
+static int pcm_accessible_state(struct snd_pcm_substream *substream)
{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
switch (runtime->state) {
case SNDRV_PCM_STATE_PREPARED:
case SNDRV_PCM_STATE_RUNNING:
case SNDRV_PCM_STATE_PAUSED:
return 0;
+ case SNDRV_PCM_STATE_DRAINING:
+ return substream->stream == SNDRV_PCM_STREAM_CAPTURE ? 0 : -EBADFD;
case SNDRV_PCM_STATE_XRUN:
return -EPIPE;
case SNDRV_PCM_STATE_SUSPENDED:
@@ -2320,7 +2324,7 @@ snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream,
nonblock = !!(substream->f_flags & O_NONBLOCK);
snd_pcm_stream_lock_irq(substream);
- err = pcm_accessible_state(runtime);
+ err = pcm_accessible_state(substream);
if (err < 0)
goto _end_unlock;
@@ -2348,7 +2352,7 @@ snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream,
if (!avail) {
if (!is_playback &&
runtime->state == SNDRV_PCM_STATE_DRAINING) {
- snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP);
+ snd_pcm_drain_done(substream);
goto _end_unlock;
}
if (nonblock) {
@@ -2388,7 +2392,7 @@ snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream,
atomic_dec(&runtime->buffer_accessing);
if (err < 0)
goto _end_unlock;
- err = pcm_accessible_state(runtime);
+ err = pcm_accessible_state(substream);
if (err < 0)
goto _end_unlock;
appl_ptr += frames;
diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c
index 6d32c12fb79b..35152fae74ca 100644
--- a/sound/core/pcm_native.c
+++ b/sound/core/pcm_native.c
@@ -580,29 +580,6 @@ int snd_pcm_hw_refine(struct snd_pcm_substream *substream,
}
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)
-{
- int err;
- struct snd_pcm_hw_params *params __free(kfree) =
- memdup_user(_params, sizeof(*params));
-
- if (IS_ERR(params))
- return PTR_ERR(params);
-
- err = snd_pcm_hw_refine(substream, params);
- if (err < 0)
- return err;
-
- err = fixup_unreferenced_params(substream, params);
- if (err < 0)
- return err;
-
- if (copy_to_user(_params, params, sizeof(*params)))
- return -EFAULT;
- return 0;
-}
-
static int period_to_usecs(struct snd_pcm_runtime *runtime)
{
int usecs;
@@ -778,11 +755,48 @@ EXPORT_SYMBOL_GPL(snd_pcm_runtime_buffer_set_silence);
#define is_oss_stream(substream) false
#endif
+static void pcm_qos_req_clear(struct snd_pcm_substream *substream)
+{
+ if (cpu_latency_qos_request_active(&substream->latency_pm_qos_req))
+ cpu_latency_qos_remove_request(&substream->latency_pm_qos_req);
+}
+
+static void pcm_qos_req_update(struct snd_pcm_substream *substream, int usecs)
+{
+ if (usecs < 0) {
+ pcm_qos_req_clear(substream);
+ return;
+ }
+
+ if (cpu_latency_qos_request_active(&substream->latency_pm_qos_req))
+ cpu_latency_qos_update_request(&substream->latency_pm_qos_req,
+ usecs);
+ else
+ cpu_latency_qos_add_request(&substream->latency_pm_qos_req,
+ usecs);
+}
+
+static int hw_params_state_check(struct snd_pcm_substream *substream)
+{
+ guard(pcm_stream_lock_irq)(substream);
+ switch (substream->runtime->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))
+ return -EBADFD;
+ return 0;
+ default:
+ return -EBADFD;
+ }
+}
+
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;
+ int err;
unsigned int bits;
snd_pcm_uframes_t frames;
@@ -792,20 +806,7 @@ static int snd_pcm_hw_params(struct snd_pcm_substream *substream,
err = snd_pcm_buffer_access_lock(runtime);
if (err < 0)
return err;
- scoped_guard(pcm_stream_lock_irq, substream) {
- switch (runtime->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;
- }
- }
+ err = hw_params_state_check(substream);
if (err)
goto unlock;
@@ -889,12 +890,6 @@ static int snd_pcm_hw_params(struct snd_pcm_substream *substream,
snd_pcm_timer_resolution_change(substream);
snd_pcm_set_state(substream, SNDRV_PCM_STATE_SETUP);
- if (cpu_latency_qos_request_active(&substream->latency_pm_qos_req))
- cpu_latency_qos_remove_request(&substream->latency_pm_qos_req);
- usecs = period_to_usecs(runtime);
- if (usecs >= 0)
- cpu_latency_qos_add_request(&substream->latency_pm_qos_req,
- usecs);
err = 0;
_error:
if (err) {
@@ -932,6 +927,57 @@ static int snd_pcm_hw_params_user(struct snd_pcm_substream *substream,
return err;
}
+/*
+ * call snd_pcm_hw_refine() after buffer lock and sanity check;
+ * this one should be used for ioctls and other external paths instead of
+ * direct call of snd_pcm_hw_refine()
+ */
+static int snd_pcm_hw_refine_with_lock(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime;
+ int err;
+
+ if (PCM_RUNTIME_CHECK(substream))
+ return -ENXIO;
+ runtime = substream->runtime;
+ err = snd_pcm_buffer_access_lock(runtime);
+ if (err < 0)
+ return err;
+ err = hw_params_state_check(substream);
+ if (err)
+ goto unlock;
+
+ err = snd_pcm_hw_refine(substream, params);
+
+ unlock:
+ snd_pcm_buffer_access_unlock(runtime);
+ return err;
+}
+
+static int snd_pcm_hw_refine_user(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params __user * _params)
+{
+ int err;
+ struct snd_pcm_hw_params *params __free(kfree) =
+ memdup_user(_params, sizeof(*params));
+
+ if (IS_ERR(params))
+ return PTR_ERR(params);
+
+ err = snd_pcm_hw_refine_with_lock(substream, params);
+ if (err < 0)
+ return err;
+
+ err = fixup_unreferenced_params(substream, params);
+ if (err < 0)
+ return err;
+
+ if (copy_to_user(_params, params, sizeof(*params)))
+ return -EFAULT;
+ return 0;
+}
+
static int do_hw_free(struct snd_pcm_substream *substream)
{
int result = 0;
@@ -971,7 +1017,7 @@ static int snd_pcm_hw_free(struct snd_pcm_substream *substream)
goto unlock;
result = do_hw_free(substream);
snd_pcm_set_state(substream, SNDRV_PCM_STATE_OPEN);
- cpu_latency_qos_remove_request(&substream->latency_pm_qos_req);
+ pcm_qos_req_clear(substream);
unlock:
snd_pcm_buffer_access_unlock(runtime);
return result;
@@ -1191,8 +1237,8 @@ static int snd_pcm_status_user32(struct snd_pcm_substream *substream,
.audio_tstamp_data = status64.audio_tstamp_data,
.audio_tstamp_sec = status64.audio_tstamp_sec,
.audio_tstamp_nsec = status64.audio_tstamp_nsec,
- .driver_tstamp_sec = status64.audio_tstamp_sec,
- .driver_tstamp_nsec = status64.audio_tstamp_nsec,
+ .driver_tstamp_sec = status64.driver_tstamp_sec,
+ .driver_tstamp_nsec = status64.driver_tstamp_nsec,
.audio_tstamp_accuracy = status64.audio_tstamp_accuracy,
};
@@ -2005,6 +2051,7 @@ static int snd_pcm_do_prepare(struct snd_pcm_substream *substream,
err = substream->ops->prepare(substream);
if (err < 0)
return err;
+ pcm_qos_req_update(substream, period_to_usecs(substream->runtime));
return snd_pcm_do_reset(substream, state);
}
@@ -2055,6 +2102,25 @@ static int snd_pcm_prepare(struct snd_pcm_substream *substream,
(snd_pcm_state_t)f_flags);
}
+static int snd_pcm_update_linked_hw_ptr(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_substream *s;
+
+ /* Keep group membership stable while locking each stream separately. */
+ guard(rwsem_read)(&snd_pcm_link_rwsem);
+ snd_pcm_group_for_each_entry(s, substream) {
+ int err;
+
+ guard(pcm_stream_lock_irq)(s);
+ if (!snd_pcm_running(s))
+ continue;
+ err = snd_pcm_update_hw_ptr(s);
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
/*
* drain ioctl
*/
@@ -2164,7 +2230,13 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream,
/* resume pause */
if (runtime->state == SNDRV_PCM_STATE_PAUSED)
snd_pcm_pause(substream, false);
+ snd_pcm_stream_unlock_irq(substream);
+
+ result = snd_pcm_update_linked_hw_ptr(substream);
+ if (result < 0)
+ return result;
+ snd_pcm_stream_lock_irq(substream);
/* pre-start/stop - all running streams are changed to DRAINING state */
result = snd_pcm_action(&snd_pcm_action_drain_init, substream,
ACTION_ARG_IGNORE);
@@ -2179,9 +2251,6 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream,
for (;;) {
long tout;
struct snd_pcm_runtime *to_check;
- unsigned int drain_rate;
- snd_pcm_uframes_t drain_bufsz;
- bool drain_no_period_wakeup;
if (signal_pending(current)) {
result = -ERESTARTSYS;
@@ -2196,45 +2265,35 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream,
runtime = s->runtime;
if (runtime->state == SNDRV_PCM_STATE_DRAINING) {
to_check = runtime;
+ /* pin the draining substream */
+ snd_refcount_get(&to_check->refcnt);
+
+ init_wait_entry(&wait, 0);
+ prepare_to_wait(&to_check->sleep, &wait,
+ TASK_INTERRUPTIBLE);
+ if (to_check->no_period_wakeup) {
+ tout = MAX_SCHEDULE_TIMEOUT;
+ } else {
+ tout = 100;
+ if (to_check->rate) {
+ long t = to_check->buffer_size *
+ 1100 / to_check->rate;
+ tout = max(t, tout);
+ }
+ tout = msecs_to_jiffies(tout);
+ }
break;
}
}
snd_pcm_group_unref(group, substream);
if (!to_check)
break; /* all drained */
- /*
- * Cache the runtime fields needed after unlock.
- * A concurrent close() on the linked stream may free
- * its runtime via snd_pcm_detach_substream() once we
- * release the stream lock below.
- */
- drain_no_period_wakeup = to_check->no_period_wakeup;
- drain_rate = to_check->rate;
- drain_bufsz = to_check->buffer_size;
- init_wait_entry(&wait, 0);
- prepare_to_wait(&to_check->sleep, &wait, TASK_INTERRUPTIBLE);
snd_pcm_stream_unlock_irq(substream);
- if (drain_no_period_wakeup)
- tout = MAX_SCHEDULE_TIMEOUT;
- else {
- tout = 100;
- if (drain_rate) {
- long t = drain_bufsz * 1100 / drain_rate;
- tout = max(t, tout);
- }
- tout = msecs_to_jiffies(tout);
- }
tout = schedule_timeout(tout);
-
snd_pcm_stream_lock_irq(substream);
- group = snd_pcm_stream_group_ref(substream);
- snd_pcm_group_for_each_entry(s, substream) {
- if (s->runtime == to_check) {
- finish_wait(&to_check->sleep, &wait);
- break;
- }
- }
- snd_pcm_group_unref(group, substream);
+ finish_wait(&to_check->sleep, &wait);
+ /* unpin the draining substream again now */
+ snd_refcount_put(&to_check->refcnt);
if (card->shutdown) {
result = -ENODEV;
@@ -2267,23 +2326,26 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream,
static int snd_pcm_drop(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime;
- int result = 0;
+ int result;
if (PCM_RUNTIME_CHECK(substream))
return -ENXIO;
runtime = substream->runtime;
- if (runtime->state == SNDRV_PCM_STATE_OPEN ||
- runtime->state == SNDRV_PCM_STATE_DISCONNECTED)
- return -EBADFD;
+ scoped_guard(pcm_stream_lock_irq, substream) {
+ if (runtime->state == SNDRV_PCM_STATE_OPEN ||
+ runtime->state == SNDRV_PCM_STATE_DISCONNECTED)
+ return -EBADFD;
- guard(pcm_stream_lock_irq)(substream);
- /* resume pause */
- if (runtime->state == SNDRV_PCM_STATE_PAUSED)
- snd_pcm_pause(substream, false);
+ /* resume pause */
+ if (runtime->state == SNDRV_PCM_STATE_PAUSED)
+ snd_pcm_pause(substream, false);
- snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP);
- /* runtime->control->appl_ptr = runtime->status->hw_ptr; */
+ result = snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP);
+ /* runtime->control->appl_ptr = runtime->status->hw_ptr; */
+ }
+
+ snd_pcm_sync_stop(substream, true);
return result;
}
@@ -2776,27 +2838,23 @@ static int snd_pcm_hw_constraints_complete(struct snd_pcm_substream *substream)
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_unlink(substream);
+
snd_pcm_drop(substream);
if (substream->hw_opened) {
+ snd_refcount_sync(&substream->runtime->refcnt);
if (substream->runtime->state != SNDRV_PCM_STATE_OPEN)
do_hw_free(substream);
substream->ops->close(substream);
substream->hw_opened = 0;
}
- if (cpu_latency_qos_request_active(&substream->latency_pm_qos_req))
- cpu_latency_qos_remove_request(&substream->latency_pm_qos_req);
+ pcm_qos_req_clear(substream);
if (substream->pcm_release) {
substream->pcm_release(substream);
substream->pcm_release = NULL;
@@ -2872,8 +2930,6 @@ static int snd_pcm_open_file(struct file *file,
return -ENOMEM;
}
pcm_file->substream = substream;
- if (substream->ref_count == 1)
- substream->pcm_release = pcm_release_private;
file->private_data = pcm_file;
return 0;
@@ -2930,7 +2986,7 @@ static int snd_pcm_open(struct file *file, struct snd_pcm *pcm, int stream)
mutex_lock(&pcm->open_mutex);
while (1) {
err = snd_pcm_open_file(file, pcm, stream);
- if (err >= 0)
+ if (!err)
break;
if (err == -EAGAIN) {
if (file->f_flags & O_NONBLOCK) {
@@ -2999,7 +3055,7 @@ static int do_pcm_hwsync(struct snd_pcm_substream *substream)
switch (substream->runtime->state) {
case SNDRV_PCM_STATE_DRAINING:
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
- return -EBADFD;
+ return 0;
fallthrough;
case SNDRV_PCM_STATE_RUNNING:
return snd_pcm_update_hw_ptr(substream);
@@ -3063,11 +3119,15 @@ static snd_pcm_sframes_t rewind_appl_ptr(struct snd_pcm_substream *substream,
static snd_pcm_sframes_t snd_pcm_rewind(struct snd_pcm_substream *substream,
snd_pcm_uframes_t frames)
{
+ struct snd_pcm_runtime *runtime = substream->runtime;
snd_pcm_sframes_t ret;
if (frames == 0)
return 0;
+ /* Keep the DMA buffer alive through the sync below. */
+ if (!atomic_inc_unless_negative(&runtime->buffer_accessing))
+ return -EBUSY;
scoped_guard(pcm_stream_lock_irq, substream) {
ret = do_pcm_hwsync(substream);
if (!ret)
@@ -3076,17 +3136,22 @@ static snd_pcm_sframes_t snd_pcm_rewind(struct snd_pcm_substream *substream,
}
if (ret >= 0)
snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE);
+ atomic_dec(&runtime->buffer_accessing);
return ret;
}
static snd_pcm_sframes_t snd_pcm_forward(struct snd_pcm_substream *substream,
snd_pcm_uframes_t frames)
{
+ struct snd_pcm_runtime *runtime = substream->runtime;
snd_pcm_sframes_t ret;
if (frames == 0)
return 0;
+ /* Keep the DMA buffer alive through the sync below. */
+ if (!atomic_inc_unless_negative(&runtime->buffer_accessing))
+ return -EBUSY;
scoped_guard(pcm_stream_lock_irq, substream) {
ret = do_pcm_hwsync(substream);
if (!ret)
@@ -3095,20 +3160,26 @@ static snd_pcm_sframes_t snd_pcm_forward(struct snd_pcm_substream *substream,
}
if (ret >= 0)
snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE);
+ atomic_dec(&runtime->buffer_accessing);
return ret;
}
static int snd_pcm_delay(struct snd_pcm_substream *substream,
snd_pcm_sframes_t *delay)
{
+ struct snd_pcm_runtime *runtime = substream->runtime;
int err;
+ /* Keep the DMA buffer alive through the sync below. */
+ if (!atomic_inc_unless_negative(&runtime->buffer_accessing))
+ return -EBUSY;
scoped_guard(pcm_stream_lock_irq, substream) {
err = do_pcm_hwsync(substream);
if (delay && !err)
*delay = snd_pcm_calc_delay(substream);
}
snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_CPU);
+ atomic_dec(&runtime->buffer_accessing);
return err;
}
@@ -4188,7 +4259,7 @@ static int snd_pcm_hw_refine_old_user(struct snd_pcm_substream *substream,
if (IS_ERR(oparams))
return PTR_ERR(oparams);
snd_pcm_hw_convert_from_old_params(params, oparams);
- err = snd_pcm_hw_refine(substream, params);
+ err = snd_pcm_hw_refine_with_lock(substream, params);
if (err < 0)
return err;
diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c
index 2617bb5b4faf..630d9fc8e9da 100644
--- a/sound/core/rawmidi.c
+++ b/sound/core/rawmidi.c
@@ -36,6 +36,13 @@ 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 */
+/* upper bound for draining the output buffer */
+#define SNDRV_RAWMIDI_DRAIN_TIMEOUT (10 * HZ)
+/* interval at which drain progress is re-checked */
+#define SNDRV_RAWMIDI_DRAIN_POLL (HZ / 5)
+/* polls without progress before the drain is considered stalled */
+#define SNDRV_RAWMIDI_DRAIN_STALLS 5
+
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);
@@ -208,14 +215,14 @@ static int snd_rawmidi_runtime_free(struct snd_rawmidi_substream *substream)
static inline void snd_rawmidi_output_trigger(struct snd_rawmidi_substream *substream, int up)
{
- if (!substream->opened)
+ if (!substream->opened || substream->disconnected)
return;
substream->ops->trigger(substream, up);
}
static void snd_rawmidi_input_trigger(struct snd_rawmidi_substream *substream, int up)
{
- if (!substream->opened)
+ if (!substream->opened || substream->disconnected)
return;
substream->ops->trigger(substream, up);
if (!up)
@@ -246,31 +253,74 @@ int snd_rawmidi_drop_output(struct snd_rawmidi_substream *substream)
}
EXPORT_SYMBOL(snd_rawmidi_drop_output);
+static bool output_drained(struct snd_rawmidi_runtime *runtime)
+{
+ return runtime->avail >= runtime->buffer_size;
+}
+
int snd_rawmidi_drain_output(struct snd_rawmidi_substream *substream)
{
- int err = 0;
- long timeout;
struct snd_rawmidi_runtime *runtime;
+ size_t avail, prev_avail;
+ unsigned int stalls = 0;
+ unsigned long deadline;
+ long timeout, wait;
+ bool done;
+ int err = 0;
scoped_guard(spinlock_irq, &substream->lock) {
runtime = substream->runtime;
- if (!substream->opened || !runtime || !runtime->buffer)
+ if (!substream->opened || !runtime || !runtime->buffer ||
+ substream->disconnected)
return -EINVAL;
snd_rawmidi_buffer_ref(runtime);
runtime->drain = 1;
+ prev_avail = runtime->avail;
+ }
+
+ /*
+ * Wait for the device to consume the buffer. Rather than always
+ * sleeping for the whole timeout, sample the free space and stop
+ * early once it has not increased for a second. A substream that
+ * is still making progress is given as long as it needs, within the
+ * same overall limit as before.
+ */
+ deadline = jiffies + SNDRV_RAWMIDI_DRAIN_TIMEOUT;
+ for (;;) {
+ /* signed difference, so this is safe across a jiffies wrap */
+ wait = (long)(deadline - jiffies);
+ if (wait <= 0) {
+ timeout = 0;
+ break;
+ }
+ wait = min_t(long, SNDRV_RAWMIDI_DRAIN_POLL, wait);
+ timeout = wait_event_interruptible_timeout(runtime->sleep,
+ output_drained(runtime), wait);
+ scoped_guard(spinlock_irq, &substream->lock) {
+ avail = runtime->avail;
+ done = output_drained(runtime);
+ }
+ if (done || signal_pending(current))
+ break;
+ if (avail == prev_avail) {
+ if (++stalls >= SNDRV_RAWMIDI_DRAIN_STALLS) {
+ timeout = 0;
+ break;
+ }
+ } else {
+ stalls = 0;
+ prev_avail = avail;
+ }
}
- timeout = wait_event_interruptible_timeout(runtime->sleep,
- (runtime->avail >= runtime->buffer_size),
- 10*HZ);
-
scoped_guard(spinlock_irq, &substream->lock) {
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);
+ dev_warn_ratelimited(substream->rmidi->dev,
+ "rawmidi drain error (avail = %li, buffer_size = %li)\n",
+ (long)runtime->avail,
+ (long)runtime->buffer_size);
err = -EIO;
}
runtime->drain = 0;
@@ -537,7 +587,7 @@ static void close_substream(struct snd_rawmidi *rmidi,
if (--substream->use_count)
return;
- if (cleanup) {
+ if (cleanup && !substream->disconnected) {
if (substream->stream == SNDRV_RAWMIDI_STREAM_INPUT)
snd_rawmidi_input_trigger(substream, 0);
else {
@@ -557,6 +607,9 @@ static void close_substream(struct snd_rawmidi *rmidi,
substream->opened = 0;
substream->append = 0;
}
+ /* Drain input work queued after the earlier cancellation. */
+ if (substream->stream == SNDRV_RAWMIDI_STREAM_INPUT)
+ cancel_work_sync(&substream->runtime->event_work);
substream->ops->close(substream);
if (substream->runtime->private_free)
substream->runtime->private_free(substream);
@@ -1151,7 +1204,7 @@ int snd_rawmidi_receive(struct snd_rawmidi_substream *substream,
struct snd_rawmidi_runtime *runtime;
guard(spinlock_irqsave)(&substream->lock);
- if (!substream->opened)
+ if (!substream->opened || substream->disconnected)
return -EBADFD;
runtime = substream->runtime;
if (!runtime || !runtime->buffer) {
@@ -2063,8 +2116,13 @@ static int snd_rawmidi_dev_disconnect(struct snd_device *device)
struct snd_rawmidi_substream *s;
list_for_each_entry(s, &rmidi->streams[dir].substreams, list) {
- if (s->runtime)
+ scoped_guard(spinlock_irq, &s->lock)
+ s->disconnected = true;
+ if (s->runtime) {
+ s->ops->trigger(s, 0);
+ cancel_work_sync(&s->runtime->event_work);
wake_up(&s->runtime->sleep);
+ }
}
}
diff --git a/sound/core/seq/Kconfig b/sound/core/seq/Kconfig
index e4f58cb985d4..0d606159103e 100644
--- a/sound/core/seq/Kconfig
+++ b/sound/core/seq/Kconfig
@@ -27,14 +27,17 @@ config SND_SEQ_DUMMY
will be called snd-seq-dummy.
config SND_SEQUENCER_OSS
- tristate "OSS Sequencer API"
+ tristate "OSS Sequencer API (deprecated)"
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.
+ The OSS sequencer API is almost nowhere used, hence this option will
+ be dropped soon.
+ Use user-space wrapper (alsa-oss) or CUSE daemon (oss-seq-emu)
+ instead of the kernel-side emulation.
To compile this driver as a module, choose M here: the module
will be called snd-seq-oss.
diff --git a/sound/core/seq/oss/seq_oss.c b/sound/core/seq/oss/seq_oss.c
index 2835576040ed..64a7e62029cc 100644
--- a/sound/core/seq/oss/seq_oss.c
+++ b/sound/core/seq/oss/seq_oss.c
@@ -67,6 +67,9 @@ static int __init alsa_seq_oss_init(void)
{
int rc;
+ pr_warn("seq-oss: kernel OSS sequencer emulation is deprecated\n");
+ pr_warn("seq-oss: Use user-space solution (alsa-oss or oss-seq-emu) instead.\n");
+
rc = register_device();
if (rc < 0)
goto error;
diff --git a/sound/core/seq/oss/seq_oss_device.h b/sound/core/seq/oss/seq_oss_device.h
index 935cf3df0b30..571186db4fa5 100644
--- a/sound/core/seq/oss/seq_oss_device.h
+++ b/sound/core/seq/oss/seq_oss_device.h
@@ -72,6 +72,7 @@ struct seq_oss_devinfo {
int cseq; /* sequencer client number */
int port; /* sequencer port number */
int queue; /* sequencer queue number */
+ bool initialized;
struct snd_seq_addr addr; /* address of this device */
diff --git a/sound/core/seq/oss/seq_oss_event.c b/sound/core/seq/oss/seq_oss_event.c
index 122735862044..cf493f6c983b 100644
--- a/sound/core/seq/oss/seq_oss_event.c
+++ b/sound/core/seq/oss/seq_oss_event.c
@@ -425,6 +425,9 @@ snd_seq_oss_event_input(struct snd_seq_event *ev, int direct, void *private_data
struct seq_oss_devinfo *dp = (struct seq_oss_devinfo *)private_data;
union evrec *rec;
+ if (!dp || !smp_load_acquire(&dp->initialized))
+ return 0; /* ignored */
+
if (ev->type != SNDRV_SEQ_EVENT_ECHO)
return snd_seq_oss_midi_input(ev, direct, private_data);
@@ -433,8 +436,12 @@ snd_seq_oss_event_input(struct snd_seq_event *ev, int direct, void *private_data
rec = (union evrec*)&ev->data;
if (rec->s.code == SEQ_SYNCTIMER) {
+ struct seq_oss_writeq *writeq = READ_ONCE(dp->writeq);
+
+ if (!writeq)
+ return 0;
/* sync echo back */
- snd_seq_oss_writeq_wakeup(dp->writeq, rec->t.time);
+ snd_seq_oss_writeq_wakeup(writeq, rec->t.time);
} else {
/* echo back event */
diff --git a/sound/core/seq/oss/seq_oss_init.c b/sound/core/seq/oss/seq_oss_init.c
index 1aece46c8b06..6474a76a3cce 100644
--- a/sound/core/seq/oss/seq_oss_init.c
+++ b/sound/core/seq/oss/seq_oss_init.c
@@ -258,6 +258,7 @@ snd_seq_oss_open(struct file *file, int level)
client_table[dp->index] = dp;
num_clients++;
+ smp_store_release(&dp->initialized, true);
return 0;
@@ -376,7 +377,7 @@ delete_seq_queue(int queue)
/*
- * free device informations - private_free callback of port
+ * free device information - private_free callback of port
*/
static void
free_devinfo(void *private)
@@ -403,6 +404,7 @@ snd_seq_oss_release(struct seq_oss_devinfo *dp)
client_table[dp->index] = NULL;
num_clients--;
+ smp_store_release(&dp->initialized, false);
snd_seq_oss_reset(dp);
diff --git a/sound/core/seq/oss/seq_oss_midi.c b/sound/core/seq/oss/seq_oss_midi.c
index 70f94df65144..b456bd37d87b 100644
--- a/sound/core/seq/oss/seq_oss_midi.c
+++ b/sound/core/seq/oss/seq_oss_midi.c
@@ -211,6 +211,11 @@ snd_seq_oss_midi_check_exit_port(int client, int port)
mdev = find_slot(client, port);
if (mdev) {
scoped_guard(spinlock_irqsave, &register_lock) {
+ /* Only one callback may release the device. */
+ if (midi_devs[mdev->seq_device] != mdev) {
+ snd_use_lock_free(&mdev->use_lock);
+ return 0;
+ }
midi_devs[mdev->seq_device] = NULL;
}
snd_use_lock_free(&mdev->use_lock);
diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c
index 239809ce48d7..0711a9e23b5f 100644
--- a/sound/core/seq/seq_clientmgr.c
+++ b/sound/core/seq/seq_clientmgr.c
@@ -95,23 +95,18 @@ 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; call under RCU read-lock */
-static struct snd_seq_client *__clientptr(int clientid)
+/* return pointer to client structure for specified id;
+ * the caller must guarantee the client's lifetime by itself, as neither RCU
+ * nor a use_lock reference is taken here.
+ */
+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 rcu_dereference_check(clienttab[clientid],
- lockdep_is_held(&clients_lock));
-}
-
-/* return pointer to client structure for specified id */
-static struct snd_seq_client *clientptr(int clientid)
-{
- guard(rcu)();
- return __clientptr(clientid);
+ return rcu_dereference_protected(clienttab[clientid], true);
}
static struct snd_seq_client *client_use_ptr(int clientid, bool load_module)
@@ -124,7 +119,7 @@ static struct snd_seq_client *client_use_ptr(int clientid, bool load_module)
return NULL;
}
scoped_guard(rcu) {
- client = __clientptr(clientid);
+ client = rcu_dereference(clienttab[clientid]);
if (client)
return snd_seq_client_ref(client);
if (clienttablock[clientid])
@@ -159,7 +154,7 @@ static struct snd_seq_client *client_use_ptr(int clientid, bool load_module)
}
}
scoped_guard(rcu) {
- client = __clientptr(clientid);
+ client = rcu_dereference(clienttab[clientid]);
if (client)
return snd_seq_client_ref(client);
}
@@ -485,11 +480,11 @@ static ssize_t snd_seq_read(struct file *file, char __user *buf, size_t count,
if (err < 0) {
if (cell)
snd_seq_fifo_cell_putback(fifo, cell);
- if (err == -EAGAIN && result > 0)
- err = 0;
}
- return (err < 0) ? err : result;
+ if (result > 0)
+ return result;
+ return err < 0 ? err : 0;
}
diff --git a/sound/core/seq/seq_compat.c b/sound/core/seq/seq_compat.c
index 80110501da6f..57aaa39afb9d 100644
--- a/sound/core/seq/seq_compat.c
+++ b/sound/core/seq/seq_compat.c
@@ -25,7 +25,9 @@ struct snd_seq_port_info32 {
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 */
+ unsigned char direction; /* port usage direction (r/w/bidir) */
+ unsigned char ump_group; /* 0 = UMP EP (no conversion), 1-16 = UMP group number */
+ char reserved[57]; /* for future use */
};
static int snd_seq_call_port_info_ioctl(struct snd_seq_client *client, unsigned int cmd,
@@ -40,7 +42,9 @@ static int snd_seq_call_port_info_ioctl(struct snd_seq_client *client, unsigned
if (copy_from_user(data, data32, sizeof(*data32)) ||
get_user(data->flags, &data32->flags) ||
- get_user(data->time_queue, &data32->time_queue))
+ get_user(data->time_queue, &data32->time_queue) ||
+ get_user(data->direction, &data32->direction) ||
+ get_user(data->ump_group, &data32->ump_group))
return -EFAULT;
data->kernel = NULL;
@@ -50,9 +54,12 @@ static int snd_seq_call_port_info_ioctl(struct snd_seq_client *client, unsigned
if (err < 0)
return err;
- if (copy_to_user(data32, data, sizeof(*data32)) ||
+ if (copy_to_user(data32, data,
+ offsetof(struct snd_seq_port_info32, kernel)) ||
put_user(data->flags, &data32->flags) ||
- put_user(data->time_queue, &data32->time_queue))
+ put_user(data->time_queue, &data32->time_queue) ||
+ put_user(data->direction, &data32->direction) ||
+ put_user(data->ump_group, &data32->ump_group))
return -EFAULT;
return err;
diff --git a/sound/core/seq/seq_ports.c b/sound/core/seq/seq_ports.c
index eb67eb0eeb14..b582d034437f 100644
--- a/sound/core/seq/seq_ports.c
+++ b/sound/core/seq/seq_ports.c
@@ -248,17 +248,9 @@ static void clear_subscriber_list(struct snd_seq_client *client,
}
/* delete port data */
-static int port_delete(struct snd_seq_client *client,
- struct snd_seq_client_port *port)
+static void port_free(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;
- /* the port has already been unlinked from the client's port list;
- * wait for a grace period so that RCU readers still traversing the
- * list can no longer take a new use_lock reference, then drain the
- * outstanding references before freeing
- */
- synchronize_rcu();
snd_use_lock_sync(&port->use_lock);
/* clear subscribers info */
@@ -272,6 +264,20 @@ static int port_delete(struct snd_seq_client *client,
snd_BUG_ON(port->c_dest.count != 0);
kfree(port);
+}
+
+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;
+ /* the port has already been unlinked from the client's port list;
+ * wait for a grace period so that RCU readers still traversing the
+ * list can no longer take a new use_lock reference, then drain the
+ * outstanding references before freeing
+ */
+ synchronize_rcu();
+ port_free(client, port);
return 0;
}
@@ -302,18 +308,25 @@ int snd_seq_delete_port(struct snd_seq_client *client, int port)
int snd_seq_delete_all_ports(struct snd_seq_client *client)
{
struct snd_seq_client_port *port, *tmp;
+ LIST_HEAD(deleted);
- /* unlink and delete each port; port_delete() waits for an RCU grace
- * period before draining the port, so concurrent lockless readers can
- * no longer take a new use_lock reference on it
+ /* unlink all ports first and wait for a single RCU grace period
+ * before draining and freeing them, so concurrent lockless readers
+ * can no longer take a new use_lock reference on any of them
*/
guard(mutex)(&client->ports_mutex);
list_for_each_entry_safe(port, tmp, &client->ports_list_head, list) {
list_del_rcu(&port->list);
client->num_ports--;
+ port->closing = 1;
+ list_add_tail(&port->delete_list, &deleted);
snd_seq_system_client_ev_port_exit(port->addr.client, port->addr.port);
- port_delete(client, port);
}
+ if (list_empty(&deleted))
+ return 0;
+ synchronize_rcu();
+ list_for_each_entry_safe(port, tmp, &deleted, delete_list)
+ port_free(client, port);
return 0;
}
diff --git a/sound/core/seq/seq_ports.h b/sound/core/seq/seq_ports.h
index 12ad86bf1489..ef1787d6c2dd 100644
--- a/sound/core/seq/seq_ports.h
+++ b/sound/core/seq/seq_ports.h
@@ -49,6 +49,7 @@ struct snd_seq_client_port {
struct module *owner; /* owner of this port */
char name[64]; /* port name */
struct list_head list; /* port list */
+ struct list_head delete_list; /* used by snd_seq_delete_all_ports() */
snd_use_lock_t use_lock;
/* subscribers */
diff --git a/sound/core/timer.c b/sound/core/timer.c
index 679b26435670..905176bf6948 100644
--- a/sound/core/timer.c
+++ b/sound/core/timer.c
@@ -2173,9 +2173,11 @@ static long snd_utimer_ioctl(struct file *file, unsigned int ioctl, unsigned lon
}
static const struct file_operations snd_utimer_fops = {
+ .owner = THIS_MODULE,
.llseek = noop_llseek,
.release = snd_utimer_release,
.unlocked_ioctl = snd_utimer_ioctl,
+ .compat_ioctl = snd_utimer_ioctl,
};
static int snd_utimer_start(struct snd_timer *t)
diff --git a/sound/core/timer_compat.c b/sound/core/timer_compat.c
index 25ee81c1668b..5bab00466de7 100644
--- a/sound/core/timer_compat.c
+++ b/sound/core/timer_compat.c
@@ -99,6 +99,7 @@ static long __snd_timer_user_ioctl_compat(struct file *file, unsigned int cmd,
case SNDRV_TIMER_IOCTL_PAUSE:
case SNDRV_TIMER_IOCTL_PAUSE_OLD:
case SNDRV_TIMER_IOCTL_NEXT_DEVICE:
+ case SNDRV_TIMER_IOCTL_CREATE:
return __snd_timer_user_ioctl(file, cmd, (unsigned long)argp, true);
case SNDRV_TIMER_IOCTL_GPARAMS32:
return snd_timer_user_gparams_compat(file, argp);
diff --git a/sound/core/ump.c b/sound/core/ump.c
index 3d1a2ed3b476..f6482a9bfe61 100644
--- a/sound/core/ump.c
+++ b/sound/core/ump.c
@@ -66,7 +66,7 @@ static void safe_copy_string(void *dst, size_t max_dst_size,
if (!max_dst_size--)
return;
- for (s = src; max_dst_size && *s && max_src_size--; s++) {
+ for (s = src; max_dst_size && max_src_size-- && *s; s++) {
if (!isascii(*s) || !isprint(*s))
continue;
*d++ = *s;
diff --git a/sound/core/ump_convert.c b/sound/core/ump_convert.c
index 85cc67de6330..0a7972d91ec5 100644
--- a/sound/core/ump_convert.c
+++ b/sound/core/ump_convert.c
@@ -323,7 +323,7 @@ static int fill_rpn(struct ump_cvt_to_ump_bank *cc,
cc->cc_data_lsb);
reset_rpn(cc);
- return 1;
+ return 8;
}
/* convert to a MIDI 1.0 Channel Voice message */
diff --git a/sound/core/vmaster.c b/sound/core/vmaster.c
index cd3dcaaf9f72..f43144ba2797 100644
--- a/sound/core/vmaster.c
+++ b/sound/core/vmaster.c
@@ -287,6 +287,8 @@ EXPORT_SYMBOL(_snd_ctl_add_follower);
*
* Adds the multiple follower kcontrols with the given names.
* Returns 0 for success or a negative error code.
+ *
+ * Note that this function takes card->controls_rwsem lock internally.
*/
int snd_ctl_add_followers(struct snd_card *card, struct snd_kcontrol *master,
const char * const *list)
@@ -294,6 +296,7 @@ int snd_ctl_add_followers(struct snd_card *card, struct snd_kcontrol *master,
struct snd_kcontrol *follower;
int err;
+ guard(rwsem_write)(&card->controls_rwsem);
for (; *list; list++) {
follower = snd_ctl_find_id_mixer(card, *list);
if (follower) {
@@ -463,6 +466,7 @@ EXPORT_SYMBOL(snd_ctl_make_virtual_master);
/**
* snd_ctl_add_vmaster_hook - Add a hook to a vmaster control
+ * @card: the card instance
* @kcontrol: vmaster kctl element
* @hook: the hook function
* @private_data: the private_data pointer to be saved
@@ -470,13 +474,17 @@ EXPORT_SYMBOL(snd_ctl_make_virtual_master);
* Adds the given hook to the vmaster control element so that it's called
* at each time when the value is changed.
*
+ * Takes the card's controls_rwsem lock internally.
+ *
* Return: Zero.
*/
-int snd_ctl_add_vmaster_hook(struct snd_kcontrol *kcontrol,
+int snd_ctl_add_vmaster_hook(struct snd_card *card, struct snd_kcontrol *kcontrol,
void (*hook)(void *private_data, int),
void *private_data)
{
struct link_master *master = snd_kcontrol_chip(kcontrol);
+
+ guard(rwsem_write)(&card->controls_rwsem);
master->hook = hook;
master->hook_private_data = private_data;
return 0;
diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c
index 81cb1f59f703..0aaa64b189da 100644
--- a/sound/drivers/aloop.c
+++ b/sound/drivers/aloop.c
@@ -1073,6 +1073,18 @@ static unsigned int get_cable_index(struct snd_pcm_substream *substream)
return !substream->stream;
}
+/*
+ * In notify mode the playback side may change format, rate, channels and,
+ * with a sound timer, the period size. A running capture that no longer
+ * matches is stopped by loopback_check_format().
+ * call in loopback->cable_lock
+ */
+static bool loopback_playback_unconstrained(struct loopback_pcm *dpcm)
+{
+ return dpcm->substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
+ get_notify(dpcm);
+}
+
static int rule_format(struct snd_pcm_hw_params *params,
struct snd_pcm_hw_rule *rule)
{
@@ -1082,6 +1094,8 @@ static int rule_format(struct snd_pcm_hw_params *params,
snd_mask_none(&m);
scoped_guard(mutex, &dpcm->loopback->cable_lock) {
+ if (loopback_playback_unconstrained(dpcm))
+ return 0;
m.bits[0] = (u_int32_t)cable->hw.formats;
m.bits[1] = (u_int32_t)(cable->hw.formats >> 32);
}
@@ -1096,6 +1110,8 @@ static int rule_rate(struct snd_pcm_hw_params *params,
struct snd_interval t;
scoped_guard(mutex, &dpcm->loopback->cable_lock) {
+ if (loopback_playback_unconstrained(dpcm))
+ return 0;
t.min = cable->hw.rate_min;
t.max = cable->hw.rate_max;
}
@@ -1112,6 +1128,8 @@ static int rule_channels(struct snd_pcm_hw_params *params,
struct snd_interval t;
scoped_guard(mutex, &dpcm->loopback->cable_lock) {
+ if (loopback_playback_unconstrained(dpcm))
+ return 0;
t.min = cable->hw.channels_min;
t.max = cable->hw.channels_max;
}
@@ -1128,6 +1146,8 @@ static int rule_period_bytes(struct snd_pcm_hw_params *params,
struct snd_interval t;
scoped_guard(mutex, &dpcm->loopback->cable_lock) {
+ if (loopback_playback_unconstrained(dpcm))
+ return 0;
t.min = cable->hw.period_bytes_min;
t.max = cable->hw.period_bytes_max;
}
@@ -1355,7 +1375,7 @@ static const struct loopback_ops loopback_snd_timer_ops = {
.dpcm_info = loopback_snd_timer_dpcm_info,
};
-static int loopback_open(struct snd_pcm_substream *substream)
+static int __loopback_open(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct loopback *loopback = substream->private_data;
@@ -1364,7 +1384,6 @@ static int loopback_open(struct snd_pcm_substream *substream)
int err = 0;
int dev = get_cable_index(substream);
- guard(mutex)(&loopback->cable_lock);
dpcm = kzalloc_obj(*dpcm);
if (!dpcm)
return -ENOMEM;
@@ -1459,6 +1478,14 @@ static int loopback_open(struct snd_pcm_substream *substream)
return err;
}
+static int loopback_open(struct snd_pcm_substream *substream)
+{
+ struct loopback *loopback = substream->private_data;
+
+ guard(mutex)(&loopback->cable_lock);
+ return __loopback_open(substream);
+}
+
static int loopback_close(struct snd_pcm_substream *substream)
{
struct loopback *loopback = substream->private_data;
diff --git a/sound/drivers/dummy.c b/sound/drivers/dummy.c
index 3f6bfee29986..6b0a06e5c876 100644
--- a/sound/drivers/dummy.c
+++ b/sound/drivers/dummy.c
@@ -1063,11 +1063,13 @@ static int snd_dummy_probe(struct platform_device *devptr)
break;
}
}
+
+ if (pcm_substreams[dev] < 1)
+ pcm_substreams[dev] = 1;
+ if (pcm_substreams[dev] > MAX_PCM_SUBSTREAMS)
+ pcm_substreams[dev] = MAX_PCM_SUBSTREAMS;
+
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)
return err;
diff --git a/sound/drivers/mpu401/mpu401_uart.c b/sound/drivers/mpu401/mpu401_uart.c
index 6de03d72a908..fcf2b686f9f5 100644
--- a/sound/drivers/mpu401/mpu401_uart.c
+++ b/sound/drivers/mpu401/mpu401_uart.c
@@ -155,20 +155,20 @@ static void snd_mpu401_uart_timer(struct timer_list *t)
scoped_guard(spinlock_irqsave, &mpu->timer_lock) {
/*mpu->mode |= MPU401_MODE_TIMER;*/
- mod_timer(&mpu->timer, 1 + jiffies);
+ if (mpu->timer_invoked)
+ mod_timer(&mpu->timer, 1 + jiffies);
}
if (mpu->rmidi)
_snd_mpu401_uart_interrupt(mpu);
}
/*
- * initialize the timer callback if not programmed yet
+ * arm the timer if it is not already active
*/
static void snd_mpu401_uart_add_timer (struct snd_mpu401 *mpu, int input)
{
guard(spinlock_irqsave)(&mpu->timer_lock);
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 :
@@ -474,6 +474,7 @@ 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);
+ timer_shutdown_sync(&mpu->timer);
release_and_free_resource(mpu->res);
kfree(mpu);
}
@@ -528,6 +529,7 @@ int snd_mpu401_uart_new(struct snd_card *card, int device,
spin_lock_init(&mpu->input_lock);
spin_lock_init(&mpu->output_lock);
spin_lock_init(&mpu->timer_lock);
+ timer_setup(&mpu->timer, snd_mpu401_uart_timer, 0);
mpu->hardware = hardware;
mpu->irq = -1;
mpu->rmidi = rmidi;
diff --git a/sound/drivers/pcmtest.c b/sound/drivers/pcmtest.c
index 5d5281e4deb7..393f132a438b 100644
--- a/sound/drivers/pcmtest.c
+++ b/sound/drivers/pcmtest.c
@@ -280,39 +280,35 @@ static void fill_block_pattern(struct pcmtst_buf_iter *v_iter, struct snd_pcm_ru
fill_block_pattern_n(v_iter, runtime);
}
+static void fill_random_block(char *buf, size_t buf_size, size_t pos,
+ size_t count)
+{
+ while (count) {
+ size_t chunk = min(count, buf_size - pos);
+
+ get_random_bytes(buf + pos, chunk);
+ count -= chunk;
+ pos = 0;
+ }
+}
+
static void fill_block_rand_n(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime)
{
unsigned int channels = runtime->channels;
- // Remaining space in all channel buffers
- size_t bytes_remain = runtime->dma_bytes - v_iter->buf_pos;
+ size_t pos = v_iter->buf_pos / channels;
unsigned int i;
- for (i = 0; i < channels; i++) {
- if (v_iter->b_rw <= bytes_remain) {
- //b_rw - count of bytes must be written for all channels at each timer tick
- get_random_bytes(runtime->dma_area + buf_pos_n(v_iter, channels, i),
- v_iter->b_rw / channels);
- } else {
- // Write to the end of buffer and start from the beginning of it
- get_random_bytes(runtime->dma_area + buf_pos_n(v_iter, channels, i),
- bytes_remain / channels);
- get_random_bytes(runtime->dma_area + v_iter->chan_block * i,
- (v_iter->b_rw - bytes_remain) / channels);
- }
- }
+ for (i = 0; i < channels; i++)
+ fill_random_block(runtime->dma_area + v_iter->chan_block * i,
+ v_iter->chan_block, pos,
+ v_iter->b_rw / channels);
inc_buf_pos(v_iter, v_iter->b_rw, runtime->dma_bytes);
}
static void fill_block_rand_i(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime)
{
- size_t in_cur_block = runtime->dma_bytes - v_iter->buf_pos;
-
- if (v_iter->b_rw <= in_cur_block) {
- get_random_bytes(&runtime->dma_area[v_iter->buf_pos], v_iter->b_rw);
- } else {
- get_random_bytes(&runtime->dma_area[v_iter->buf_pos], in_cur_block);
- get_random_bytes(runtime->dma_area, v_iter->b_rw - in_cur_block);
- }
+ fill_random_block(runtime->dma_area, runtime->dma_bytes,
+ v_iter->buf_pos, v_iter->b_rw);
inc_buf_pos(v_iter, v_iter->b_rw, runtime->dma_bytes);
}
@@ -349,6 +345,7 @@ static void timer_timeout(struct timer_list *data)
v_iter = timer_container_of(v_iter, data, timer_instance);
substream = v_iter->substream;
+ guard(pcm_stream_lock_irqsave)(substream);
if (v_iter->suspend)
return;
@@ -362,7 +359,7 @@ static void timer_timeout(struct timer_list *data)
v_iter->period_pos += v_iter->b_rw;
if (v_iter->period_pos >= v_iter->period_bytes) {
v_iter->period_pos %= v_iter->period_bytes;
- snd_pcm_period_elapsed(substream);
+ snd_pcm_period_elapsed_under_stream_lock(substream);
}
if (!v_iter->suspend)
@@ -449,24 +446,6 @@ static snd_pcm_uframes_t snd_pcmtst_pcm_pointer(struct snd_pcm_substream *substr
return bytes_to_frames(substream->runtime, v_iter->buf_pos);
}
-static int snd_pcmtst_free(struct pcmtst *pcmtst)
-{
- if (!pcmtst)
- return 0;
- kfree(pcmtst);
- return 0;
-}
-
-// These callbacks are required, but empty - all freeing occurs in pdev_remove
-static int snd_pcmtst_dev_free(struct snd_device *device)
-{
- return 0;
-}
-
-static void pcmtst_pdev_release(struct device *dev)
-{
-}
-
static int snd_pcmtst_pcm_prepare(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
@@ -566,40 +545,9 @@ static int snd_pcmtst_new_pcm(struct pcmtst *pcmtst)
return err;
}
-static int snd_pcmtst_create(struct snd_card *card, struct platform_device *pdev,
- struct pcmtst **r_pcmtst)
-{
- struct pcmtst *pcmtst;
- int err;
- static const struct snd_device_ops ops = {
- .dev_free = snd_pcmtst_dev_free,
- };
-
- pcmtst = kzalloc_obj(*pcmtst);
- if (!pcmtst)
- return -ENOMEM;
- pcmtst->card = card;
- pcmtst->pdev = pdev;
-
- err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, pcmtst, &ops);
- if (err < 0)
- goto _err_free_chip;
-
- err = snd_pcmtst_new_pcm(pcmtst);
- if (err < 0)
- goto _err_free_chip;
-
- *r_pcmtst = pcmtst;
- return 0;
-
-_err_free_chip:
- snd_pcmtst_free(pcmtst);
- return err;
-}
-
static int pcmtst_probe(struct platform_device *pdev)
{
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
struct pcmtst *pcmtst;
int err;
@@ -607,10 +555,16 @@ static int pcmtst_probe(struct platform_device *pdev)
if (err)
return err;
- err = snd_devm_card_new(&pdev->dev, index, id, THIS_MODULE, 0, &card);
+ err = snd_devm_card_new(&pdev->dev, index, id, THIS_MODULE,
+ sizeof(*pcmtst), &card);
if (err < 0)
return err;
- err = snd_pcmtst_create(card, pdev, &pcmtst);
+
+ pcmtst = card->private_data;
+ pcmtst->card = card;
+ pcmtst->pdev = pdev;
+
+ err = snd_pcmtst_new_pcm(pcmtst);
if (err < 0)
return err;
@@ -623,27 +577,19 @@ static int pcmtst_probe(struct platform_device *pdev)
return err;
platform_set_drvdata(pdev, pcmtst);
+ card = NULL; /* probe succeeded, don't release as error */
return 0;
}
-static void pdev_remove(struct platform_device *pdev)
-{
- struct pcmtst *pcmtst = platform_get_drvdata(pdev);
-
- snd_pcmtst_free(pcmtst);
-}
+#define SND_PCMTEST_DRIVER "pcmtest"
-static struct platform_device pcmtst_pdev = {
- .name = "pcmtest",
- .dev.release = pcmtst_pdev_release,
-};
+static struct platform_device *pcmtst_pdev;
static struct platform_driver pcmtst_pdrv = {
.probe = pcmtst_probe,
- .remove = pdev_remove,
.driver = {
- .name = "pcmtest",
+ .name = SND_PCMTEST_DRIVER,
},
};
@@ -755,12 +701,14 @@ static int __init mod_init(void)
err = init_debug_files(buf_allocated);
if (err)
goto err_free_patterns;
- err = platform_device_register(&pcmtst_pdev);
- if (err)
+ pcmtst_pdev = platform_device_register_simple(SND_PCMTEST_DRIVER, -1, NULL, 0);
+ if (IS_ERR(pcmtst_pdev)) {
+ err = PTR_ERR(pcmtst_pdev);
goto err_clear_debug;
+ }
err = platform_driver_register(&pcmtst_pdrv);
if (err) {
- platform_device_unregister(&pcmtst_pdev);
+ platform_device_unregister(pcmtst_pdev);
goto err_clear_debug;
}
@@ -779,7 +727,7 @@ static void __exit mod_exit(void)
free_pattern_buffers();
platform_driver_unregister(&pcmtst_pdrv);
- platform_device_unregister(&pcmtst_pdev);
+ platform_device_unregister(pcmtst_pdev);
}
MODULE_DESCRIPTION("Virtual ALSA driver for PCM testing/fuzzing");
diff --git a/sound/drivers/serial-generic.c b/sound/drivers/serial-generic.c
index 766206c6ca75..8ba5e35af4a1 100644
--- a/sound/drivers/serial-generic.c
+++ b/sound/drivers/serial-generic.c
@@ -183,10 +183,7 @@ static void snd_serial_generic_input_trigger(struct snd_rawmidi_substream *subst
{
struct snd_serial_generic *drvdata = substream->rmidi->card->private_data;
- if (up)
- set_bit(SERIAL_MODE_INPUT_TRIGGERED, &drvdata->filemode);
- else
- clear_bit(SERIAL_MODE_INPUT_TRIGGERED, &drvdata->filemode);
+ assign_bit(SERIAL_MODE_INPUT_TRIGGERED, &drvdata->filemode, up);
}
static int snd_serial_generic_output_open(struct snd_rawmidi_substream *substream)
@@ -230,10 +227,7 @@ static void snd_serial_generic_output_trigger(struct snd_rawmidi_substream *subs
{
struct snd_serial_generic *drvdata = substream->rmidi->card->private_data;
- if (up)
- set_bit(SERIAL_MODE_OUTPUT_TRIGGERED, &drvdata->filemode);
- else
- clear_bit(SERIAL_MODE_OUTPUT_TRIGGERED, &drvdata->filemode);
+ assign_bit(SERIAL_MODE_OUTPUT_TRIGGERED, &drvdata->filemode, up);
if (up)
snd_serial_generic_tx_wakeup(drvdata);
diff --git a/sound/firewire/bebob/bebob_hwdep.c b/sound/firewire/bebob/bebob_hwdep.c
index 216d1fceb6e7..1c523525fad2 100644
--- a/sound/firewire/bebob/bebob_hwdep.c
+++ b/sound/firewire/bebob/bebob_hwdep.c
@@ -8,7 +8,7 @@
/*
* This codes give three functionality.
*
- * 1.get firewire node infomation
+ * 1.get firewire node information
* 2.get notification about starting/stopping stream
* 3.lock/unlock stream
*/
diff --git a/sound/hda/codecs/ca0132.c b/sound/hda/codecs/ca0132.c
index 0f8fb62f021d..896091c1144f 100644
--- a/sound/hda/codecs/ca0132.c
+++ b/sound/hda/codecs/ca0132.c
@@ -18,6 +18,7 @@
#include <linux/types.h>
#include <linux/io.h>
#include <linux/pci.h>
+#include <linux/dma-mapping.h>
#include <asm/io.h>
#include <sound/core.h>
#include <sound/hda_codec.h>
@@ -1060,6 +1061,8 @@ enum dsp_download_state {
* CA0132 specific
*/
+#define AE5_STRIP_MAX_LEDS 100
+
struct ca0132_spec {
struct hda_gen_spec gen;
@@ -1162,6 +1165,13 @@ struct ca0132_spec {
* Renames PlayEnhancement and CrystalVoice too.
*/
bool use_alt_controls;
+
+#if IS_ENABLED(CONFIG_SND_HDA_DSP_LOADER)
+ /* AE-5 WS2812 strip control */
+ struct mutex ae5_strip_mutex;
+ u32 ae5_strip_cur_colors[AE5_STRIP_MAX_LEDS];
+ int ae5_strip_cur_num_leds;
+#endif
};
/*
@@ -6846,6 +6856,15 @@ static int ca0132_alt_add_mic_boost_enum(struct hda_codec *codec)
}
+#if IS_ENABLED(CONFIG_SND_HDA_DSP_LOADER)
+static int ae5_add_strip_controls(struct hda_codec *codec);
+#else
+static inline int ae5_add_strip_controls(struct hda_codec *codec)
+{
+ return 0;
+}
+#endif
+
/*
* Add headphone gain enumerated control for the AE-5. This switches between
* three modes, low, medium, and high. When non-headphone outputs are selected,
@@ -7149,6 +7168,10 @@ static int ca0132_build_controls(struct hda_codec *codec)
switch (ca0132_quirk(spec)) {
case QUIRK_AE5:
+ err = ae5_add_strip_controls(codec);
+ if (err < 0)
+ return err;
+ fallthrough;
case QUIRK_AE7:
err = ae5_add_headphone_gain_enum(codec);
if (err < 0)
@@ -8316,8 +8339,367 @@ static void sbz_setup_defaults(struct hda_codec *codec)
}
/*
- * Setup default parameters for the Sound BlasterX AE-5 DSP.
+ * Sound BlasterX AE-5 External WS2812B RGB LED Strip Transport
+ */
+#if IS_ENABLED(CONFIG_SND_HDA_DSP_LOADER)
+#define AE5_STRIP_DMA_SIZE 0x8000
+#define AE5_STRIP_TOTAL_WORDS (AE5_STRIP_DMA_SIZE / sizeof(u32))
+#define AE5_STRIP_PREAMBLE_WORDS 5
+#define AE5_STRIP_WORDS_PER_LED 4
+#define AE5_STRIP_RESET_GAP_WORDS 83
+
+#define CA0113_SERIALIZER_BASE(spec, ch) ((spec)->mem_base + 0x42c + (ch) * 0x40)
+#define CA0113_SERIALIZER_CLK(spec, ch) ((spec)->mem_base + 0x43c + (ch) * 0x40)
+#define CA0113_SERIALIZER_OUT(spec, ch) ((spec)->mem_base + 0x454 + (ch) * 0x40)
+
+static void ae5_strip_encode_24(u32 u, u32 *words)
+{
+ /*
+ * 24-bit pixel -> 4 x 32-bit audio words (6 bits per word).
+ * Hardware audio serializer transmits MSB first (bits 31 down to 0).
+ * For word i (i8 = 23, 17, 11, 5), bit_idx 0..5 (MSB to LSB within the word)
+ * is placed at n = 29 - bit_idx * 4:
+ * Bit 0 -> 0b100 << n (375ns high, 1042ns low): BIT(n + 2)
+ * Bit 1 -> 0b111 << n (1083ns high, 333ns low): BIT(n + 2) | BIT(n + 1) | BIT(n)
+ */
+ int i8 = 23;
+ int i, bit_idx;
+
+ for (i = 0; i < 4; i++) {
+ u32 out = 0;
+
+ for (bit_idx = 0; bit_idx < 6; bit_idx++) {
+ int bit = (u >> (i8 - bit_idx)) & 1;
+ int n = 29 - bit_idx * 4;
+
+ out |= (bit ? (BIT(2) | BIT(1) | BIT(0)) : BIT(2)) << n;
+ }
+ words[i] = out;
+ i8 -= 6;
+ }
+}
+
+static struct hdac_stream *ae5_strip_find_stream(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ struct hdac_bus *bus = &codec->bus->core;
+ struct hdac_stream *s, *dsp = NULL, *first = NULL;
+
+ guard(spinlock_irqsave)(&bus->reg_lock);
+ list_for_each_entry(s, &bus->stream_list, list) {
+ if (s->direction != SNDRV_PCM_STREAM_PLAYBACK)
+ continue;
+ if (s->opened || s->running || s->locked)
+ continue;
+ if (!first)
+ first = s;
+ /* The DSP DMA consumes the DSP-loader stream (tag in dsp_stream_id). */
+ if (spec->dsp_stream_id && s->stream_tag == spec->dsp_stream_id)
+ dsp = s;
+ }
+
+ s = dsp ? dsp : first;
+ if (s)
+ s->opened = 1;
+
+ return s;
+}
+
+/*
+ * Caller must hold spec->ae5_strip_mutex.
*/
+static int ae5_strip_send_frame(struct hda_codec *codec, const u32 *grb_colors, int num_leds)
+{
+ struct ca0132_spec *spec = codec->spec;
+ struct hdac_bus *bus = &codec->bus->core;
+ struct snd_dma_buffer dmab;
+ struct hdac_stream *hstr = NULL;
+ unsigned int format, stream_tag;
+ u32 enc_words[4];
+ __le32 *dst;
+ int word_idx = 0;
+ int frame_words;
+ int led, w, i;
+ int retries = 20; /* ~100ms max */
+
+ if (!spec || !spec->mem_base)
+ return -ENODEV;
+ if (num_leds <= 0 || num_leds > AE5_STRIP_MAX_LEDS)
+ return -EINVAL;
+
+ lockdep_assert_held(&spec->ae5_strip_mutex);
+
+ CLASS(snd_hda_power_pm, pm)(codec);
+ if (pm.err < 0)
+ return pm.err;
+
+ /*
+ * 44.1kHz, 24-bit, 2-channel format (0x4031) matches Windows CtxHda
+ * HDAUDIO_STREAM_FORMAT.
+ */
+ format = snd_hdac_stream_format(2, 24, 44100);
+
+ for (;;) {
+ hstr = ae5_strip_find_stream(codec);
+ if (!hstr) {
+ if (--retries <= 0) {
+ codec_warn(codec, "AE5 strip: no free azx stream after retries\n");
+ return -EBUSY;
+ }
+ usleep_range(5000, 10000);
+ continue;
+ }
+
+ stream_tag = snd_hdac_dsp_prepare(hstr, format, AE5_STRIP_DMA_SIZE, &dmab);
+ if ((int)stream_tag > 0)
+ break;
+
+ /* Prepare failed (e.g. -EBUSY due to race with playback starting); retry */
+ scoped_guard(spinlock_irq, &bus->reg_lock)
+ hstr->opened = 0;
+
+ if (--retries <= 0) {
+ codec_warn(codec, "AE5 strip: snd_hdac_dsp_prepare failed %d\n",
+ (int)stream_tag);
+ return (int)stream_tag;
+ }
+ usleep_range(5000, 10000);
+ }
+
+ /* 1. Populate the 32KB DMA buffer with repeating WS2812 frames + reset gaps */
+ memset(dmab.area, 0, AE5_STRIP_DMA_SIZE);
+ dst = (__le32 *)dmab.area;
+
+ /* Words per frame: preamble + (num_leds * words_per_led) + reset gap */
+ frame_words = AE5_STRIP_PREAMBLE_WORDS + (num_leds * AE5_STRIP_WORDS_PER_LED) +
+ AE5_STRIP_RESET_GAP_WORDS;
+
+ while (word_idx + frame_words <= AE5_STRIP_TOTAL_WORDS) {
+ /* Preamble per Windows CtxHda FUN_00042944 */
+ for (w = 0; w < AE5_STRIP_PREAMBLE_WORDS; w++) {
+ *dst++ = 0;
+ word_idx++;
+ }
+ /* LEDs */
+ for (led = 0; led < num_leds; led++) {
+ ae5_strip_encode_24(grb_colors[led], enc_words);
+ for (w = 0; w < AE5_STRIP_WORDS_PER_LED; w++) {
+ *dst++ = cpu_to_le32(enc_words[w]);
+ word_idx++;
+ }
+ }
+ /* Zero reset gap (> 1.8 ms; WS2812 requires > 50 us low) */
+ for (w = 0; w < AE5_STRIP_RESET_GAP_WORDS; w++) {
+ *dst++ = 0;
+ word_idx++;
+ }
+ }
+ while (word_idx < AE5_STRIP_TOTAL_WORDS) {
+ *dst++ = 0;
+ word_idx++;
+ }
+ /* Ensure DMA buffer writes are committed before triggering the HDA stream */
+ wmb();
+
+ /* 2. Configure CA0113 BAR2 hardware registers */
+ /* Power up clocks (CtxHdb 0x141b7 / 0x14210) */
+ writel(readl(spec->mem_base + 0xc04) | 0x7, spec->mem_base + 0xc04);
+
+ /* Base serializer initialization (CtxHdb 0x14ba4) */
+ writel(readl(spec->mem_base + 0x400) | 1, spec->mem_base + 0x400);
+ for (i = 0; i < 4; i++) {
+ writel(readl(CA0113_SERIALIZER_BASE(spec, i)) & ~1,
+ CA0113_SERIALIZER_BASE(spec, i));
+ writel(0, CA0113_SERIALIZER_CLK(spec, i));
+ }
+ writel(readl(spec->mem_base + 0x408) | 1, spec->mem_base + 0x408);
+ writel(readl(spec->mem_base + 0x40c) | 1, spec->mem_base + 0x40c);
+ writel((readl(spec->mem_base + 0x410) & ~0xb) | 0x14, spec->mem_base + 0x410);
+
+ /* Configure serializers 0..3 clocking (CtxHdb 0x14a6c) */
+ for (i = 0; i < 4; i++)
+ writel(readl(CA0113_SERIALIZER_CLK(spec, i)) | 0x33,
+ CA0113_SERIALIZER_CLK(spec, i));
+
+ /* BAR2 + 0x100 format & channel enables:
+ * - base 0x70f
+ * - bit 28 = 1 (24-bit audio format via CtxHdb 0x14f58)
+ * - bits 29..31 = 1 (44.1 kHz sample rate via CtxHdb 0x14fa4)
+ * - bits 4..7 = 0xf (stage 1 enables)
+ * - bits 12..15 = 0xf (stage 2 enables)
+ * => 0x3000ff8f
+ */
+ writel((readl(spec->mem_base + 0x100) & ~0xe000ffff) | 0x3000ff8f, spec->mem_base + 0x100);
+
+ /* Enable serializer output drivers for pins 0..3 (CtxHdb 0x14e94) */
+ for (i = 0; i < 4; i++)
+ writel(readl(CA0113_SERIALIZER_OUT(spec, i)) | 1,
+ CA0113_SERIALIZER_OUT(spec, i));
+
+ /* Route all channels 0..3 to stream_tag (CtxHdb 0x1502c) */
+ writel((stream_tag << 28) | (stream_tag << 20) | (stream_tag << 12) | (stream_tag << 4),
+ spec->mem_base + 0x104);
+
+ /* Enable CA0113 MMIO GPIO 0 & 1 */
+ ca0113_mmio_gpio_set(codec, 0, true);
+ ca0113_mmio_gpio_set(codec, 1, true);
+
+ codec_dbg(codec, "AE5 strip: send_frame: num_leds=%d stream_tag=%u (0x%08x)\n",
+ num_leds, stream_tag, readl(spec->mem_base + 0x104));
+
+ /* 3. Trigger stream transmission on HDA link */
+ snd_hdac_dsp_trigger(hstr, true);
+ /*
+ * WS2812 latches on the >50us reset gap after one frame. The 32KB buffer holds ~64
+ * repeating frame+reset cycles; at ~1.4ms/frame a 15ms run transmits ~10 complete
+ * cycles - more than enough to latch reliably. Keep it short so rapid sysfs writes
+ * (OpenRGB effects/theme changes) are not serialized behind a 1s blocking sleep.
+ */
+ usleep_range(15000, 20000);
+ snd_hdac_dsp_trigger(hstr, false);
+ codec_dbg(codec, "AE5 strip: trigger complete\n");
+
+ /* Teardown: clear 0x104 pin mux back to 0 */
+ writel(0x00000000, spec->mem_base + 0x104);
+
+ snd_hdac_dsp_cleanup(hstr, &dmab);
+ scoped_guard(spinlock_irq, &bus->reg_lock)
+ hstr->opened = 0;
+
+ return 0;
+}
+
+static int ae5_strip_ctl_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES;
+ uinfo->count = AE5_STRIP_MAX_LEDS * 3;
+ return 0;
+}
+
+static int ae5_strip_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;
+ int i;
+
+ guard(mutex)(&spec->ae5_strip_mutex);
+ for (i = 0; i < AE5_STRIP_MAX_LEDS; i++) {
+ u32 grb = spec->ae5_strip_cur_colors[i];
+
+ ucontrol->value.bytes.data[i * 3 + 0] = (grb >> 8) & 0xff;
+ ucontrol->value.bytes.data[i * 3 + 1] = (grb >> 16) & 0xff;
+ ucontrol->value.bytes.data[i * 3 + 2] = grb & 0xff;
+ }
+ return 0;
+}
+
+static int ae5_strip_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;
+ u32 colors[AE5_STRIP_MAX_LEDS];
+ int i, num_leds, change, ret;
+
+ guard(mutex)(&spec->ae5_strip_mutex);
+ for (i = 0; i < AE5_STRIP_MAX_LEDS; i++) {
+ u8 r = ucontrol->value.bytes.data[i * 3 + 0];
+ u8 g = ucontrol->value.bytes.data[i * 3 + 1];
+ u8 b = ucontrol->value.bytes.data[i * 3 + 2];
+
+ colors[i] = ((u32)g << 16) | ((u32)r << 8) | (u32)b;
+ }
+ change = memcmp(spec->ae5_strip_cur_colors, colors,
+ sizeof(spec->ae5_strip_cur_colors)) != 0;
+ num_leds = spec->ae5_strip_cur_num_leds;
+ ret = ae5_strip_send_frame(codec, colors, num_leds);
+ if (ret < 0)
+ return ret;
+ if (change)
+ memcpy(spec->ae5_strip_cur_colors, colors,
+ sizeof(spec->ae5_strip_cur_colors));
+ return change;
+}
+
+static int ae5_strip_num_leds_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 = AE5_STRIP_MAX_LEDS;
+ return 0;
+}
+
+static int ae5_strip_num_leds_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;
+
+ guard(mutex)(&spec->ae5_strip_mutex);
+ ucontrol->value.integer.value[0] = spec->ae5_strip_cur_num_leds;
+ return 0;
+}
+
+static int ae5_strip_num_leds_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 val = ucontrol->value.integer.value[0];
+ int ret;
+
+ if (val < 1 || val > AE5_STRIP_MAX_LEDS)
+ return -EINVAL;
+
+ guard(mutex)(&spec->ae5_strip_mutex);
+ if (spec->ae5_strip_cur_num_leds == val)
+ return 0;
+ ret = ae5_strip_send_frame(codec, spec->ae5_strip_cur_colors, val);
+ if (ret < 0)
+ return ret;
+ spec->ae5_strip_cur_num_leds = val;
+ return 1;
+}
+
+static const struct snd_kcontrol_new ae5_strip_ctls[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_CARD,
+ .name = "AE-5 LED Strip",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = ae5_strip_ctl_info,
+ .get = ae5_strip_ctl_get,
+ .put = ae5_strip_ctl_put,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_CARD,
+ .name = "AE-5 LED Strip Count",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = ae5_strip_num_leds_info,
+ .get = ae5_strip_num_leds_get,
+ .put = ae5_strip_num_leds_put,
+ },
+};
+
+static int ae5_add_strip_controls(struct hda_codec *codec)
+{
+ int i, err;
+
+ for (i = 0; i < ARRAY_SIZE(ae5_strip_ctls); i++) {
+ err = snd_hda_ctl_add(codec, 0,
+ snd_ctl_new1(&ae5_strip_ctls[i], codec));
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+#endif /* CONFIG_SND_HDA_DSP_LOADER */
+
static void ae5_setup_defaults(struct hda_codec *codec)
{
struct ca0132_spec *spec = codec->spec;
@@ -8741,6 +9123,10 @@ static void ca0132_init_chip(struct hda_codec *codec)
unsigned int on;
mutex_init(&spec->chipio_mutex);
+#if IS_ENABLED(CONFIG_SND_HDA_DSP_LOADER)
+ mutex_init(&spec->ae5_strip_mutex);
+ spec->ae5_strip_cur_num_leds = 10;
+#endif
/*
* The Windows driver always does this upon startup, which seems to
@@ -9627,6 +10013,9 @@ static void ca0132_free(struct hda_codec *codec)
if (spec->mem_base)
pci_iounmap(codec->bus->pci, spec->mem_base);
#endif
+#if IS_ENABLED(CONFIG_SND_HDA_DSP_LOADER)
+ mutex_destroy(&spec->ae5_strip_mutex);
+#endif
kfree(spec->spec_init_verbs);
kfree(codec->spec);
codec->spec = NULL;
diff --git a/sound/hda/codecs/cirrus/cs8409-tables.c b/sound/hda/codecs/cirrus/cs8409-tables.c
index b9ec8fb8eab7..d0cef8492c99 100644
--- a/sound/hda/codecs/cirrus/cs8409-tables.c
+++ b/sound/hda/codecs/cirrus/cs8409-tables.c
@@ -594,7 +594,7 @@ const struct hda_quirk cs8409_fixup_tbl[] = {
SND_PCI_QUIRK(0x1028, 0x0BA8, "Odin", CS8409_ODIN),
SND_PCI_QUIRK(0x1028, 0x0BAA, "Odin", CS8409_ODIN),
SND_PCI_QUIRK(0x1028, 0x0BAE, "Odin", CS8409_ODIN),
- SND_PCI_QUIRK(0x1028, 0x0BB2, "Warlock MLK", CS8409_WARLOCK_MLK),
+ SND_PCI_QUIRK(0x1028, 0x0BB2, "Warlock MLK", CS8409_WARLOCK_MLK_DELAYED_JD),
SND_PCI_QUIRK(0x1028, 0x0BB3, "Warlock MLK", CS8409_WARLOCK_MLK),
SND_PCI_QUIRK(0x1028, 0x0BB4, "Warlock MLK", CS8409_WARLOCK_MLK),
SND_PCI_QUIRK(0x1028, 0x0BB5, "Warlock N3 15 TGL-U Nuvoton EC", CS8409_WARLOCK),
@@ -625,6 +625,7 @@ const struct hda_model_fixup cs8409_models[] = {
{ .id = CS8409_BULLSEYE, .name = "bullseye" },
{ .id = CS8409_WARLOCK, .name = "warlock" },
{ .id = CS8409_WARLOCK_MLK, .name = "warlock mlk" },
+ { .id = CS8409_WARLOCK_MLK_DELAYED_JD, .name = "warlock mlk delayed jd" },
{ .id = CS8409_WARLOCK_MLK_DUAL_MIC, .name = "warlock mlk dual mic" },
{ .id = CS8409_CYBORG, .name = "cyborg" },
{ .id = CS8409_DOLPHIN, .name = "dolphin" },
@@ -652,6 +653,12 @@ const struct hda_fixup cs8409_fixups[] = {
.chained = true,
.chain_id = CS8409_FIXUPS,
},
+ [CS8409_WARLOCK_MLK_DELAYED_JD] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = cs8409_cs42l42_pincfgs,
+ .chained = true,
+ .chain_id = CS8409_FIXUPS,
+ },
[CS8409_WARLOCK_MLK_DUAL_MIC] = {
.type = HDA_FIXUP_PINS,
.v.pins = cs8409_cs42l42_pincfgs,
diff --git a/sound/hda/codecs/cirrus/cs8409.c b/sound/hda/codecs/cirrus/cs8409.c
index c43ff3ef75b6..12ee71d7a35c 100644
--- a/sound/hda/codecs/cirrus/cs8409.c
+++ b/sound/hda/codecs/cirrus/cs8409.c
@@ -56,6 +56,9 @@ static int cs8409_parse_auto_config(struct hda_codec *codec)
}
static void cs8409_disable_i2c_clock_worker(struct work_struct *work);
+static void cs8409_jack_detect_worker(struct work_struct *work);
+static void cs42l42_run_jack_detect(struct sub_codec *cs42l42);
+static int cs8409_i2c_read(struct sub_codec *scodec, unsigned int addr);
static struct cs8409_spec *cs8409_alloc_spec(struct hda_codec *codec)
{
@@ -69,6 +72,7 @@ static struct cs8409_spec *cs8409_alloc_spec(struct hda_codec *codec)
codec->power_save_node = 1;
mutex_init(&spec->i2c_mux);
INIT_DELAYED_WORK(&spec->i2c_clk_work, cs8409_disable_i2c_clock_worker);
+ INIT_DELAYED_WORK(&spec->jack_detect_work, cs8409_jack_detect_worker);
snd_hda_gen_spec_init(&spec->gen);
return spec;
@@ -116,6 +120,28 @@ static void cs8409_disable_i2c_clock_worker(struct work_struct *work)
}
/*
+ * cs8409_jack_detect_worker - Perform initial jack detection once hardware is settled
+ *
+ * Defer initial jack detection. On ChromeOS and possibly other platforms this
+ * delay is required for jack detection to work as expected.
+ */
+static void cs8409_jack_detect_worker(struct work_struct *work)
+{
+ struct cs8409_spec *spec = container_of(work, struct cs8409_spec, jack_detect_work.work);
+ struct sub_codec *cs42l42 = spec->scodecs[CS8409_CODEC0];
+
+ if (spec->init_done && spec->build_ctrl_done && !cs42l42->hp_jack_in) {
+ int reg_ts_status = cs8409_i2c_read(cs42l42, CS42L42_TSRS_PLUG_STATUS);
+
+ /* error case */
+ if (reg_ts_status < 0)
+ return;
+
+ cs42l42_run_jack_detect(cs42l42);
+ }
+}
+
+/*
* cs8409_enable_i2c_clock - Enable I2C clocks
* @codec: the codec instance
* Enable I2C clocks.
@@ -951,6 +977,8 @@ static void cs8409_remove(struct hda_codec *codec)
{
struct cs8409_spec *spec = codec->spec;
+ /* Disable jack detect work */
+ disable_delayed_work_sync(&spec->jack_detect_work);
/* Cancel i2c clock disable timer, and disable clock if left enabled */
cancel_delayed_work_sync(&spec->i2c_clk_work);
cs8409_disable_i2c_clock(codec);
@@ -1020,6 +1048,9 @@ static int cs8409_cs42l42_suspend(struct hda_codec *codec)
cs8409_enable_ur(codec, 0);
+ /* Cancel jack detect work first as it accesses CS42L42 over I2C */
+ cancel_delayed_work_sync(&spec->jack_detect_work);
+
for (i = 0; i < spec->num_scodecs; i++)
cs42l42_suspend(spec->scodecs[i]);
@@ -1153,6 +1184,9 @@ void cs8409_cs42l42_fixups(struct hda_codec *codec, const struct hda_fixup *fix,
spec->scodecs[CS8409_CODEC0]->full_scale_vol = CS42L42_FULL_SCALE_VOL_0DB;
spec->speaker_pdn_gpio = CS8409_CYBORG_SPEAKER_PDN;
break;
+ case CS8409_WARLOCK_MLK_DELAYED_JD:
+ spec->delay_jack_detect = 1;
+ fallthrough;
case CS8409_WARLOCK_MLK:
case CS8409_WARLOCK_MLK_DUAL_MIC:
spec->scodecs[CS8409_CODEC0]->full_scale_vol = CS42L42_FULL_SCALE_VOL_0DB;
@@ -1197,8 +1231,13 @@ void cs8409_cs42l42_fixups(struct hda_codec *codec, const struct hda_fixup *fix,
cs8409_cs42l42_hw_init(codec);
spec->init_done = 1;
if (spec->init_done && spec->build_ctrl_done
- && !spec->scodecs[CS8409_CODEC0]->hp_jack_in)
- cs42l42_run_jack_detect(spec->scodecs[CS8409_CODEC0]);
+ && !spec->scodecs[CS8409_CODEC0]->hp_jack_in) {
+ if (spec->delay_jack_detect)
+ schedule_delayed_work(&spec->jack_detect_work,
+ msecs_to_jiffies(CS8409_JACK_DETECT_DELAY_MS));
+ else
+ cs42l42_run_jack_detect(spec->scodecs[CS8409_CODEC0]);
+ }
break;
case HDA_FIXUP_ACT_BUILD:
spec->build_ctrl_done = 1;
@@ -1208,8 +1247,13 @@ void cs8409_cs42l42_fixups(struct hda_codec *codec, const struct hda_fixup *fix,
* Run immediately after init.
*/
if (spec->init_done && spec->build_ctrl_done
- && !spec->scodecs[CS8409_CODEC0]->hp_jack_in)
- cs42l42_run_jack_detect(spec->scodecs[CS8409_CODEC0]);
+ && !spec->scodecs[CS8409_CODEC0]->hp_jack_in) {
+ if (spec->delay_jack_detect)
+ schedule_delayed_work(&spec->jack_detect_work,
+ msecs_to_jiffies(CS8409_JACK_DETECT_DELAY_MS));
+ else
+ cs42l42_run_jack_detect(spec->scodecs[CS8409_CODEC0]);
+ }
break;
default:
break;
diff --git a/sound/hda/codecs/cirrus/cs8409.h b/sound/hda/codecs/cirrus/cs8409.h
index be1714a84fff..dacc1083b9d4 100644
--- a/sound/hda/codecs/cirrus/cs8409.h
+++ b/sound/hda/codecs/cirrus/cs8409.h
@@ -247,6 +247,7 @@ enum cs8409_coefficient_index_registers {
#define CS8409_CS42L42_AMIC_PIN_NID CS8409_PIN_ASP1_RECEIVER_A
#define CS8409_CS42L42_DMIC_PIN_NID CS8409_PIN_DMIC1_IN
#define CS8409_CS42L42_DMIC_ADC_PIN_NID CS8409_PIN_DMIC1
+#define CS8409_JACK_DETECT_DELAY_MS 100
/* Dolphin */
@@ -266,6 +267,7 @@ enum {
CS8409_BULLSEYE,
CS8409_WARLOCK,
CS8409_WARLOCK_MLK,
+ CS8409_WARLOCK_MLK_DELAYED_JD,
CS8409_WARLOCK_MLK_DUAL_MIC,
CS8409_CYBORG,
CS8409_FIXUPS,
@@ -339,12 +341,14 @@ struct cs8409_spec {
unsigned int i2c_clck_enabled;
unsigned int dev_addr;
struct delayed_work i2c_clk_work;
+ struct delayed_work jack_detect_work;
unsigned int playback_started:1;
unsigned int capture_started:1;
unsigned int init_done:1;
unsigned int build_ctrl_done:1;
unsigned int speaker_muted:1;
+ unsigned int delay_jack_detect:1;
/* verb exec op override */
int (*exec_verb)(struct hdac_device *dev, unsigned int cmd, unsigned int flags,
diff --git a/sound/hda/codecs/conexant.c b/sound/hda/codecs/conexant.c
index 9bfdbf5c9032..a141c94eee13 100644
--- a/sound/hda/codecs/conexant.c
+++ b/sound/hda/codecs/conexant.c
@@ -302,6 +302,7 @@ enum {
CXT_FIXUP_HP_SPECTRE,
CXT_FIXUP_HP_GATE_MIC,
CXT_FIXUP_MUTE_LED_GPIO,
+ CXT_FIXUP_ACER_ASPIRE_GO_MUTE_LED,
CXT_FIXUP_HP_ELITEONE_OUT_DIS,
CXT_FIXUP_HP_ZBOOK_MUTE_LED,
CXT_FIXUP_HEADSET_MIC,
@@ -312,6 +313,7 @@ enum {
CXT_FIXUP_HP_A_U,
CXT_FIXUP_ACER_SWIFT_HP,
CXT_FIXUP_HUAWEI_MATEBOOK_HP,
+ CXT_FIXUP_MATEBOOK14_SN6140,
};
/* for hda_fixup_thinkpad_acpi() */
@@ -808,6 +810,13 @@ static void cxt_fixup_hp_zbook_mute_led(struct hda_codec *codec,
cxt_setup_mute_led(codec, 0x10, 0x20);
}
+static void cxt_fixup_acer_aspire_go_mute_led(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action == HDA_FIXUP_ACT_PRE_PROBE)
+ cxt_setup_mute_led(codec, 0, 0x10);
+}
+
static void cxt_fixup_hp_a_u(struct hda_codec *codec,
const struct hda_fixup *fix, int action)
{
@@ -817,6 +826,67 @@ static void cxt_fixup_hp_a_u(struct hda_codec *codec,
cxt_setup_gpio_unmute(codec, 0x2);
}
+/* Huawei MateBook 14 (SN6140): the speaker output follows the headphone
+ * pin's connection select in hardware. Both pins must be routed to the
+ * shared DAC 0x10, which carries the playback stream; the generic
+ * parser mutes that DAC when no headphone is plugged, silencing the
+ * speaker. Suppress the auto-mute, bind the speaker pin to the shared
+ * DAC, remove its mute switch and keep it unmuted; the auto-mute hook
+ * re-applies the routing after every output update.
+ */
+static void cxt_mb14_route_unmute(struct hda_codec *codec)
+{
+ /* route both pins to the shared DAC 0x10, enable the speaker
+ * EAPD and unmute the DAC at a moderate gain; the vmaster
+ * volume takes over once userspace starts
+ */
+ snd_hda_codec_write(codec, 0x16, 0, AC_VERB_SET_CONNECT_SEL, 0);
+ snd_hda_codec_write(codec, 0x17, 0, AC_VERB_SET_CONNECT_SEL, 0);
+ snd_hda_codec_write(codec, 0x17, 0, AC_VERB_SET_EAPD_BTLENABLE, 0x02);
+ snd_hda_codec_amp_stereo(codec, 0x10, HDA_OUTPUT, 0,
+ HDA_AMP_VOLMASK | HDA_AMP_MUTE, 0x40);
+}
+
+static void cxt_mb14_automute(struct hda_codec *codec)
+{
+ snd_hda_gen_update_outputs(codec);
+ cxt_mb14_route_unmute(codec);
+}
+
+static void cxt_fixup_matebook14_sn6140(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ struct conexant_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ static const hda_nid_t dac_0x10[] = { 0x10 };
+
+ spec->gen.suppress_auto_mute = 1;
+ spec->gen.automute_hook = cxt_mb14_automute;
+ /* Bind the speaker pin to the shared DAC 0x10.
+ */
+ snd_hda_override_conn_list(codec, 0x17, 1, dac_0x10);
+ return;
+ }
+ if (action == HDA_FIXUP_ACT_PROBE) {
+ unsigned int caps;
+
+ /* Remove the mute switch on the shared DAC so that
+ * userspace (WirePlumber) cannot mute it when the
+ * headphone route becomes unavailable (e.g. after
+ * resume). Must be done here: the codec regmap is not
+ * available at pre-probe time.
+ */
+ caps = query_amp_caps(codec, 0x10, HDA_OUTPUT);
+ snd_hda_override_amp_caps(codec, 0x10, HDA_OUTPUT,
+ caps & ~AC_AMPCAP_MUTE);
+ return;
+ }
+ if (action != HDA_FIXUP_ACT_INIT)
+ return;
+ cxt_mb14_route_unmute(codec);
+}
+
/* ThinkPad X200 & co with cxt5051 */
static const struct hda_pintbl cxt_pincfg_lenovo_x200[] = {
{ 0x16, 0x042140ff }, /* HP (seq# overridden) */
@@ -1007,6 +1077,10 @@ static const struct hda_fixup cxt_fixups[] = {
.type = HDA_FIXUP_FUNC,
.v.func = cxt_fixup_mute_led_gpio,
},
+ [CXT_FIXUP_ACER_ASPIRE_GO_MUTE_LED] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = cxt_fixup_acer_aspire_go_mute_led,
+ },
[CXT_FIXUP_HP_ELITEONE_OUT_DIS] = {
.type = HDA_FIXUP_FUNC,
.v.func = cxt_fixup_update_pinctl,
@@ -1062,6 +1136,10 @@ static const struct hda_fixup cxt_fixups[] = {
{ }
},
},
+ [CXT_FIXUP_MATEBOOK14_SN6140] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = cxt_fixup_matebook14_sn6140,
+ },
};
static const struct hda_quirk cxt5045_fixups[] = {
@@ -1112,6 +1190,7 @@ static const struct hda_quirk cxt5066_fixups[] = {
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(0x1025, 0x136d, "Acer Swift SF314", CXT_FIXUP_ACER_SWIFT_HP),
+ SND_PCI_QUIRK(0x1025, 0x1937, "Acer Aspire Go 16", CXT_FIXUP_ACER_ASPIRE_GO_MUTE_LED),
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),
@@ -1164,6 +1243,7 @@ static const struct hda_quirk cxt5066_fixups[] = {
SND_PCI_QUIRK(0x17aa, 0x397b, "Lenovo S205", CXT_FIXUP_STEREO_DMIC),
SND_PCI_QUIRK_VENDOR(0x17aa, "Thinkpad/Ideapad", CXT_FIXUP_LENOVO_XPAD_ACPI),
SND_PCI_QUIRK(0x19e5, 0x3289, "Huawei Matebook", CXT_FIXUP_HUAWEI_MATEBOOK_HP),
+ SND_PCI_QUIRK(0x19e5, 0x329e, "Huawei MateBook 14", CXT_FIXUP_MATEBOOK14_SN6140),
SND_PCI_QUIRK(0x1c06, 0x2011, "Lemote A1004", CXT_PINCFG_LEMOTE_A1004),
SND_PCI_QUIRK(0x1c06, 0x2012, "Lemote A1205", CXT_PINCFG_LEMOTE_A1205),
SND_PCI_QUIRK(0x1d05, 0x3012, "MECHREVO Wujie 15X Pro", CXT_FIXUP_HEADSET_MIC),
diff --git a/sound/hda/codecs/hdmi/eld.c b/sound/hda/codecs/hdmi/eld.c
index 1464fd1c675b..fc6fe1b0873c 100644
--- a/sound/hda/codecs/hdmi/eld.c
+++ b/sound/hda/codecs/hdmi/eld.c
@@ -155,9 +155,11 @@ void snd_hdmi_write_eld_info(struct hdmi_eld *eld,
e->aud_synch_delay = val;
else if (!strcmp(name, "speakers"))
e->spk_alloc = val;
- else if (!strcmp(name, "sad_count"))
+ else if (!strcmp(name, "sad_count")) {
+ if (val > ELD_MAX_SAD)
+ continue;
e->sad_count = val;
- else if (!strncmp(name, "sad", 3)) {
+ } else if (!strncmp(name, "sad", 3)) {
sname = name + 4;
n = name[3] - '0';
if (name[4] >= '0' && name[4] <= '9') {
diff --git a/sound/hda/codecs/hdmi/hdmi.c b/sound/hda/codecs/hdmi/hdmi.c
index 1e7a05c87733..355fd3e73183 100644
--- a/sound/hda/codecs/hdmi/hdmi.c
+++ b/sound/hda/codecs/hdmi/hdmi.c
@@ -983,7 +983,8 @@ static int hdmi_read_pin_conn(struct hda_codec *codec, int pin_idx)
snd_hda_set_dev_select(codec, pin_nid, dev_id);
if (spec->intel_hsw_fixup) {
- conns = spec->num_cvts;
+ /* spec->cvt_nids[] only has ARRAY_SIZE(spec->cvt_nids) slots */
+ conns = min_t(int, spec->num_cvts, ARRAY_SIZE(spec->cvt_nids));
memcpy(per_pin->mux_nids, spec->cvt_nids,
sizeof(hda_nid_t) * conns);
} else {
@@ -1671,7 +1672,7 @@ int snd_hda_hdmi_generic_pcm_prepare(struct hda_pcm_stream *hinfo,
* 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,
+ * default converter and mute control will affect each other,
* which can cause a resumed audio playback become silent
* after S3.
*/
diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c
index 1623cf926c71..e3b3e88dbf8a 100644
--- a/sound/hda/codecs/realtek/alc269.c
+++ b/sound/hda/codecs/realtek/alc269.c
@@ -1754,6 +1754,25 @@ static void alc295_fixup_hp_pavilion_mute_led_1b(struct hda_codec *codec,
spec->mute_led_nid = 0x1b;
}
+/*
+ * HP 245 14 inch G10 Notebook PC (SSID 103c:8ba4): the speaker mute LED is
+ * controlled through the HP_Mute_LED DMI string (NID 0x1a), same as the
+ * ALC269_FIXUP_HP_MUTE_LED fallback, but the microphone mute LED is wired to
+ * COEF 0x35 bits 2-3 with an inverted sense.
+ */
+static void alc236_fixup_hp_mute_led_micmute_coef_inv(struct hda_codec *codec,
+ const struct hda_fixup *fix,
+ int action)
+{
+ struct alc_spec *spec = codec->spec;
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE)
+ spec->micmute_led_polarity = 1;
+
+ alc269_fixup_hp_mute_led(codec, fix, action);
+ alc236_fixup_hp_coef_micmute_led(codec, fix, action);
+}
+
static void alc233_fixup_lenovo_coef_micmute_led(struct hda_codec *codec,
const struct hda_fixup *fix, int action)
{
@@ -1840,6 +1859,48 @@ struct alc298_samsung_amp_desc {
unsigned short init_seq[2][2];
};
+/*
+ * The MSI GE66 Raider 11UE drives its speakers through a Realtek ALC1316 smart
+ * amplifier, reached over the ALC298 coefficient tunnel. Left unconfigured the
+ * amplifier's silence detection mutes the speaker path after about 0.2 s of
+ * quiet and takes about 0.4 s to reopen, so the start of every sound that
+ * follows a pause is lost. Headphones do not pass through the amplifier and are
+ * unaffected.
+ *
+ * Tunnel layout for ALC286/ALC298: coefficient 0x22 selects the amplifier, 0x23
+ * carries the 16-bit register, 0x24 and 0x25 the value's high and low halves,
+ * and 0x26 commits. Bit 4 of the commit word is a self-clearing go bit.
+ */
+static void alc298_fixup_alc1316_amp(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ static const unsigned short init_seq[][2] = {
+ { 0x0000, 0x01 }, { 0xc001, 0x14 }, { 0xc003, 0x00 }, { 0xc004, 0x11 },
+ { 0xc005, 0x00 }, { 0xc006, 0x00 }, { 0xc007, 0x11 }, { 0xc008, 0x11 },
+ { 0xc009, 0x00 }, { 0xc730, 0x06 }, { 0xc612, 0x16 }, { 0xc00f, 0xf7 },
+ { 0xc00a, 0xd8 }, { 0xc00b, 0xb7 }, { 0xc60b, 0xff }, { 0xc60c, 0x37 },
+ { 0xc605, 0xf0 }, { 0xc020, 0x00 }, { 0xc022, 0xd6 }, { 0xc023, 0x00 },
+ { 0xc025, 0xd6 }, { 0xc602, 0x07 }, { 0xc603, 0x07 }, { 0xd101, 0x00 },
+ { 0xc093, 0x80 }, { 0xc090, 0x87 }, { 0xc091, 0x11 }, { 0xc614, 0x20 },
+ { 0xc615, 0x0a }, { 0xc616, 0x02 }, { 0xc617, 0x00 }, { 0xc050, 0x80 },
+ { 0xc051, 0x7f }, { 0xc052, 0x00 }, { 0xc053, 0x70 }, { 0xc054, 0x00 },
+ { 0xc0a2, 0x01 }, { 0xc09d, 0x80 }, { 0xc09c, 0x71 }
+ };
+ int i;
+
+ if (action != HDA_FIXUP_ACT_INIT)
+ return;
+
+ alc_write_coef_idx(codec, 0x22, 0x0010);
+
+ for (i = 0; i < ARRAY_SIZE(init_seq); i++) {
+ alc_write_coef_idx(codec, 0x23, init_seq[i][0]);
+ alc_write_coef_idx(codec, 0x24, 0x0000);
+ alc_write_coef_idx(codec, 0x25, init_seq[i][1]);
+ alc_write_coef_idx(codec, 0x26, 0xb031);
+ }
+}
+
static void alc298_fixup_samsung_amp(struct hda_codec *codec,
const struct hda_fixup *fix, int action)
{
@@ -2767,6 +2828,19 @@ static void alc275_fixup_gpio4_off(struct hda_codec *codec,
}
}
+static void alc256_fixup_asus_aio_gpio2_low(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;
+ spec->gpio_data &= ~0x04;
+ }
+}
+
/* Quirk for Thinkpad X1 7th and 8th Gen
* The following fixed routing needed
* DAC1 (NID 0x02) -> Speaker (NID 0x14); some eq applied secretly
@@ -3433,6 +3507,28 @@ static void alc287_fixup_legion_16ithg6_speakers(struct hda_codec *cdc, const st
comp_generic_fixup(cdc, action, "i2c", "CLSA0101", "-%s:00-cs35l41-hda.%d", 2);
}
+static void alc287_fixup_yoga_slim7_carbon_speakers(struct hda_codec *cdc,
+ const struct hda_fixup *fix, int action)
+{
+ /*
+ * The bass speakers are driven by two CS35L41 amps fed over I2S. The codec only
+ * clocks the I2S bus while pin 0x17 is enabled, but the BIOS marks it unconnected.
+ * Keep the pin on DAC 0x02 with the other speakers: DAC 0x06 gets powered down
+ * with stereo streams, which stops the clock and the amps fail to power up.
+ */
+ static const struct hda_pintbl pincfgs[] = {
+ { 0x17, 0x90170121 },
+ { }
+ };
+ static const hda_nid_t conn[] = { 0x02 };
+
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ snd_hda_apply_pincfgs(cdc, pincfgs);
+ snd_hda_override_conn_list(cdc, 0x17, ARRAY_SIZE(conn), conn);
+ }
+ comp_generic_fixup(cdc, action, "i2c", "CLSA0102", "-%s:00-cs35l41-hda.%d", 2);
+}
+
static void alc285_fixup_asus_ga403u(struct hda_codec *cdc, const struct hda_fixup *fix, int action)
{
/*
@@ -3537,6 +3633,27 @@ static void alc233_fixup_no_audio_jack(struct hda_codec *codec,
alc_process_coef_fw(codec, alc233_fixup_no_audio_jack_coefs);
}
+static const struct coef_fw alc256_asus_speaker_coefs[] = {
+ WRITE_COEF(0x10, 0x7f20), WRITE_COEF(0x16, 0x0c50), WRITE_COEF(0x35, 0x8d6a),
+ WRITE_COEF(0x37, 0xfe06), WRITE_COEF(0x57, 0x7f7f),
+ {}
+};
+
+static void alc256_fixup_asus_speaker_coefs(struct hda_codec *codec,
+ const struct hda_fixup *fix,
+ int action)
+{
+ /*
+ * The internal speakers of the ASUS ExpertBook PM3606CHA stay silent
+ * with the default COEF values. These are the values set by the
+ * Windows driver. They are applied on every init so that they are
+ * restored after resume, too.
+ */
+ if (action != HDA_FIXUP_ACT_INIT)
+ return;
+ alc_process_coef_fw(codec, alc256_asus_speaker_coefs);
+}
+
static void alc256_fixup_mic_no_presence_and_resume(struct hda_codec *codec,
const struct hda_fixup *fix,
int action)
@@ -3661,6 +3778,28 @@ static void alc287_fixup_yoga9_14iap7_bass_spk_pin(struct hda_codec *codec,
}
}
+/*
+ * The Lenovo Xiaoxin 14 GT (SSID 17aa:3912) uses the same bass-speaker
+ * pin wiring as its 0x390d/0x3911/0x391a siblings, but on this board the
+ * codec's vendor processing coefficients (node 0x20) are left at their
+ * power-on default instead of being programmed the way the vendor
+ * (Windows) driver programs them. That leaves audible hiss/crackle on
+ * both the headphone jack and the internal speakers, independent of
+ * volume, regardless of which pin/DAC routing is used.
+ *
+ * This single coefficient write was captured from the vendor driver's
+ * own init sequence (via a VFIO pass-through of the physical codec to a
+ * Windows VM) and confirmed to eliminate the headphone jack noise.
+ */
+static void alc287_fixup_lenovo_xiaoxin_14gt_hp_noise(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action != HDA_FIXUP_ACT_INIT)
+ return;
+
+ alc_write_coef_idx(codec, 0x36, 0x77d7);
+}
+
static void alc_fixup_yoga_pro7_audio(struct hda_codec *codec,
const struct hda_fixup *fix, int action)
{
@@ -4165,6 +4304,7 @@ enum {
ALC256_FIXUP_ASUS_HEADSET_MODE,
ALC256_FIXUP_ASUS_MIC,
ALC256_FIXUP_ASUS_AIO_GPIO2,
+ ALC256_FIXUP_ASUS_AIO_GPIO2_LOW,
ALC233_FIXUP_ASUS_MIC_NO_PRESENCE,
ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE,
ALC233_FIXUP_LENOVO_MULTI_CODECS,
@@ -4181,6 +4321,7 @@ enum {
ALC255_FIXUP_DUMMY_LINEOUT_VERB,
ALC255_FIXUP_DELL_HEADSET_MIC,
ALC256_FIXUP_HUAWEI_MACH_WX9_PINS,
+ ALC256_FIXUP_HUAWEI_MACHR_WX9_PINS,
ALC298_FIXUP_HUAWEI_MBX_STEREO,
ALC295_FIXUP_HP_X360,
ALC295_FIXUP_HP_PAVILION_X360,
@@ -4193,6 +4334,7 @@ enum {
ALC294_FIXUP_ASUS_I2C_HEADSET_MIC,
ALC294_FIXUP_ASUS_SPI_HEADSET_MIC,
ALC294_FIXUP_ASUS_SPK,
+ ALC294_FIXUP_ASUS_Q423SA,
ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE,
ALC285_FIXUP_LENOVO_PC_BEEP_IN_NOISE,
ALC255_FIXUP_ACER_HEADSET_MIC,
@@ -4237,10 +4379,12 @@ enum {
ALC236_FIXUP_HP_MUTE_LED_COEFBIT2,
ALC236_FIXUP_HP_GPIO_LED,
ALC236_FIXUP_HP_MUTE_LED,
+ ALC236_FIXUP_HP_MUTE_LED_MICMUTE_COEF_INV,
ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF,
ALC236_FIXUP_HP_15_FD0XXX,
ALC236_FIXUP_LENOVO_INV_DMIC,
ALC298_FIXUP_SAMSUNG_AMP,
+ ALC298_FIXUP_MSI_GE66_ALC1316_AMP,
ALC298_FIXUP_SAMSUNG_AMP_V2_2_AMPS,
ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS,
ALC298_FIXUP_LG_GRAM_STYLE_14,
@@ -4285,11 +4429,13 @@ enum {
ALC295_FIXUP_HP_OMEN,
ALC285_FIXUP_HP_SPECTRE_X360,
ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP,
+ ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP_LED,
ALC623_FIXUP_LENOVO_THINKSTATION_P340,
ALC255_FIXUP_ACER_HEADPHONE_AND_MIC,
ALC236_FIXUP_HP_LIMIT_INT_MIC_BOOST,
ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS,
ALC287_FIXUP_LEGION_15IMHG05_AUTOMUTE,
+ ALC287_FIXUP_LEGION_S7_15IMH05_AMP,
ALC287_FIXUP_YOGA7_14ITL_SPEAKERS,
ALC298_FIXUP_LENOVO_C940_DUET7,
ALC287_FIXUP_LENOVO_YOGA_BOOK_9I,
@@ -4317,9 +4463,11 @@ enum {
ALC295_FIXUP_FRAMEWORK_LAPTOP_MIC_NO_PRESENCE,
ALC295_FIXUP_FRAMEWORK_LAPTOP_LIMIT_INT_MIC_BOOST,
ALC287_FIXUP_LEGION_16ITHG6,
+ ALC287_FIXUP_YOGA_SLIM7_CARBON_SPEAKERS,
ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK,
ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN,
ALC287_FIXUP_YOGA9_14IMH9_BASS_SPK_PIN,
+ ALC287_FIXUP_LENOVO_XIAOXIN_14GT_HP_NOISE,
ALC295_FIXUP_DELL_INSPIRON_TOP_SPEAKERS,
ALC236_FIXUP_DELL_DUAL_CODECS,
ALC287_FIXUP_CS35L41_I2C_2_THINKPAD_ACPI,
@@ -4338,9 +4486,12 @@ enum {
ALC245_FIXUP_HP_X360_MUTE_LEDS,
ALC287_FIXUP_THINKPAD_I2S_SPK,
ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD,
+ ALC287_FIXUP_CS35L41_I2C_4_THINKPAD_ACPI,
+ ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_4_THINKPAD,
ALC2XX_FIXUP_HEADSET_MIC,
ALC289_FIXUP_DELL_CS35L41_SPI_2,
ALC256_FIXUP_ACER_SFG16_MICMUTE_LED,
+ ALC256_FIXUP_ACER_HEADSET_MICMUTE_LED,
ALC256_FIXUP_HEADPHONE_AMP_VOL,
ALC245_FIXUP_HP_SPECTRE_X360_EU0XXX,
ALC245_FIXUP_HP_SPECTRE_X360_16_AA0XXX,
@@ -4375,8 +4526,9 @@ enum {
ALC245_FIXUP_BASS_HP_DAC,
ALC245_FIXUP_ACER_MICMUTE_LED,
ALC245_FIXUP_CS35L41_I2C_2_MUTE_LED,
- ALC236_FIXUP_HP_DMIC,
ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO,
+ ALC256_FIXUP_HONOR_DRA_XX_SPEAKERS,
+ ALC256_FIXUP_HONOR_DRA_XX_HEADSET_MODE,
ALC245_FIXUP_HP_ENVY_X360_15_FH0XXX,
ALC287_FIXUP_ACER_MICMUTE_LED,
ALC236_FIXUP_DELL_HP_POP_NOISE,
@@ -4388,6 +4540,8 @@ enum {
ALC285_LENOVO_DAC_RENAME,
ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1,
ALC256_FIXUP_IPASON_SMARTBOOK_S1,
+ ALC256_FIXUP_ASUS_SPEAKER_COEFS,
+ ALC245_FIXUP_MINISFORUM_V3_MIC_NO_PRESENCE,
};
/* A special fixup for Lenovo C940 and Yoga Duet 7;
@@ -4437,6 +4591,53 @@ static void alc700_fixup_intel_hades_canyon(struct hda_codec *codec,
alc_update_coef_idx(codec, 0x1b, 0x0400, 0x0400);
}
+static const struct hda_verb legion_amp_verbs[] = {
+ /* left speaker */
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x24 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x41 },
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0xc },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x0 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x1a },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x2 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x0 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x0 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+
+ /* right speaker */
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x24 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x42 },
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0xc },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x0 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x2a },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x2 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x0 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x0 },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
+ {}
+};
+
+static void alc287_fixup_legion_s7_amp(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action != HDA_FIXUP_ACT_INIT)
+ return;
+
+ snd_hda_sequence_write(codec, legion_amp_verbs);
+ /*
+ * The amplifiers accept the setup only a few ms after the first
+ * write (8 ms is the measured minimum on the test machine), so
+ * keep a margin.
+ */
+ msleep(20);
+ snd_hda_sequence_write(codec, legion_amp_verbs);
+}
+
static const struct hda_fixup alc269_fixups[] = {
[ALC298_FIXUP_RAZER_BLADE16_2025_PINS] = {
.type = HDA_FIXUP_PINS,
@@ -4834,6 +5035,24 @@ static const struct hda_fixup alc269_fixups[] = {
.chained = true,
.chain_id = ALC255_FIXUP_MIC_MUTE_LED
},
+ [ALC256_FIXUP_HUAWEI_MACHR_WX9_PINS] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ {0x12, 0x90a60130},
+ {0x13, 0x40000000},
+ {0x14, 0x90170110},
+ {0x18, 0x411111f0},
+ {0x19, 0x04a11140}, /* use as headset mic, without its own jack detect */
+ {0x1a, 0x411111f0},
+ {0x1b, 0x90170112},
+ {0x1d, 0x40759a05},
+ {0x1e, 0x411111f0},
+ {0x21, 0x04211020},
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC269_FIXUP_HEADSET_MODE
+ },
[ALC298_FIXUP_HUAWEI_MBX_STEREO] = {
.type = HDA_FIXUP_FUNC,
.v.func = alc298_fixup_huawei_mbx_stereo,
@@ -5442,6 +5661,10 @@ static const struct hda_fixup alc269_fixups[] = {
/* Set up GPIO2 for the speaker amp */
.v.func = alc_fixup_gpio4,
},
+ [ALC256_FIXUP_ASUS_AIO_GPIO2_LOW] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc256_fixup_asus_aio_gpio2_low,
+ },
[ALC233_FIXUP_ASUS_MIC_NO_PRESENCE] = {
.type = HDA_FIXUP_PINS,
.v.pins = (const struct hda_pintbl[]) {
@@ -5669,6 +5892,17 @@ static const struct hda_fixup alc269_fixups[] = {
.chained = true,
.chain_id = ALC294_FIXUP_ASUS_HEADSET_MIC
},
+ [ALC294_FIXUP_ASUS_Q423SA] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = (const struct hda_verb[]) {
+ /* Restore bit 10 lost after reboot from Windows */
+ { 0x20, AC_VERB_SET_COEF_INDEX, 0x1b },
+ { 0x20, AC_VERB_SET_PROC_COEF, 0x4e4b },
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC2XX_FIXUP_HEADSET_MIC,
+ },
[ALC295_FIXUP_CHROME_BOOK] = {
.type = HDA_FIXUP_FUNC,
.v.func = alc295_fixup_chromebook,
@@ -6033,6 +6267,10 @@ static const struct hda_fixup alc269_fixups[] = {
.type = HDA_FIXUP_FUNC,
.v.func = alc236_fixup_hp_mute_led_micmute_vref,
},
+ [ALC236_FIXUP_HP_MUTE_LED_MICMUTE_COEF_INV] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc236_fixup_hp_mute_led_micmute_coef_inv,
+ },
[ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO] = {
.type = HDA_FIXUP_FUNC,
.v.func = alc236_fixup_hp_mute_led_micmute_gpio,
@@ -6055,6 +6293,10 @@ static const struct hda_fixup alc269_fixups[] = {
.type = HDA_FIXUP_FUNC,
.v.func = alc295_fixup_hp_pavilion_mute_led_1b,
},
+ [ALC298_FIXUP_MSI_GE66_ALC1316_AMP] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc298_fixup_alc1316_amp,
+ },
[ALC298_FIXUP_SAMSUNG_AMP] = {
.type = HDA_FIXUP_FUNC,
.v.func = alc298_fixup_samsung_amp,
@@ -6435,6 +6677,12 @@ static const struct hda_fixup alc269_fixups[] = {
.chained = true,
.chain_id = ALC285_FIXUP_THINKPAD_HEADSET_JACK,
},
+ [ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP_LED] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_ideapad_acpi,
+ .chained = true,
+ .chain_id = ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP,
+ },
[ALC287_FIXUP_LENOVO_YOGA_PRO7] = {
.type = HDA_FIXUP_FUNC,
.v.func = alc_fixup_yoga_pro7_audio,
@@ -6486,41 +6734,13 @@ static const struct hda_fixup alc269_fixups[] = {
},
[ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS] = {
.type = HDA_FIXUP_VERBS,
- //.v.verbs = legion_15imhg05_coefs,
- .v.verbs = (const struct hda_verb[]) {
- // set left speaker Legion 7i.
- { 0x20, AC_VERB_SET_COEF_INDEX, 0x24 },
- { 0x20, AC_VERB_SET_PROC_COEF, 0x41 },
-
- { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
- { 0x20, AC_VERB_SET_PROC_COEF, 0xc },
- { 0x20, AC_VERB_SET_PROC_COEF, 0x0 },
- { 0x20, AC_VERB_SET_PROC_COEF, 0x1a },
- { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
-
- { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
- { 0x20, AC_VERB_SET_PROC_COEF, 0x2 },
- { 0x20, AC_VERB_SET_PROC_COEF, 0x0 },
- { 0x20, AC_VERB_SET_PROC_COEF, 0x0 },
- { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
-
- // set right speaker Legion 7i.
- { 0x20, AC_VERB_SET_COEF_INDEX, 0x24 },
- { 0x20, AC_VERB_SET_PROC_COEF, 0x42 },
-
- { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
- { 0x20, AC_VERB_SET_PROC_COEF, 0xc },
- { 0x20, AC_VERB_SET_PROC_COEF, 0x0 },
- { 0x20, AC_VERB_SET_PROC_COEF, 0x2a },
- { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
-
- { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 },
- { 0x20, AC_VERB_SET_PROC_COEF, 0x2 },
- { 0x20, AC_VERB_SET_PROC_COEF, 0x0 },
- { 0x20, AC_VERB_SET_PROC_COEF, 0x0 },
- { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 },
- {}
- },
+ .v.verbs = legion_amp_verbs,
+ .chained = true,
+ .chain_id = ALC287_FIXUP_LEGION_15IMHG05_AUTOMUTE,
+ },
+ [ALC287_FIXUP_LEGION_S7_15IMH05_AMP] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc287_fixup_legion_s7_amp,
.chained = true,
.chain_id = ALC287_FIXUP_LEGION_15IMHG05_AUTOMUTE,
},
@@ -6713,6 +6933,10 @@ static const struct hda_fixup alc269_fixups[] = {
.type = HDA_FIXUP_FUNC,
.v.func = alc287_fixup_legion_16ithg6_speakers,
},
+ [ALC287_FIXUP_YOGA_SLIM7_CARBON_SPEAKERS] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc287_fixup_yoga_slim7_carbon_speakers,
+ },
[ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK] = {
.type = HDA_FIXUP_VERBS,
.v.verbs = (const struct hda_verb[]) {
@@ -6787,6 +7011,12 @@ static const struct hda_fixup alc269_fixups[] = {
.chained = true,
.chain_id = ALC287_FIXUP_CS35L41_I2C_2,
},
+ [ALC287_FIXUP_LENOVO_XIAOXIN_14GT_HP_NOISE] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc287_fixup_lenovo_xiaoxin_14gt_hp_noise,
+ .chained = true,
+ .chain_id = ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN,
+ },
[ALC295_FIXUP_DELL_INSPIRON_TOP_SPEAKERS] = {
.type = HDA_FIXUP_FUNC,
.v.func = alc295_fixup_dell_inspiron_top_speakers,
@@ -6886,6 +7116,18 @@ static const struct hda_fixup alc269_fixups[] = {
.chained = true,
.chain_id = ALC287_FIXUP_CS35L41_I2C_2_THINKPAD_ACPI,
},
+ [ALC287_FIXUP_CS35L41_I2C_4_THINKPAD_ACPI] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = cs35l41_fixup_i2c_four,
+ .chained = true,
+ .chain_id = ALC285_FIXUP_THINKPAD_NO_BASS_SPK_HEADSET_JACK,
+ },
+ [ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_4_THINKPAD] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc287_fixup_tb_vmaster_led,
+ .chained = true,
+ .chain_id = ALC287_FIXUP_CS35L41_I2C_4_THINKPAD_ACPI,
+ },
[ALC2XX_FIXUP_HEADSET_MIC] = {
.type = HDA_FIXUP_FUNC,
.v.func = alc2xx_fixup_headset_mic,
@@ -6900,6 +7142,13 @@ static const struct hda_fixup alc269_fixups[] = {
.type = HDA_FIXUP_FUNC,
.v.func = alc256_fixup_acer_sfg16_micmute_led,
},
+ [ALC256_FIXUP_ACER_HEADSET_MICMUTE_LED] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc256_fixup_acer_sfg16_micmute_led,
+ .chained = true,
+ .chained_before = true,
+ .chain_id = ALC2XX_FIXUP_HEADSET_MIC,
+ },
[ALC256_FIXUP_HEADPHONE_AMP_VOL] = {
.type = HDA_FIXUP_FUNC,
.v.func = alc256_decrease_headphone_amp_val,
@@ -7084,13 +7333,6 @@ static const struct hda_fixup alc269_fixups[] = {
.chained = true,
.chain_id = ALC287_FIXUP_CS35L41_I2C_2,
},
- [ALC236_FIXUP_HP_DMIC] = {
- .type = HDA_FIXUP_PINS,
- .v.pins = (const struct hda_pintbl[]) {
- { 0x12, 0x90a60160 }, /* use as internal mic */
- { }
- },
- },
[ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO] = {
.type = HDA_FIXUP_PINS,
.v.pins = (const struct hda_pintbl[]) {
@@ -7101,6 +7343,20 @@ static const struct hda_fixup alc269_fixups[] = {
{ }
}
},
+ [ALC256_FIXUP_HONOR_DRA_XX_SPEAKERS] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x14, 0x90170111 }, /* tweeters (top speakers) */
+ { 0x19, 0x03a1113c }, /* headset mic, without its own jack detect */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC256_FIXUP_HONOR_DRA_XX_HEADSET_MODE
+ },
+ [ALC256_FIXUP_HONOR_DRA_XX_HEADSET_MODE] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_headset_mode,
+ },
[ALC245_FIXUP_HP_ENVY_X360_15_FH0XXX] = {
.type = HDA_FIXUP_FUNC,
.v.func = cs35l41_fixup_i2c_two,
@@ -7174,6 +7430,21 @@ static const struct hda_fixup alc269_fixups[] = {
{ }
},
},
+ [ALC256_FIXUP_ASUS_SPEAKER_COEFS] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc256_fixup_asus_speaker_coefs,
+ .chained = true,
+ .chain_id = ALC256_FIXUP_ASUS_MIC_NO_PRESENCE,
+ },
+ [ALC245_FIXUP_MINISFORUM_V3_MIC_NO_PRESENCE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ { 0x19, 0x04a19150 }, /* headset mic jack, no presence detect */
+ { }
+ },
+ .chained = true,
+ .chain_id = ALC245_FIXUP_BASS_HP_DAC
+ },
};
static const struct hda_quirk alc269_fixup_tbl[] = {
@@ -7230,7 +7501,13 @@ static const struct hda_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x1025, 0x160e, "Acer PT316-51S", ALC2XX_FIXUP_HEADSET_MIC),
SND_PCI_QUIRK(0x1025, 0x1616, "Acer Aspire A515-57", ALC256_FIXUP_ACER_COLDBOOT),
SND_PCI_QUIRK(0x1025, 0x161f, "Acer S40-54", ALC256_FIXUP_ACER_MIC_NO_PRESENCE),
- SND_PCI_QUIRK(0x1025, 0x1640, "Acer Aspire A315-44P", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED),
+ /*
+ * Shared SSID: A315-44P needs GPIO3 micmute LED; AG15-42P needs
+ * the headset pin remap (BIOS leaves 0x19 as 0x411111f0). One
+ * entry applies both. GPIO3 is unused on AG15.
+ */
+ SND_PCI_QUIRK(0x1025, 0x1640, "Acer Aspire A315-44P / AG15-42P",
+ ALC256_FIXUP_ACER_HEADSET_MICMUTE_LED),
SND_PCI_QUIRK(0x1025, 0x166c, "Acer Predator PH16-71", ALC2XX_FIXUP_HEADSET_MIC),
SND_PCI_QUIRK(0x1025, 0x1679, "Acer Nitro 16 AN16-41", ALC2XX_FIXUP_HEADSET_MIC),
SND_PCI_QUIRK(0x1025, 0x169a, "Acer Swift SFG16", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED),
@@ -7491,6 +7768,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x103c, 0x8812, "HP Spectre x360 15-eb1xxx", ALC285_FIXUP_HP_SPECTRE_X360_EB1),
SND_PCI_QUIRK(0x103c, 0x881d, "HP 250 G8 Notebook PC", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2),
SND_PCI_QUIRK(0x103c, 0x881e, "HP Laptop 15s-du3xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2),
+ SND_PCI_QUIRK(0x103c, 0x8820, "HP ENVY Laptop 13-ba1xxx", ALC245_FIXUP_HP_X360_MUTE_LEDS),
SND_PCI_QUIRK(0x103c, 0x8846, "HP EliteBook 850 G8 Notebook PC", ALC285_FIXUP_HP_GPIO_LED),
SND_PCI_QUIRK(0x103c, 0x8847, "HP EliteBook x360 830 G8 Notebook PC", ALC285_FIXUP_HP_GPIO_LED),
SND_PCI_QUIRK(0x103c, 0x884b, "HP EliteBook 840 Aero G8 Notebook PC", ALC285_FIXUP_HP_GPIO_LED),
@@ -7502,6 +7780,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x103c, 0x8870, "HP ZBook Fury 15.6 Inch G8 Mobile Workstation PC", ALC285_FIXUP_HP_GPIO_AMP_INIT),
SND_PCI_QUIRK(0x103c, 0x8873, "HP ZBook Studio 15.6 Inch G8 Mobile Workstation PC", ALC285_FIXUP_HP_GPIO_AMP_INIT),
SND_PCI_QUIRK(0x103c, 0x887a, "HP Laptop 15s-eq2xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2),
+ SND_PCI_QUIRK(0x103c, 0x89fc, "HP Laptop 15s-eq3xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2),
SND_PCI_QUIRK(0x103c, 0x887c, "HP Laptop 14s-fq1xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2),
SND_PCI_QUIRK(0x103c, 0x888a, "HP ENVY x360 Convertible 15-eu0xxx", ALC245_FIXUP_HP_X360_MUTE_LEDS),
SND_PCI_QUIRK(0x103c, 0x888d, "HP ZBook Power 15.6 inch G8 Mobile Workstation PC", ALC236_FIXUP_HP_GPIO_LED),
@@ -7622,6 +7901,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x103c, 0x8b92, "HP", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED),
SND_PCI_QUIRK(0x103c, 0x8b96, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF),
SND_PCI_QUIRK(0x103c, 0x8b97, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF),
+ SND_PCI_QUIRK(0x103c, 0x8ba4, "HP 245 14 inch G10 Notebook PC",
+ ALC236_FIXUP_HP_MUTE_LED_MICMUTE_COEF_INV),
SND_PCI_QUIRK(0x103c, 0x8ba9, "HP Omen 16-wd0xxx", ALC245_FIXUP_HP_MUTE_LED_V1_COEFBIT),
SND_PCI_QUIRK(0x103c, 0x8bb1, "HP Victus 15-fa1xxx (MB 8BB1)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT),
SND_PCI_QUIRK(0x103c, 0x8bb3, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2),
@@ -7643,7 +7924,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x103c, 0x8be5, "HP Envy 16", ALC287_FIXUP_CS35L41_I2C_2),
SND_PCI_QUIRK(0x103c, 0x8be6, "HP Envy 16", ALC287_FIXUP_CS35L41_I2C_2),
SND_PCI_QUIRK(0x103c, 0x8be7, "HP Envy 17", ALC287_FIXUP_CS35L41_I2C_2),
- SND_PCI_QUIRK(0x103c, 0x8be8, "HP Envy 17", ALC287_FIXUP_CS35L41_I2C_2),
+ SND_PCI_QUIRK(0x103c, 0x8be8, "HP Envy 17", ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS),
SND_PCI_QUIRK(0x103c, 0x8be9, "HP Envy x360 2-in-1 Laptop 15-fh0xxx", ALC245_FIXUP_HP_ENVY_X360_15_FH0XXX),
SND_PCI_QUIRK(0x103c, 0x8bf0, "HP", ALC236_FIXUP_HP_GPIO_LED),
SND_PCI_QUIRK(0x103c, 0x8bfd, "HP Pavilion 15-eg3xxx", ALC287_FIXUP_HP_GPIO_LED),
@@ -7722,12 +8003,12 @@ static const struct hda_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x103c, 0x8d9c, "HP 17 Turbine OmniBook 7 DIS", ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS),
SND_PCI_QUIRK(0x103c, 0x8d9d, "HP 17 Turbine OmniBook X UMA", ALC287_FIXUP_CS35L41_I2C_2),
SND_PCI_QUIRK(0x103c, 0x8d9e, "HP 17 Turbine OmniBook X DIS", ALC287_FIXUP_CS35L41_I2C_2),
- SND_PCI_QUIRK(0x103c, 0x8d9f, "HP 14 Cadet (x360)", ALC287_FIXUP_CS35L41_I2C_2),
+ SND_PCI_QUIRK(0x103c, 0x8d9f, "HP 14 Cadet (x360)", ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS),
SND_PCI_QUIRK(0x103c, 0x8da0, "HP 16 Clipper OmniBook 7(X360)", ALC287_FIXUP_CS35L41_I2C_2),
SND_PCI_QUIRK(0x103c, 0x8da1, "HP 16 Clipper OmniBook X", ALC287_FIXUP_CS35L41_I2C_2),
SND_PCI_QUIRK(0x103c, 0x8da7, "HP 14 Enstrom OmniBook X", ALC287_FIXUP_CS35L41_I2C_2),
SND_PCI_QUIRK(0x103c, 0x8da8, "HP 16 Piston OmniBook X", ALC245_FIXUP_HP_ENVY_X360_15_FH0XXX),
- SND_PCI_QUIRK(0x103c, 0x8dc9, "HP Laptop 15-fc0xxx", ALC236_FIXUP_HP_DMIC),
+ SND_PCI_QUIRK(0x103c, 0x8dc9, "HP Laptop 15-fc0xxx", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO),
SND_PCI_QUIRK(0x103c, 0x8dd4, "HP EliteStudio 8 AIO", ALC274_FIXUP_HP_AIO_BIND_DACS),
SND_PCI_QUIRK(0x103c, 0x8dd7, "HP Laptop 15-fd0xxx", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO),
SND_PCI_QUIRK(0x103c, 0x8de8, "HP Gemtree", ALC245_FIXUP_TAS2781_SPI_2),
@@ -7820,7 +8101,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x1043, 0x10a1, "ASUS UX391UA", ALC294_FIXUP_ASUS_SPK),
SND_PCI_QUIRK(0x1043, 0x10a4, "ASUS TP3407SA", ALC287_FIXUP_TAS2781_I2C),
SND_PCI_QUIRK(0x1043, 0x10c0, "ASUS X540SA", ALC256_FIXUP_ASUS_MIC),
- SND_PCI_QUIRK(0x1043, 0x10c4, "ASUS Vivobook S14 S5406SA", ALC2XX_FIXUP_HEADSET_MIC),
+ SND_PCI_QUIRK(0x1043, 0x10c4, "ASUS S5406SA/Q423SA", ALC294_FIXUP_ASUS_Q423SA),
SND_PCI_QUIRK(0x1043, 0x10d0, "ASUS X540LA/X540LJ", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE),
SND_PCI_QUIRK(0x1043, 0x10d3, "ASUS K6500ZC", ALC294_FIXUP_ASUS_SPK),
SND_PCI_QUIRK(0x1043, 0x1154, "ASUS TP3607SH", ALC287_FIXUP_TAS2781_I2C),
@@ -7867,7 +8148,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x1043, 0x1663, "ASUS GU603ZI/ZJ/ZQ/ZU/ZV", ALC285_FIXUP_ASUS_HEADSET_MIC),
SND_PCI_QUIRK(0x1043, 0x1664, "ASUS ExpertBook PM3406CHA", ALC256_FIXUP_ASUS_PM3406CHA),
SND_PCI_QUIRK(0x1043, 0x1683, "ASUS UM3402YAR", ALC287_FIXUP_CS35L41_I2C_2),
- SND_PCI_QUIRK(0x1043, 0x16a3, "ASUS UX3402VA", ALC245_FIXUP_CS35L41_SPI_2),
+ SND_PCI_QUIRK(0x1043, 0x16a3, "ASUS UX3402VA", ALC294_FIXUP_ASUS_SPI_HEADSET_MIC),
SND_PCI_QUIRK(0x1043, 0x16b2, "ASUS GU603", ALC289_FIXUP_ASUS_GA401),
SND_PCI_QUIRK(0x1043, 0x16d3, "ASUS UX5304VA", ALC245_FIXUP_CS35L41_SPI_2),
SND_PCI_QUIRK(0x1043, 0x16e3, "ASUS UX50", ALC269_FIXUP_STEREO_DMIC),
@@ -7950,6 +8231,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x1043, 0x31e1, "ASUS B5605CCA", ALC294_FIXUP_ASUS_CS35L41_SPI_2),
SND_PCI_QUIRK(0x1043, 0x31f1, "ASUS B3605CCA", ALC294_FIXUP_ASUS_CS35L41_SPI_2),
SND_PCI_QUIRK(0x1043, 0x3391, "ASUS PM3606CKA", ALC287_FIXUP_CS35L41_I2C_2),
+ SND_PCI_QUIRK(0x1043, 0x3500, "ASUS E5402WHA", ALC256_FIXUP_ASUS_AIO_GPIO2_LOW),
+ SND_PCI_QUIRK(0x1043, 0x3501, "ASUS PM3606CHA", ALC256_FIXUP_ASUS_SPEAKER_COEFS),
SND_PCI_QUIRK(0x1043, 0x3601, "ASUS PM5406CGA", ALC287_FIXUP_CS35L41_I2C_2),
SND_PCI_QUIRK(0x1043, 0x3611, "ASUS PM5606CGA", ALC287_FIXUP_CS35L41_I2C_2),
SND_PCI_QUIRK(0x1043, 0x3701, "ASUS P5406CCA", ALC245_FIXUP_CS35L41_SPI_2),
@@ -8031,6 +8314,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x144d, 0xc1cc, "Samsung Galaxy Book3 Ultra (NT960XFH)", ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS),
SND_PCI_QUIRK(0x1458, 0x900e, "Gigabyte G5 KF5 (2023)", ALC2XX_FIXUP_HEADSET_MIC),
SND_PCI_QUIRK(0x1458, 0xfa53, "Gigabyte BXBT-2807", ALC283_FIXUP_HEADSET_MIC),
+ SND_PCI_QUIRK(0x1462, 0x12fb, "MSI GE66 Raider 11UE",
+ ALC298_FIXUP_MSI_GE66_ALC1316_AMP),
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),
@@ -8206,7 +8491,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = {
HDA_CODEC_QUIRK(0x17aa, 0x38d5, "Lenovo Yoga Pro 9 16IMH9", ALC287_FIXUP_TAS2781_I2C),
HDA_CODEC_QUIRK(0x17aa, 0x38d6, "Lenovo Yoga Pro 9 16IMH9", ALC287_FIXUP_TAS2781_I2C),
HDA_CODEC_QUIRK(0x17aa, 0x3855, "Legion 7 16ITHG6", ALC287_FIXUP_LEGION_16ITHG6),
- SND_PCI_QUIRK(0x17aa, 0x3811, "Legion S7 15IMH05", ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS),
+ SND_PCI_QUIRK(0x17aa, 0x3811, "Legion S7 15IMH05", ALC287_FIXUP_LEGION_S7_15IMH05_AMP),
SND_PCI_QUIRK(0x17aa, 0x3813, "Legion 7i 15IMHG05", ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS),
SND_PCI_QUIRK(0x17aa, 0x3818, "Lenovo C940 / Yoga Duet 7", ALC298_FIXUP_LENOVO_C940_DUET7),
SND_PCI_QUIRK(0x17aa, 0x3819, "Lenovo 13s Gen2 ITL", ALC287_FIXUP_13S_GEN2_SPEAKERS),
@@ -8216,6 +8501,12 @@ static const struct hda_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x17aa, 0x3827, "Ideapad S740", ALC285_FIXUP_IDEAPAD_S740_COEF),
SND_PCI_QUIRK(0x17aa, 0x3834, "Lenovo IdeaPad Slim 9i 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS),
SND_PCI_QUIRK(0x17aa, 0x383d, "Legion Y9000X 2019", ALC285_FIXUP_LEGION_Y9000X_SPEAKERS),
+ SND_PCI_QUIRK(0x17aa, 0x3842, "ThinkBook X G2 IAH", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_4_THINKPAD),
+ /* IdeaPad Pro 5 14IAH10 shares PCI SSID 17aa:3843 with Yoga Book 9i;
+ * use codec SSID to distinguish them
+ */
+ HDA_CODEC_QUIRK(0x17aa, 0x3914, "Lenovo IdeaPad Pro 5 14IAH10",
+ ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP_LED),
SND_PCI_QUIRK(0x17aa, 0x3843, "Lenovo Yoga 9i / Yoga Book 9i", ALC287_FIXUP_LENOVO_YOGA_BOOK_9I),
/* Yoga Pro 7 14IMH9 shares PCI SSID 17aa:3847 with Legion 7 16ACHG6;
* use codec SSID to distinguish them
@@ -8230,6 +8521,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x17aa, 0x3852, "Lenovo Yoga 7 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS),
SND_PCI_QUIRK(0x17aa, 0x3853, "Lenovo Yoga 7 15ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS),
SND_PCI_QUIRK(0x17aa, 0x3855, "Legion 7 16ITHG6", ALC287_FIXUP_LEGION_16ITHG6),
+ HDA_CODEC_QUIRK(0x17aa, 0x3856, "Lenovo Yoga Slim 7 Carbon 14ACN6", ALC287_FIXUP_YOGA_SLIM7_CARBON_SPEAKERS),
SND_PCI_QUIRK(0x17aa, 0x3862, "Lenovo IdeaPad Slim 3 15ABR8", ALC269_FIXUP_LIMIT_INT_MIC_BOOST),
SND_PCI_QUIRK(0x17aa, 0x3865, "Lenovo 13X", ALC287_FIXUP_CS35L41_I2C_2),
SND_PCI_QUIRK(0x17aa, 0x3866, "Lenovo 13X", ALC287_FIXUP_CS35L41_I2C_2),
@@ -8243,6 +8535,10 @@ static const struct hda_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x17aa, 0x3870, "Lenovo Yoga 7 14ARB7", ALC287_FIXUP_YOGA7_14ARB7_I2C),
SND_PCI_QUIRK(0x17aa, 0x3871, "Lenovo ThinkBook 16p Gen 3 ARH", ALC287_FIXUP_YOGA7_14ARB7_I2C),
SND_PCI_QUIRK(0x17aa, 0x3874, "Legion 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2),
+ /* Lenovo ThinkBook Plus G3 IAP shares PCI SSID 17aa:3891 with Lenovo Yoga
+ * Pro 7 14AHP9; use codec SSID to distinguish them.
+ */
+ HDA_CODEC_QUIRK(0x17aa, 0x3875, "ThinkBook Plus G3 IAP", ALC287_FIXUP_CS35L41_I2C_2),
SND_PCI_QUIRK(0x17aa, 0x3877, "Lenovo Legion 7 Slim 16ARHA7", ALC287_FIXUP_CS35L41_I2C_2),
SND_PCI_QUIRK(0x17aa, 0x3878, "Lenovo Legion 7 Slim 16ARHA7", ALC287_FIXUP_CS35L41_I2C_2),
SND_PCI_QUIRK(0x17aa, 0x387d, "Yoga S780-16 pro Quad AAC", ALC287_FIXUP_TAS2781_I2C),
@@ -8309,7 +8605,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = {
HDA_CODEC_QUIRK(0x17aa, 0x3907, "Legion Pro 7i 16IAX10H / Y9000P IAX10", ALC287_FIXUP_LENOVO_LEGION_AW88399),
SND_PCI_QUIRK(0x17aa, 0x390d, "Lenovo Yoga Pro 7 14ASP10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN_HEADSET),
SND_PCI_QUIRK(0x17aa, 0x3911, "Lenovo Yoga Pro 7 14IAH10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN),
- SND_PCI_QUIRK(0x17aa, 0x3912, "Lenovo Xiaoxin 14 GT", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN),
+ SND_PCI_QUIRK(0x17aa, 0x3912, "Lenovo Xiaoxin 14 GT", ALC287_FIXUP_LENOVO_XIAOXIN_14GT_HP_NOISE),
SND_PCI_QUIRK(0x17aa, 0x3913, "Lenovo 145", ALC236_FIXUP_LENOVO_INV_DMIC),
SND_PCI_QUIRK(0x17aa, 0x391f, "Yoga S990-16 pro Quad YC Quad", ALC287_FIXUP_TXNW2781_I2C),
SND_PCI_QUIRK(0x17aa, 0x3920, "Yoga S990-16 pro Quad VECO Quad", ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1),
@@ -8359,6 +8655,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x19e5, 0x3204, "Huawei MACH-WX9", ALC256_FIXUP_HUAWEI_MACH_WX9_PINS),
SND_PCI_QUIRK(0x19e5, 0x320f, "Huawei WRT-WX9 ", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE),
SND_PCI_QUIRK(0x19e5, 0x3212, "Huawei KLV-WX9 ", ALC256_FIXUP_ACER_HEADSET_MIC),
+ SND_PCI_QUIRK(0x19e5, 0x3e13, "Huawei MACHR-WX9", ALC256_FIXUP_HUAWEI_MACHR_WX9_PINS),
SND_PCI_QUIRK(0x1a58, 0x2023, "Razer Blade 16 (2025)",
ALC298_FIXUP_RAZER_BLADE16_2025_PINS),
SND_PCI_QUIRK(0x1a58, 0x300e, "Razer Blade 16 (2025)",
@@ -8400,11 +8697,13 @@ static const struct hda_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x1e50, 0x7007, "Positivo DN50E", ALC269_FIXUP_LIMIT_INT_MIC_BOOST),
SND_PCI_QUIRK(0x1e50, 0x7036, "Acer Gadget E10 ETBook", ALC233_FIXUP_WUJIE_SPEAKERS),
SND_PCI_QUIRK(0x1e50, 0x7038, "Positivo DN140", ALC269_FIXUP_LIMIT_INT_MIC_BOOST),
+ SND_PCI_QUIRK(0x1ee7, 0x204e, "HONOR DRA-XX M1020", ALC256_FIXUP_HONOR_DRA_XX_SPEAKERS),
SND_PCI_QUIRK(0x1ee7, 0x2078, "HONOR BRB-X M1010", ALC2XX_FIXUP_HEADSET_MIC),
SND_PCI_QUIRK(0x1ee7, 0x2081, "HONOR MRB-XXX M1020", ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO),
SND_PCI_QUIRK(0x1f4c, 0xb020, "Minisforum AI X1 Pro",
ALC245_FIXUP_MINISFORUM_JACK_DETECT),
- SND_PCI_QUIRK(0x1f4c, 0xe001, "Minisforum V3 (SE)", ALC245_FIXUP_BASS_HP_DAC),
+ SND_PCI_QUIRK(0x1f4c, 0xe001, "Minisforum V3 (SE)",
+ ALC245_FIXUP_MINISFORUM_V3_MIC_NO_PRESENCE),
SND_PCI_QUIRK(0x1f66, 0x0105, "Ayaneo Portable Game Player", ALC287_FIXUP_CS35L41_I2C_2),
SND_PCI_QUIRK(0x2014, 0x800a, "Positivo ARN50", ALC269_FIXUP_LIMIT_INT_MIC_BOOST),
SND_PCI_QUIRK(0x2039, 0x0001, "Inspur S14-G1", ALC295_FIXUP_CHROME_BOOK),
@@ -8419,6 +8718,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x2782, 0x1701, "Infinix Y4 Max", ALC269VC_FIXUP_INFINIX_Y4_MAX),
SND_PCI_QUIRK(0x2782, 0x1705, "MEDION E15433", ALC269VC_FIXUP_INFINIX_Y4_MAX),
SND_PCI_QUIRK(0x2782, 0x1707, "Vaio VJFE-ADL", ALC298_FIXUP_SPK_VOLUME),
+ SND_PCI_QUIRK(0x2782, 0x1718, "Acer Aspire AL16-31P", ALC269VC_FIXUP_INFINIX_Y4_MAX),
SND_PCI_QUIRK(0x2782, 0x4900, "MEDION E15443", ALC233_FIXUP_MEDION_MTL_SPK),
SND_PCI_QUIRK(0x2782, 0xa128, "Positivo N15RPE-S", ALC269_FIXUP_LIMIT_INT_MIC_BOOST),
SND_PCI_QUIRK(0x2782, 0xa212, "Lunnen Ground 14", ALC269VC_FIXUP_LUNNEN_GROUND_14),
@@ -8616,6 +8916,7 @@ static const struct hda_model_fixup alc269_fixup_models[] = {
{.id = ALC298_FIXUP_HUAWEI_MBX_STEREO, .name = "huawei-mbx-stereo"},
{.id = ALC256_FIXUP_MEDION_HEADSET_NO_PRESENCE, .name = "alc256-medion-headset"},
{.id = ALC298_FIXUP_SAMSUNG_AMP, .name = "alc298-samsung-amp"},
+ {.id = ALC298_FIXUP_MSI_GE66_ALC1316_AMP, .name = "alc298-alc1316-amp"},
{.id = ALC298_FIXUP_SAMSUNG_AMP_V2_2_AMPS, .name = "alc298-samsung-amp-v2-2-amps"},
{.id = ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS, .name = "alc298-samsung-amp-v2-4-amps"},
{.id = ALC256_FIXUP_SAMSUNG_HEADPHONE_VERY_QUIET, .name = "alc256-samsung-headphone"},
diff --git a/sound/hda/codecs/realtek/alc662.c b/sound/hda/codecs/realtek/alc662.c
index eff7c83b9c59..c1c7277b6754 100644
--- a/sound/hda/codecs/realtek/alc662.c
+++ b/sound/hda/codecs/realtek/alc662.c
@@ -198,6 +198,24 @@ static void alc897_fixup_lenovo_headset_mode(struct hda_codec *codec,
}
}
+static void alc897_fixup_limit_linein_boost(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action != HDA_FIXUP_ACT_PROBE)
+ return;
+
+ snd_hda_override_amp_caps(codec, 0x1a, HDA_INPUT,
+ (0x00 << AC_AMPCAP_OFFSET_SHIFT) |
+ (0x01 << AC_AMPCAP_NUM_STEPS_SHIFT) |
+ (0x2f << AC_AMPCAP_STEP_SIZE_SHIFT) |
+ (0 << AC_AMPCAP_MUTE_SHIFT));
+ snd_hda_override_amp_caps(codec, 0x08, HDA_INPUT,
+ (0x00 << AC_AMPCAP_OFFSET_SHIFT) |
+ (0x01 << AC_AMPCAP_NUM_STEPS_SHIFT) |
+ (0x02 << AC_AMPCAP_STEP_SIZE_SHIFT) |
+ (0 << AC_AMPCAP_MUTE_SHIFT));
+}
+
static const struct coef_fw alc668_coefs[] = {
WRITE_COEF(0x01, 0xbebe), WRITE_COEF(0x02, 0xaaaa), WRITE_COEF(0x03, 0x0),
WRITE_COEF(0x04, 0x0180), WRITE_COEF(0x06, 0x0), WRITE_COEF(0x07, 0x0f80),
@@ -326,6 +344,7 @@ enum {
ALC897_FIXUP_HEADSET_MIC_PIN3,
ALC662_FIXUP_CSL_GPIO,
ALC897_FIXUP_BEELINK_SER6_AMP,
+ ALC897_FIXUP_LIMIT_LINEIN_BOOST,
};
static const struct hda_fixup alc662_fixups[] = {
@@ -787,6 +806,10 @@ static const struct hda_fixup alc662_fixups[] = {
.type = HDA_FIXUP_FUNC,
.v.func = alc_fixup_gpio4,
},
+ [ALC897_FIXUP_LIMIT_LINEIN_BOOST] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc897_fixup_limit_linein_boost,
+ },
};
static const struct hda_quirk alc662_fixup_tbl[] = {
@@ -857,6 +880,7 @@ static const struct hda_quirk alc662_fixup_tbl[] = {
SND_PCI_QUIRK(0x1b35, 0x1234, "CZC ET26", ALC662_FIXUP_CZC_ET26),
SND_PCI_QUIRK(0x1b35, 0x2206, "CZC P10T", ALC662_FIXUP_CZC_P10T),
SND_PCI_QUIRK(0x1c6c, 0x1239, "Compaq N14JP6-V2", ALC897_FIXUP_HP_HSMIC_VERB),
+ SND_PCI_QUIRK(0x1e0b, 0x1297, "DECENTA MATX03", ALC897_FIXUP_LIMIT_LINEIN_BOOST),
SND_PCI_QUIRK(0x1e63, 0x6d9a, "F+ FLAPTOP r", ALC897_FIXUP_HP_HSMIC_VERB),
SND_PCI_QUIRK(0x1f66, 0x0202, "Beelink SER6 Max 6900", ALC897_FIXUP_BEELINK_SER6_AMP),
diff --git a/sound/hda/codecs/realtek/alc882.c b/sound/hda/codecs/realtek/alc882.c
index fd466b6985f0..800c3d6f5134 100644
--- a/sound/hda/codecs/realtek/alc882.c
+++ b/sound/hda/codecs/realtek/alc882.c
@@ -372,7 +372,7 @@ static const struct hda_fixup alc882_fixups[] = {
[ALC883_FIXUP_ACER_EAPD] = {
.type = HDA_FIXUP_VERBS,
.v.verbs = (const struct hda_verb[]) {
- /* eanable EAPD on Acer laptops */
+ /* enable EAPD on Acer laptops */
{ 0x20, AC_VERB_SET_COEF_INDEX, 0x07 },
{ 0x20, AC_VERB_SET_PROC_COEF, 0x3050 },
{ }
diff --git a/sound/hda/codecs/side-codecs/Kconfig b/sound/hda/codecs/side-codecs/Kconfig
index f90f1dfcec68..b7e2a6fa4258 100644
--- a/sound/hda/codecs/side-codecs/Kconfig
+++ b/sound/hda/codecs/side-codecs/Kconfig
@@ -22,6 +22,7 @@ config SND_HDA_SCODEC_AW88399_I2C
depends on I2C
depends on ACPI
depends on SND_SOC
+ imply SERIAL_MULTI_INSTANTIATE
select SND_HDA_SCODEC_AW88399
select SND_SOC_AW88399_LIB
help
diff --git a/sound/hda/codecs/side-codecs/cs35l41_hda.c b/sound/hda/codecs/side-codecs/cs35l41_hda.c
index 62296be45c55..d50c3d8f5303 100644
--- a/sound/hda/codecs/side-codecs/cs35l41_hda.c
+++ b/sound/hda/codecs/side-codecs/cs35l41_hda.c
@@ -1907,7 +1907,6 @@ err:
hw_cfg->valid = false;
hw_cfg->gpio1.valid = false;
hw_cfg->gpio2.valid = false;
- acpi_dev_put(cs35l41->dacpi);
return ret;
}
@@ -1928,10 +1927,8 @@ static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, i
cs35l41->dacpi = adev;
struct device *physdev __free(put_device) =
get_device(acpi_get_first_physical_node(adev));
- if (!physdev) {
- acpi_dev_put(adev);
+ if (!physdev)
return -ENODEV;
- }
sub = acpi_get_subsystem_id(ACPI_HANDLE(physdev));
if (IS_ERR(sub))
@@ -1986,8 +1983,10 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i
dev_set_drvdata(dev, cs35l41);
ret = cs35l41_hda_read_acpi(cs35l41, device_name, id);
- if (ret)
- return dev_err_probe(cs35l41->dev, ret, "Platform not supported\n");
+ if (ret) {
+ dev_err_probe(cs35l41->dev, ret, "Platform not supported\n");
+ goto err_acpi;
+ }
if (IS_ERR(cs35l41->reset_gpio)) {
ret = PTR_ERR(cs35l41->reset_gpio);
@@ -2074,6 +2073,7 @@ err_pm:
err:
if (cs35l41_safe_reset(cs35l41->regmap, cs35l41->hw_cfg.bst_type))
gpiod_set_value_cansleep(cs35l41->reset_gpio, 0);
+err_acpi:
gpiod_put(cs35l41->reset_gpio);
gpiod_put(cs35l41->cs_gpio);
acpi_dev_put(cs35l41->dacpi);
diff --git a/sound/hda/codecs/side-codecs/cs35l41_hda_i2c.c b/sound/hda/codecs/side-codecs/cs35l41_hda_i2c.c
index fdf406e92fca..59b82238f2b8 100644
--- a/sound/hda/codecs/side-codecs/cs35l41_hda_i2c.c
+++ b/sound/hda/codecs/side-codecs/cs35l41_hda_i2c.c
@@ -23,6 +23,8 @@ static int cs35l41_hda_i2c_probe(struct i2c_client *clt)
device_name = "CLSA0100";
else if (strstr(dev_name(&clt->dev), "CLSA0101"))
device_name = "CLSA0101";
+ else if (strstr(dev_name(&clt->dev), "CLSA0102"))
+ device_name = "CLSA0102";
else if (strstr(dev_name(&clt->dev), "CSC3551"))
device_name = "CSC3551";
else
@@ -45,6 +47,7 @@ static const struct i2c_device_id cs35l41_hda_i2c_id[] = {
static const struct acpi_device_id cs35l41_acpi_hda_match[] = {
{"CLSA0100", 0 },
{"CLSA0101", 0 },
+ {"CLSA0102", 0 },
{"CSC3551", 0 },
{}
};
diff --git a/sound/hda/codecs/side-codecs/cs35l41_hda_property.c b/sound/hda/codecs/side-codecs/cs35l41_hda_property.c
index 416d7bf3e289..c50c7e689533 100644
--- a/sound/hda/codecs/side-codecs/cs35l41_hda_property.c
+++ b/sound/hda/codecs/side-codecs/cs35l41_hda_property.c
@@ -85,6 +85,8 @@ static const struct cs35l41_config cs35l41_config_table[] = {
{ "103C8C51", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4100, 24 },
{ "103C8CDD", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4100, 24 },
{ "103C8CDE", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 3900, 24 },
+ { "103C8E60", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4500, 24 },
+ { "103C8F07", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 3900, 24 },
{ "104312AF", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 1, 2, 0, 1000, 4500, 24 },
{ "10431433", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4500, 24 },
{ "10431463", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4500, 24 },
@@ -126,6 +128,8 @@ static const struct cs35l41_config cs35l41_config_table[] = {
{ "10433A60", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 1, 2, 0, 1000, 4500, 24 },
{ "17AA3865", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, -1, -1, 0, 0, 0 },
{ "17AA3866", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, -1, -1, 0, 0, 0 },
+ { "17AA3845", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, -1, -1, 0, 0, 0 },
+ { "17AA3846", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, -1, -1, 0, 0, 0 },
{ "17AA386E", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 2, -1, 0, 0, 0 },
{ "17AA386F", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, -1, -1, 0, 0, 0 },
{ "17AA3874", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, -1, -1, 0, 0, 0 },
@@ -439,6 +443,33 @@ static int lenovo_legion_no_acpi(struct cs35l41_hda *cs35l41, struct device *phy
return 0;
}
+/*
+ * Device CLSA0102 (Lenovo Yoga Slim 7 Carbon 14ACN6) has the same ACPI layout as CLSA010(0/1):
+ * no _DSD, both amps in one node, reset on GPIO index 0. It has no speaker ID GPIO, the
+ * interrupt GPIO sits at index 2. The speakers use an external VSPK supply switched by GPIO1
+ * (the Windows driver sets ExternalVspkControl and keeps the boost converter off).
+ */
+static int lenovo_yoga_slim7_carbon_no_acpi(struct cs35l41_hda *cs35l41, struct device *physdev,
+ int id, const char *hid)
+{
+ struct cs35l41_hw_cfg *hw_cfg = &cs35l41->hw_cfg;
+
+ /* check I2C address to assign the index */
+ cs35l41->index = id == 0x40 ? 0 : 1;
+ cs35l41->channel_index = 0;
+ cs35l41->reset_gpio = gpiod_get_index(physdev, NULL, 0, GPIOD_OUT_HIGH);
+ cs35l41->speaker_id = -ENOENT;
+ hw_cfg->spk_pos = cs35l41->index;
+ hw_cfg->bst_type = CS35L41_EXT_BOOST;
+ hw_cfg->gpio1.func = CS35l41_VSPK_SWITCH;
+ hw_cfg->gpio1.valid = true;
+ hw_cfg->gpio2.func = CS35L41_INTERRUPT;
+ hw_cfg->gpio2.valid = true;
+ hw_cfg->valid = true;
+
+ return 0;
+}
+
static int missing_speaker_id_gpio2(struct cs35l41_hda *cs35l41, struct device *physdev, int id,
const char *hid)
{
@@ -463,6 +494,7 @@ struct cs35l41_prop_model {
static const struct cs35l41_prop_model cs35l41_prop_model_table[] = {
{ "CLSA0100", NULL, lenovo_legion_no_acpi },
{ "CLSA0101", NULL, lenovo_legion_no_acpi },
+ { "CLSA0102", NULL, lenovo_yoga_slim7_carbon_no_acpi },
{ "CSC3551", "10251826", generic_dsd_config },
{ "CSC3551", "1025182C", generic_dsd_config },
{ "CSC3551", "10251844", generic_dsd_config },
@@ -511,6 +543,8 @@ static const struct cs35l41_prop_model cs35l41_prop_model_table[] = {
{ "CSC3551", "103C8C6A", hp_i2c_int_2amp_dual_spkid },
{ "CSC3551", "103C8CDD", generic_dsd_config },
{ "CSC3551", "103C8CDE", generic_dsd_config },
+ { "CSC3551", "103C8E60", generic_dsd_config },
+ { "CSC3551", "103C8F07", generic_dsd_config },
{ "CSC3551", "104312AF", generic_dsd_config },
{ "CSC3551", "10431433", generic_dsd_config },
{ "CSC3551", "10431463", generic_dsd_config },
@@ -551,6 +585,8 @@ static const struct cs35l41_prop_model cs35l41_prop_model_table[] = {
{ "CSC3551", "10433A40", generic_dsd_config },
{ "CSC3551", "10433A50", generic_dsd_config },
{ "CSC3551", "10433A60", generic_dsd_config },
+ { "CSC3551", "17AA3845", generic_dsd_config },
+ { "CSC3551", "17AA3846", generic_dsd_config },
{ "CSC3551", "17AA3865", generic_dsd_config },
{ "CSC3551", "17AA3866", generic_dsd_config },
{ "CSC3551", "17AA386E", generic_dsd_config },
diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c
index 5e5f46c9fb6b..44855f593daf 100644
--- a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c
+++ b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c
@@ -335,10 +335,11 @@ static int tas2563_save_calibration(struct tas2781_hda *h)
}
cd->cali_dat_sz_per_dev = TAS2563_CAL_DATA_SIZE * TASDEV_CALIB_N;
+ cd->total_sz = 0;
/* extra byte for each device is the device number */
- cd->total_sz = (cd->cali_dat_sz_per_dev + 1) * p->ndev;
- data = cd->data = devm_kzalloc(p->dev, cd->total_sz,
+ data = cd->data = devm_kzalloc(p->dev,
+ (cd->cali_dat_sz_per_dev + 1) * p->ndev,
GFP_KERNEL);
if (!data)
return -ENOMEM;
@@ -352,7 +353,6 @@ static int tas2563_save_calibration(struct tas2781_hda *h)
if (ret < 0 || ret >= sizeof(var8) - 1) {
dev_err(p->dev, "%s: Read %s failed\n",
__func__, var8);
- cd->total_sz = 0;
return -EINVAL;
}
/*
@@ -370,7 +370,11 @@ static int tas2563_save_calibration(struct tas2781_hda *h)
dev_warn(p->dev,
"Dev %d: Caldat[%d] read failed %ld\n",
i, j, status);
- cd->total_sz = 0;
+ return -EINVAL;
+ }
+ if (*(uint32_t *)&data[offset] == 0) {
+ dev_warn(p->dev,
+ "Dev %d: Caldat[%d] is zero\n", i, j);
return -EINVAL;
}
bedata = cpu_to_be32(*(uint32_t *)&data[offset]);
@@ -379,12 +383,7 @@ static int tas2563_save_calibration(struct tas2781_hda *h)
}
}
- if (cd->total_sz != offset) {
- dev_err(p->dev, "%s: tot_size(%lu) and offset(%u) mismatch\n",
- __func__, cd->total_sz, offset);
- cd->total_sz = 0;
- return -EINVAL;
- }
+ cd->total_sz = offset;
r->r0_reg = TAS2563_CAL_R0;
r->invr0_reg = TAS2563_CAL_INVR0;
diff --git a/sound/hda/codecs/sigmatel.c b/sound/hda/codecs/sigmatel.c
index ee3bd21adc36..9a8ba43667f7 100644
--- a/sound/hda/codecs/sigmatel.c
+++ b/sound/hda/codecs/sigmatel.c
@@ -361,7 +361,7 @@ static unsigned int stac_vref_led_power_filter(struct hda_codec *codec,
return snd_hda_gen_path_power_filter(codec, nid, power_state);
}
-/* update mute-LED accoring to the master switch */
+/* update mute-LED according to the master switch */
static void stac_update_led_status(struct hda_codec *codec, bool muted)
{
struct sigmatel_spec *spec = codec->spec;
@@ -1710,7 +1710,7 @@ static const struct hda_pintbl ref92hd73xx_pin_configs[] = {
{ 0x11, 0x01014030 },
// CD in
{ 0x12, 0x02319040 },
- // Digial Mic ins
+ // Digital Mic ins
{ 0x13, 0x90a000f0 },
{ 0x14, 0x90a000f0 },
// Digital outs
@@ -3817,7 +3817,7 @@ static const struct hda_pintbl dell_3st_pin_configs[] = {
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 */
+ /* no jack detection for ref-no-jd model */
if (action == HDA_FIXUP_ACT_PRE_PROBE)
codec->no_jack_detect = 1;
}
diff --git a/sound/hda/codecs/via.c b/sound/hda/codecs/via.c
index 807312cffd35..0641fc14b346 100644
--- a/sound/hda/codecs/via.c
+++ b/sound/hda/codecs/via.c
@@ -933,6 +933,7 @@ static const struct hda_fixup via_fixups[] = {
static const struct hda_quirk vt2002p_fixups[] = {
SND_PCI_QUIRK(0x1043, 0x13f7, "Asus B23E", VIA_FIXUP_POWER_SAVE),
SND_PCI_QUIRK(0x1043, 0x1487, "Asus G75", VIA_FIXUP_ASUS_G75),
+ SND_PCI_QUIRK(0x1043, 0x1c33, "Asus X55A", VIA_FIXUP_POWER_SAVE),
SND_PCI_QUIRK(0x1043, 0x8532, "Asus X202E", VIA_FIXUP_INTMIC_BOOST),
SND_PCI_QUIRK_VENDOR(0x1558, "Clevo", VIA_FIXUP_POWER_SAVE),
{}
diff --git a/sound/hda/common/bind.c b/sound/hda/common/bind.c
index 6a728a773556..957feace0f58 100644
--- a/sound/hda/common/bind.c
+++ b/sound/hda/common/bind.c
@@ -89,8 +89,12 @@ static int hda_codec_driver_probe(struct device *dev)
struct hda_codec *codec = dev_to_hda_codec(dev);
struct module *owner = dev->driver->owner;
struct hda_codec_driver *driver = hda_codec_to_driver(codec);
+ bool probed = false;
int err;
+ /* unsol events are still blocked until registered */
+ codec->core.unsol_disabled = false;
+
if (codec->bus->core.ext_ops) {
if (WARN_ON(!codec->bus->core.ext_ops->hdev_attach))
return -EINVAL;
@@ -119,7 +123,8 @@ static int hda_codec_driver_probe(struct device *dev)
err = driver->ops->probe(codec, codec->preset);
if (err < 0)
- goto error_module_put;
+ goto error_module;
+ probed = true;
err = snd_hda_codec_build_pcms(codec);
if (err < 0)
goto error_module;
@@ -138,7 +143,9 @@ static int hda_codec_driver_probe(struct device *dev)
return 0;
error_module:
- if (driver->ops->remove)
+ snd_hdac_device_disable_unsol(&codec->core);
+ cancel_delayed_work_sync(&codec->jackpoll_work);
+ if (probed && driver->ops->remove)
driver->ops->remove(codec);
error_module_put:
module_put(owner);
@@ -154,6 +161,10 @@ static int hda_codec_driver_remove(struct device *dev)
struct hda_codec *codec = dev_to_hda_codec(dev);
struct hda_codec_driver *driver = hda_codec_to_driver(codec);
+ /* stop asynchronous jack handling before freeing driver resources */
+ snd_hdac_device_disable_unsol(&codec->core);
+ cancel_delayed_work_sync(&codec->jackpoll_work);
+
if (codec->bus->core.ext_ops) {
if (WARN_ON(!codec->bus->core.ext_ops->hdev_detach))
return -EINVAL;
diff --git a/sound/hda/common/codec.c b/sound/hda/common/codec.c
index 5d9b53bd64ea..31e5fad78b0f 100644
--- a/sound/hda/common/codec.c
+++ b/sound/hda/common/codec.c
@@ -757,11 +757,15 @@ static void codec_release_pcms(struct hda_codec *codec)
*/
void snd_hda_codec_cleanup_for_unbind(struct hda_codec *codec)
{
+ struct hda_bus *bus = codec->bus;
+
if (codec->core.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->core.registered = 0;
+ spin_lock_irq(&bus->core.reg_lock);
+ codec->core.registered = false;
+ spin_unlock_irq(&bus->core.reg_lock);
}
snd_hda_codec_disconnect_pcms(codec);
@@ -808,6 +812,8 @@ void snd_hda_codec_display_power(struct hda_codec *codec, bool enable)
*/
void snd_hda_codec_register(struct hda_codec *codec)
{
+ struct hda_bus *bus = codec->bus;
+
if (codec->core.registered)
return;
if (device_is_registered(hda_codec_dev(codec))) {
@@ -815,7 +821,9 @@ void snd_hda_codec_register(struct hda_codec *codec)
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->core.registered = 1;
+ spin_lock_irq(&bus->core.reg_lock);
+ codec->core.registered = true;
+ spin_unlock_irq(&bus->core.reg_lock);
}
}
EXPORT_SYMBOL_GPL(snd_hda_codec_register);
@@ -921,7 +929,6 @@ snd_hda_codec_device_init(struct hda_bus *bus, unsigned int codec_addr,
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);
@@ -2018,7 +2025,7 @@ int snd_hda_add_vmaster_hook(struct hda_codec *codec,
if (!hook->hook || !hook->sw_kctl)
return 0;
hook->codec = codec;
- snd_ctl_add_vmaster_hook(hook->sw_kctl, vmaster_hook, hook);
+ snd_ctl_add_vmaster_hook(codec->card, hook->sw_kctl, vmaster_hook, hook);
return 0;
}
EXPORT_SYMBOL_GPL(snd_hda_add_vmaster_hook);
@@ -3040,6 +3047,7 @@ void snd_hda_codec_shutdown(struct hda_codec *codec)
codec->jackpoll_interval = 0; /* don't poll any longer */
cancel_delayed_work_sync(&codec->jackpoll_work);
+ snd_hdac_device_disable_unsol(&codec->core);
list_for_each_entry(cpcm, &codec->pcm_list_head, list)
snd_pcm_suspend_all(cpcm->pcm);
@@ -3774,7 +3782,7 @@ int snd_hda_multi_out_analog_open(struct hda_codec *codec,
EXPORT_SYMBOL_GPL(snd_hda_multi_out_analog_open);
/**
- * snd_hda_multi_out_analog_prepare - Preapre the analog outputs.
+ * snd_hda_multi_out_analog_prepare - Prepare the analog outputs.
* @codec: the HDA codec
* @mout: hda_multi_out object
* @stream_tag: stream tag to assign
diff --git a/sound/hda/common/hda_jack.h b/sound/hda/common/hda_jack.h
index e9b9970c59ed..a06b315e4b05 100644
--- a/sound/hda/common/hda_jack.h
+++ b/sound/hda/common/hda_jack.h
@@ -44,6 +44,7 @@ struct hda_jack_tbl {
int type;
int button_state;
struct snd_jack *jack;
+ struct hda_jack_tbl *next;
};
struct hda_jack_keymap {
diff --git a/sound/hda/common/jack.c b/sound/hda/common/jack.c
index 1d6b0f0e6f27..6b8aa1a6c008 100644
--- a/sound/hda/common/jack.c
+++ b/sound/hda/common/jack.c
@@ -76,12 +76,11 @@ static u32 read_pin_sense(struct hda_codec *codec, hda_nid_t nid, int dev_id)
struct hda_jack_tbl *
snd_hda_jack_tbl_get_mst(struct hda_codec *codec, hda_nid_t nid, int dev_id)
{
- struct hda_jack_tbl *jack = codec->jacktbl.list;
- int i;
+ struct hda_jack_tbl *jack;
- if (!nid || !jack)
+ if (!nid)
return NULL;
- for (i = 0; i < codec->jacktbl.used; i++, jack++)
+ for (jack = codec->jacktbl; jack; jack = jack->next)
if (jack->nid == nid && jack->dev_id == dev_id)
return jack;
return NULL;
@@ -98,12 +97,11 @@ struct hda_jack_tbl *
snd_hda_jack_tbl_get_from_tag(struct hda_codec *codec,
unsigned char tag, int dev_id)
{
- struct hda_jack_tbl *jack = codec->jacktbl.list;
- int i;
+ struct hda_jack_tbl *jack;
- if (!tag || !jack)
+ if (!tag)
return NULL;
- for (i = 0; i < codec->jacktbl.used; i++, jack++)
+ for (jack = codec->jacktbl; jack; jack = jack->next)
if (jack->tag == tag && jack->dev_id == dev_id)
return jack;
return NULL;
@@ -113,12 +111,11 @@ EXPORT_SYMBOL_GPL(snd_hda_jack_tbl_get_from_tag);
static struct hda_jack_tbl *
any_jack_tbl_get_from_nid(struct hda_codec *codec, hda_nid_t nid)
{
- struct hda_jack_tbl *jack = codec->jacktbl.list;
- int i;
+ struct hda_jack_tbl *jack;
- if (!nid || !jack)
+ if (!nid)
return NULL;
- for (i = 0; i < codec->jacktbl.used; i++, jack++)
+ for (jack = codec->jacktbl; jack; jack = jack->next)
if (jack->nid == nid)
return jack;
return NULL;
@@ -142,12 +139,19 @@ snd_hda_jack_tbl_new(struct hda_codec *codec, hda_nid_t nid, int dev_id)
if (jack)
return jack;
- jack = snd_array_new(&codec->jacktbl);
+ jack = kzalloc_obj(*jack);
if (!jack)
return NULL;
jack->nid = nid;
jack->dev_id = dev_id;
jack->jack_dirty = 1;
+ if (!codec->jacktbl)
+ codec->jacktbl = jack;
+ else
+ codec->jacktbl_last->next = jack;
+ codec->jacktbl_last = jack;
+ codec->jacktbl_used++;
+
if (existing_nid_jack) {
jack->tag = existing_nid_jack->tag;
@@ -158,7 +162,7 @@ snd_hda_jack_tbl_new(struct hda_codec *codec, hda_nid_t nid, int dev_id)
*/
jack->jack_detect = existing_nid_jack->jack_detect;
} else {
- jack->tag = codec->jacktbl.used;
+ jack->tag = codec->jacktbl_used;
}
return jack;
@@ -166,10 +170,9 @@ snd_hda_jack_tbl_new(struct hda_codec *codec, hda_nid_t nid, int dev_id)
void snd_hda_jack_tbl_disconnect(struct hda_codec *codec)
{
- struct hda_jack_tbl *jack = codec->jacktbl.list;
- int i;
+ struct hda_jack_tbl *jack;
- for (i = 0; i < codec->jacktbl.used; i++, jack++) {
+ for (jack = codec->jacktbl; jack; jack = jack->next) {
if (!codec->bus->shutdown && jack->jack)
snd_device_disconnect(codec->card, jack->jack);
}
@@ -177,22 +180,31 @@ void snd_hda_jack_tbl_disconnect(struct hda_codec *codec)
void snd_hda_jack_tbl_clear(struct hda_codec *codec)
{
- struct hda_jack_tbl *jack = codec->jacktbl.list;
- int i;
+ struct hda_jack_tbl *jack, *jack_next;
- for (i = 0; i < codec->jacktbl.used; i++, jack++) {
+ for (jack = codec->jacktbl; jack; jack = jack_next) {
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);
+ jack_next = jack->next;
+
+ if (jack->jack) {
+ /* fingers away from stale data */
+ jack->jack->private_data = NULL;
+ jack->jack->private_free = NULL;
+ /* free jack instances manually when clearing/reconfiguring */
+ if (!codec->bus->shutdown)
+ snd_device_free(codec->card, jack->jack);
+ }
for (cb = jack->callback; cb; cb = next) {
next = cb->next;
kfree(cb);
}
+ kfree(jack);
}
- snd_array_free(&codec->jacktbl);
+
+ codec->jacktbl = codec->jacktbl_last = NULL;
+ codec->jacktbl_used = 0;
}
#define get_jack_plug_state(sense) !!(sense & AC_PINSENSE_PRESENCE)
@@ -238,10 +250,9 @@ static void jack_detect_update(struct hda_codec *codec,
*/
void snd_hda_jack_set_dirty_all(struct hda_codec *codec)
{
- struct hda_jack_tbl *jack = codec->jacktbl.list;
- int i;
+ struct hda_jack_tbl *jack;
- for (i = 0; i < codec->jacktbl.used; i++, jack++)
+ for (jack = codec->jacktbl; jack; jack = jack->next)
if (jack->nid)
jack->jack_dirty = 1;
}
@@ -389,12 +400,15 @@ EXPORT_SYMBOL_GPL(snd_hda_jack_detect_enable);
int snd_hda_jack_set_gating_jack(struct hda_codec *codec, hda_nid_t gated_nid,
hda_nid_t gating_nid)
{
- struct hda_jack_tbl *gated = snd_hda_jack_tbl_new(codec, gated_nid, 0);
- struct hda_jack_tbl *gating =
- snd_hda_jack_tbl_new(codec, gating_nid, 0);
+ struct hda_jack_tbl *gated, *gating;
WARN_ON(codec->dp_mst);
+ if (!snd_hda_jack_tbl_new(codec, gated_nid, 0) ||
+ !snd_hda_jack_tbl_new(codec, gating_nid, 0))
+ return -ENOMEM;
+ gated = snd_hda_jack_tbl_get_mst(codec, gated_nid, 0);
+ gating = snd_hda_jack_tbl_get_mst(codec, gating_nid, 0);
if (!gated || !gating)
return -EINVAL;
@@ -475,19 +489,17 @@ EXPORT_SYMBOL_GPL(snd_hda_jack_set_button_state);
void snd_hda_jack_report_sync(struct hda_codec *codec)
{
struct hda_jack_tbl *jack;
- int i, state;
+ int state;
/* update all jacks at first */
- jack = codec->jacktbl.list;
- for (i = 0; i < codec->jacktbl.used; i++, jack++)
+ for (jack = codec->jacktbl; jack; jack = jack->next)
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++)
+ for (jack = codec->jacktbl; jack; jack = jack->next)
if (jack->nid) {
if (!jack->jack || jack->block_report)
continue;
@@ -755,10 +767,10 @@ EXPORT_SYMBOL_GPL(snd_hda_jack_unsol_event);
*/
void snd_hda_jack_poll_all(struct hda_codec *codec)
{
- struct hda_jack_tbl *jack = codec->jacktbl.list;
- int i, changes = 0;
+ struct hda_jack_tbl *jack;
+ int changes = 0;
- for (i = 0; i < codec->jacktbl.used; i++, jack++) {
+ for (jack = codec->jacktbl; jack; jack = jack->next) {
unsigned int old_sense;
if (!jack->nid || !jack->jack_dirty || jack->phantom_jack)
continue;
diff --git a/sound/hda/controllers/acpi.c b/sound/hda/controllers/acpi.c
index 505cc97e0ee9..04b261dde22e 100644
--- a/sound/hda/controllers/acpi.c
+++ b/sound/hda/controllers/acpi.c
@@ -266,6 +266,38 @@ static void hda_acpi_shutdown(struct platform_device *pdev)
azx_stop_chip(chip);
}
+static int hda_acpi_runtime_suspend(struct device *dev)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ struct azx *chip = card->private_data;
+
+ if (chip && chip->running) {
+ /* enable controller wake up event */
+ azx_writew(chip, WAKEEN, azx_readw(chip, WAKEEN) |
+ STATESTS_INT_MASK);
+
+ azx_stop_chip(chip);
+ azx_enter_link_reset(chip);
+ }
+
+ return 0;
+}
+
+static int hda_acpi_runtime_resume(struct device *dev)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ struct azx *chip = card->private_data;
+
+ if (chip && chip->running) {
+ azx_init_chip(chip, 1);
+ /* disable controller wake up event */
+ azx_writew(chip, WAKEEN, azx_readw(chip, WAKEEN) &
+ ~STATESTS_INT_MASK);
+ }
+
+ return 0;
+}
+
static int hda_acpi_suspend(struct device *dev)
{
struct snd_card *card = dev_get_drvdata(dev);
@@ -274,6 +306,12 @@ static int hda_acpi_suspend(struct device *dev)
rc = pm_runtime_force_suspend(dev);
if (rc < 0)
return rc;
+ /*
+ * Runtime PM is never enabled for this device, so the force_suspend
+ * above does not reach the runtime callback; stop the controller
+ * explicitly.
+ */
+ hda_acpi_runtime_suspend(dev);
snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
return 0;
@@ -287,6 +325,8 @@ static int hda_acpi_resume(struct device *dev)
rc = pm_runtime_force_resume(dev);
if (rc < 0)
return rc;
+ /* See hda_acpi_suspend(): re-init the controller explicitly. */
+ hda_acpi_runtime_resume(dev);
snd_power_change_state(card, SNDRV_CTL_POWER_D0);
return 0;
@@ -294,6 +334,7 @@ static int hda_acpi_resume(struct device *dev)
static const struct dev_pm_ops hda_acpi_pm = {
SYSTEM_SLEEP_PM_OPS(hda_acpi_suspend, hda_acpi_resume)
+ RUNTIME_PM_OPS(hda_acpi_runtime_suspend, hda_acpi_runtime_resume, NULL)
};
static const struct hda_data nvidia_hda_data = {
diff --git a/sound/hda/controllers/intel.c b/sound/hda/controllers/intel.c
index 24015c73a67f..6b80b59b8743 100644
--- a/sound/hda/controllers/intel.c
+++ b/sound/hda/controllers/intel.c
@@ -1184,7 +1184,7 @@ static const struct dev_pm_ops azx_pm = {
};
-static int azx_probe_continue(struct azx *chip);
+static int azx_probe_continue(struct azx *chip, bool free_on_error);
#ifdef SUPPORT_VGA_SWITCHEROO
static struct pci_dev *get_bound_vga(struct pci_dev *pci);
@@ -1211,8 +1211,10 @@ static void azx_vs_set_state(struct pci_dev *pci,
if (!disabled) {
dev_info(chip->card->dev,
"Start delayed initialization\n");
- if (azx_probe_continue(chip) < 0)
+ if (azx_probe_continue(chip, false) < 0) {
+ hda->init_failed = 1;
dev_err(chip->card->dev, "initialization error\n");
+ }
}
} else {
dev_info(chip->card->dev, "%s via vga_switcheroo\n",
@@ -1251,8 +1253,6 @@ static bool azx_vs_can_switch(struct pci_dev *pci)
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))
@@ -1380,7 +1380,6 @@ static void azx_free(struct azx *chip)
* actually stop the chip) to allow GPU to suspend via
* vga_switcheroo, and print a warning.
*/
- dev_warn(&pci->dev, "GPU sound probed, but not operational: please add a quirk to driver_denylist\n");
pm_runtime_disable(&pci->dev);
pm_runtime_set_suspended(&pci->dev);
pm_runtime_enable(&pci->dev);
@@ -1738,7 +1737,12 @@ static void azx_check_snoop_available(struct azx *chip)
static void azx_probe_work(struct work_struct *work)
{
struct hda_intel *hda = container_of(work, struct hda_intel, probe_work.work);
- azx_probe_continue(&hda->chip);
+
+ if (azx_probe_continue(&hda->chip, false) < 0) {
+ hda->init_failed = 1;
+ complete_all(&hda->probe_wait);
+ snd_card_disconnect(hda->chip.card);
+ }
}
static int default_bdl_pos_adj(struct azx *chip)
@@ -2356,7 +2360,7 @@ static const unsigned int azx_max_codecs[AZX_NUM_DRIVERS] = {
[AZX_DRIVER_TERA] = 1,
};
-static int azx_probe_continue(struct azx *chip)
+static int azx_probe_continue(struct azx *chip, bool free_on_error)
{
struct hda_intel *hda = container_of(chip, struct hda_intel, chip);
struct hdac_bus *bus = azx_bus(chip);
@@ -2447,8 +2451,12 @@ static int azx_probe_continue(struct azx *chip)
out_free:
if (err < 0) {
- pci_set_drvdata(pci, NULL);
- snd_card_free(chip->card);
+ if (hda->vga_switcheroo_registered)
+ dev_warn(&pci->dev, "GPU sound probed, but not operational: please add a quirk to driver_denylist\n");
+ if (free_on_error) {
+ pci_set_drvdata(pci, NULL);
+ snd_card_free(chip->card);
+ }
return err;
}
@@ -2481,12 +2489,17 @@ static void azx_remove(struct pci_dev *pci)
static void azx_shutdown(struct pci_dev *pci)
{
struct snd_card *card = pci_get_drvdata(pci);
+ struct hda_intel *hda;
struct azx *chip;
if (!card)
return;
chip = card->private_data;
- if (chip && chip->running)
+ if (!chip)
+ return;
+ hda = container_of(chip, struct hda_intel, chip);
+ cancel_delayed_work_sync(&hda->probe_work);
+ if (chip->running)
__azx_shutdown_chip(chip, true);
}
diff --git a/sound/hda/core/bus.c b/sound/hda/core/bus.c
index 81498f1e413e..02462bd035e7 100644
--- a/sound/hda/core/bus.c
+++ b/sound/hda/core/bus.c
@@ -21,7 +21,7 @@ static const struct hdac_bus_ops default_ops = {
};
/**
- * snd_hdac_bus_init - initialize a HD-audio bas bus
+ * snd_hdac_bus_init - initialize a HD-audio bus device
* @bus: the pointer to bus object
* @dev: device pointer
* @ops: bus verb operators
@@ -121,11 +121,15 @@ int snd_hdac_bus_exec_verb_unlocked(struct hdac_bus *bus, unsigned int addr,
break;
/* process pending verbs */
err = bus->ops->get_response(bus, addr, &tmp);
- if (err)
+ if (err) {
+ snd_hdac_bus_reset_response_counter(bus, addr);
break;
+ }
}
if (!err && res) {
err = bus->ops->get_response(bus, addr, res);
+ if (err)
+ snd_hdac_bus_reset_response_counter(bus, addr);
trace_hda_get_response(bus, addr, *res);
}
return err;
@@ -180,18 +184,40 @@ static void snd_hdac_bus_process_unsol_events(struct work_struct *work)
if (!(caddr & (1 << 4))) /* no unsolicited event? */
continue;
codec = bus->caddr_tbl[caddr & 0x0f];
- if (!codec || !codec->registered)
+ if (!codec || !codec->registered || codec->unsol_disabled)
+ continue;
+ if (!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);
+ if (!drv->unsol_event)
+ continue;
+ spin_unlock_irq(&bus->reg_lock);
+ drv->unsol_event(codec, res);
spin_lock_irq(&bus->reg_lock);
}
spin_unlock_irq(&bus->reg_lock);
}
/**
+ * snd_hdac_device_disable_unsol - block and flush unsol events for the codec
+ * @codec: the HDA core device
+ *
+ * Stop dispatching the unsolicited events to the given codec, and wait for
+ * the pending unsol event handler to finish. Called at unbinding the codec
+ * driver before releasing the driver resources.
+ */
+void snd_hdac_device_disable_unsol(struct hdac_device *codec)
+{
+ struct hdac_bus *bus = codec->bus;
+
+ spin_lock_irq(&bus->reg_lock);
+ codec->unsol_disabled = true;
+ spin_unlock_irq(&bus->reg_lock);
+ flush_work(&bus->unsol_work);
+}
+EXPORT_SYMBOL_GPL(snd_hdac_device_disable_unsol);
+
+/**
* snd_hdac_bus_add_device - Add a codec to bus
* @bus: HDA core bus
* @codec: HDA core device to add
diff --git a/sound/hda/core/component.c b/sound/hda/core/component.c
index 04755903880e..83b31186b027 100644
--- a/sound/hda/core/component.c
+++ b/sound/hda/core/component.c
@@ -70,10 +70,7 @@ void snd_hdac_display_power(struct hdac_bus *bus, unsigned int idx, bool enable)
dev_dbg(bus->dev, "display power %s\n", str_enable_disable(enable));
guard(mutex)(&bus->lock);
- if (enable)
- set_bit(idx, &bus->display_power_status);
- else
- clear_bit(idx, &bus->display_power_status);
+ assign_bit(idx, &bus->display_power_status, enable);
if (!acomp || !acomp->ops)
return;
diff --git a/sound/hda/core/controller.c b/sound/hda/core/controller.c
index 78855ac357c6..a7acd3418ae8 100644
--- a/sound/hda/core/controller.c
+++ b/sound/hda/core/controller.c
@@ -396,6 +396,25 @@ int snd_hdac_bus_get_response(struct hdac_bus *bus, unsigned int addr,
}
EXPORT_SYMBOL_GPL(snd_hdac_bus_get_response);
+/**
+ * snd_hdac_bus_reset_response_counter - reset the pending response count
+ * @bus: HD-audio core bus
+ * @addr: codec address
+ *
+ * Drop the number of commands that are still waiting for a response from
+ * the given codec address. This is called when a verb is known to be dead,
+ * e.g. after a response timeout, so that a lost response won't leave the
+ * counter non-zero forever and block all later verbs to the same codec.
+ */
+void snd_hdac_bus_reset_response_counter(struct hdac_bus *bus, unsigned int addr)
+{
+ if (addr >= HDA_MAX_CODECS)
+ return;
+
+ guard(spinlock_irq)(&bus->reg_lock);
+ bus->rirb.cmds[addr] = 0;
+}
+
#define HDAC_MAX_CAPS 10
/**
* snd_hdac_bus_parse_capabilities - parse capability structure
@@ -781,9 +800,6 @@ EXPORT_SYMBOL_GPL(snd_hdac_bus_free_stream_pages);
*/
void snd_hdac_bus_link_power(struct hdac_device *codec, bool enable)
{
- if (enable)
- set_bit(codec->addr, &codec->bus->codec_powered);
- else
- clear_bit(codec->addr, &codec->bus->codec_powered);
+ assign_bit(codec->addr, &codec->bus->codec_powered, enable);
}
EXPORT_SYMBOL_GPL(snd_hdac_bus_link_power);
diff --git a/sound/hda/core/device.c b/sound/hda/core/device.c
index a45f61e12c1a..6187ffaedbfa 100644
--- a/sound/hda/core/device.c
+++ b/sound/hda/core/device.c
@@ -572,14 +572,12 @@ 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)
+void snd_hdac_power_down(struct hdac_device *codec)
{
struct device *dev = &codec->dev;
- return pm_runtime_put_autosuspend(dev);
+ pm_runtime_put_autosuspend(dev);
}
EXPORT_SYMBOL_GPL(snd_hdac_power_down);
@@ -624,14 +622,11 @@ int snd_hdac_keep_power_up(struct hdac_device *codec)
*
* 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)
+void 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;
+ snd_hdac_power_down(codec);
}
EXPORT_SYMBOL_GPL(snd_hdac_power_down_pm);
#endif
diff --git a/sound/hda/core/local.h b/sound/hda/core/local.h
index 5f03b203c416..b9e58395decb 100644
--- a/sound/hda/core/local.h
+++ b/sound/hda/core/local.h
@@ -16,6 +16,8 @@ int snd_hdac_bus_add_device(struct hdac_bus *bus, struct hdac_device *codec);
void snd_hdac_bus_remove_device(struct hdac_bus *bus,
struct hdac_device *codec);
void snd_hdac_bus_queue_event(struct hdac_bus *bus, u32 res, u32 res_ex);
+void snd_hdac_bus_reset_response_counter(struct hdac_bus *bus,
+ unsigned int addr);
int snd_hdac_bus_exec_verb(struct hdac_bus *bus, unsigned int addr,
unsigned int cmd, unsigned int *res);
diff --git a/sound/i2c/other/ak4113.c b/sound/i2c/other/ak4113.c
index 6ef5fffda8d7..9405d985a87d 100644
--- a/sound/i2c/other/ak4113.c
+++ b/sound/i2c/other/ak4113.c
@@ -130,7 +130,7 @@ void snd_ak4113_reinit(struct ak4113 *chip)
scoped_guard(mutex, &chip->reinit_mutex) {
ak4113_init_regs(chip);
}
- /* bring up statistics / event queing */
+ /* bring up statistics / event queuing */
if (atomic_dec_and_test(&chip->wq_processing))
schedule_delayed_work(&chip->work, HZ / 10);
}
diff --git a/sound/i2c/other/ak4114.c b/sound/i2c/other/ak4114.c
index 71efb29b6c7c..4c3dbbf8c316 100644
--- a/sound/i2c/other/ak4114.c
+++ b/sound/i2c/other/ak4114.c
@@ -135,7 +135,7 @@ void snd_ak4114_reinit(struct ak4114 *chip)
scoped_guard(mutex, &chip->reinit_mutex) {
ak4114_init_regs(chip);
}
- /* bring up statistics / event queing */
+ /* bring up statistics / event queuing */
if (atomic_dec_and_test(&chip->wq_processing))
schedule_delayed_work(&chip->work, HZ / 10);
}
diff --git a/sound/i2c/other/ak4xxx-adda.c b/sound/i2c/other/ak4xxx-adda.c
index 9dd36b82a6ac..e7410f0f8660 100644
--- a/sound/i2c/other/ak4xxx-adda.c
+++ b/sound/i2c/other/ak4xxx-adda.c
@@ -147,7 +147,7 @@ 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
+ * Used for AK4524/AK4620 input/output attenuation, AK4528, and
* AK5365 input attenuation
*/
static const unsigned char vol_cvt_datt[128] = {
diff --git a/sound/isa/galaxy/galaxy.c b/sound/isa/galaxy/galaxy.c
index b0f1562d0fc0..aa31744ed582 100644
--- a/sound/isa/galaxy/galaxy.c
+++ b/sound/isa/galaxy/galaxy.c
@@ -478,11 +478,11 @@ static void snd_galaxy_free(struct snd_card *card)
galaxy_set_config(galaxy, galaxy->config);
}
-static int __snd_galaxy_probe(struct device *dev, unsigned int n)
+static int snd_galaxy_probe(struct device *dev, unsigned int n)
{
struct snd_galaxy *galaxy;
struct snd_wss *chip;
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
u8 type;
int err;
@@ -595,14 +595,10 @@ static int __snd_galaxy_probe(struct device *dev, unsigned int n)
return err;
dev_set_drvdata(dev, card);
+ card = NULL; /* probe succeeded, don't release as error */
return 0;
}
-static int snd_galaxy_probe(struct device *dev, unsigned int n)
-{
- return snd_card_free_on_error(dev, __snd_galaxy_probe(dev, n));
-}
-
static struct isa_driver snd_galaxy_driver = {
.match = snd_galaxy_match,
.probe = snd_galaxy_probe,
diff --git a/sound/isa/opti9xx/opti92x-ad1848.c b/sound/isa/opti9xx/opti92x-ad1848.c
index abaa3ed3ab5c..8a6db40ff893 100644
--- a/sound/isa/opti9xx/opti92x-ad1848.c
+++ b/sound/isa/opti9xx/opti92x-ad1848.c
@@ -378,7 +378,7 @@ static int snd_opti9xx_configure(struct snd_opti9xx *chip,
*/
snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(21), 0x82, 0xff);
/*
- * This bit sets OPTI931 to automaticaly select FM
+ * This bit sets OPTI931 to automatically select FM
* or digital input signal.
*/
snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(26), 0x01, 0x01);
diff --git a/sound/isa/sc6000.c b/sound/isa/sc6000.c
index cd3a63c7c2a7..f75533fe03a4 100644
--- a/sound/isa/sc6000.c
+++ b/sound/isa/sc6000.c
@@ -572,14 +572,14 @@ static void snd_sc6000_free(struct snd_card *card)
sc6000_setup_board(card->dev, sc6000->vport, 0);
}
-static int __snd_sc6000_probe(struct device *devptr, unsigned int dev)
+static int snd_sc6000_probe(struct device *devptr, unsigned int dev)
{
static const int possible_irqs[] = { 5, 7, 9, 10, 11, -1 };
static const int possible_dmas[] = { 1, 3, 0, -1 };
int err;
int xirq = irq[dev];
int xdma = dma[dev];
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
struct snd_sc6000 *sc6000;
struct snd_wss *chip;
struct snd_opl3 *opl3;
@@ -696,14 +696,10 @@ static int __snd_sc6000_probe(struct device *devptr, unsigned int dev)
return err;
dev_set_drvdata(devptr, card);
+ card = NULL; /* probe succeeded, don't release as error */
return 0;
}
-static int snd_sc6000_probe(struct device *devptr, unsigned int dev)
-{
- return snd_card_free_on_error(devptr, __snd_sc6000_probe(devptr, dev));
-}
-
#ifdef CONFIG_PM
static int snd_sc6000_suspend(struct device *devptr, unsigned int dev,
pm_message_t state)
diff --git a/sound/mips/hal2.c b/sound/mips/hal2.c
index 2beb6a0dc3b7..6decbaa5800e 100644
--- a/sound/mips/hal2.c
+++ b/sound/mips/hal2.c
@@ -375,7 +375,7 @@ static void hal2_setup_dac(struct snd_hal2 *hal2)
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 */
+ /* Set endianness */
hal2_i_clearbit16(hal2, H2I_DMA_END, H2I_DMA_END_CODECTX);
/* Set DMA bus */
hal2_i_setbit16(hal2, H2I_DMA_DRV, (1 << pbus->pbusnr));
@@ -400,7 +400,7 @@ static void hal2_setup_adc(struct snd_hal2 *hal2)
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 */
+ /* Set endianness */
hal2_i_clearbit16(hal2, H2I_DMA_END, H2I_DMA_END_CODECR);
/* Set DMA bus */
hal2_i_setbit16(hal2, H2I_DMA_DRV, (1 << pbus->pbusnr));
@@ -837,30 +837,24 @@ static int hal2_create(struct snd_card *card, struct snd_hal2 **rchip)
static int hal2_probe(struct platform_device *pdev)
{
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
struct snd_hal2 *chip;
int err;
- err = snd_card_new(&pdev->dev, index, id, THIS_MODULE, 0, &card);
+ err = snd_devm_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);
+ if (err < 0)
return err;
- }
err = hal2_pcm_create(chip);
- if (err < 0) {
- snd_card_free(card);
+ if (err < 0)
return err;
- }
err = hal2_mixer_create(chip);
- if (err < 0) {
- snd_card_free(card);
+ if (err < 0)
return err;
- }
strscpy(card->driver, "SGI HAL2 Audio");
strscpy(card->shortname, "SGI HAL2 Audio");
@@ -869,24 +863,16 @@ static int hal2_probe(struct platform_device *pdev)
SGI_HPCDMA_IRQ);
err = snd_card_register(card);
- if (err < 0) {
- snd_card_free(card);
+ if (err < 0)
return err;
- }
+
platform_set_drvdata(pdev, card);
+ card = NULL; /* probe succeeded, don't release as error */
return 0;
}
-static void hal2_remove(struct platform_device *pdev)
-{
- struct snd_card *card = platform_get_drvdata(pdev);
-
- snd_card_free(card);
-}
-
static struct platform_driver hal2_driver = {
.probe = hal2_probe,
- .remove = hal2_remove,
.driver = {
.name = "sgihal2",
}
diff --git a/sound/mips/sgio2audio.c b/sound/mips/sgio2audio.c
index 497d84cff12f..25c1871f72c1 100644
--- a/sound/mips/sgio2audio.c
+++ b/sound/mips/sgio2audio.c
@@ -742,7 +742,8 @@ static struct {
/* ALSA driver */
-static int snd_sgio2audio_free(struct snd_sgio2audio *chip)
+static int snd_sgio2audio_free(struct snd_sgio2audio *chip,
+ unsigned int irq_count)
{
int i;
@@ -752,7 +753,7 @@ static int snd_sgio2audio_free(struct snd_sgio2audio *chip)
writeq(0, &mace->perif.audio.control);
/* release IRQ's */
- for (i = 0; i < ARRAY_SIZE(snd_sgio2_isr_table); i++)
+ for (i = 0; i < irq_count; i++)
free_irq(snd_sgio2_isr_table[i].irq,
&chip->channel[snd_sgio2_isr_table[i].idx]);
@@ -768,7 +769,7 @@ static int snd_sgio2audio_dev_free(struct snd_device *device)
{
struct snd_sgio2audio *chip = device->device_data;
- return snd_sgio2audio_free(chip);
+ return snd_sgio2audio_free(chip, ARRAY_SIZE(snd_sgio2_isr_table));
}
static const struct snd_device_ops ops = {
@@ -819,7 +820,7 @@ static int snd_sgio2audio_create(struct snd_card *card,
0,
snd_sgio2_isr_table[i].desc,
&chip->channel[snd_sgio2_isr_table[i].idx])) {
- snd_sgio2audio_free(chip);
+ snd_sgio2audio_free(chip, i);
printk(KERN_ERR "sgio2audio: cannot allocate irq %d\n",
snd_sgio2_isr_table[i].irq);
return -EBUSY;
@@ -843,13 +844,13 @@ static int snd_sgio2audio_create(struct snd_card *card,
/* initialize the AD1843 codec */
err = ad1843_init(&chip->ad1843);
if (err < 0) {
- snd_sgio2audio_free(chip);
+ snd_sgio2audio_free(chip, ARRAY_SIZE(snd_sgio2_isr_table));
return err;
}
err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
if (err < 0) {
- snd_sgio2audio_free(chip);
+ snd_sgio2audio_free(chip, ARRAY_SIZE(snd_sgio2_isr_table));
return err;
}
*rchip = chip;
@@ -858,30 +859,24 @@ static int snd_sgio2audio_create(struct snd_card *card,
static int snd_sgio2audio_probe(struct platform_device *pdev)
{
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
struct snd_sgio2audio *chip;
int err;
- err = snd_card_new(&pdev->dev, index, id, THIS_MODULE, 0, &card);
+ err = snd_devm_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);
+ if (err < 0)
return err;
- }
err = snd_sgio2audio_new_pcm(chip);
- if (err < 0) {
- snd_card_free(card);
+ if (err < 0)
return err;
- }
err = snd_sgio2audio_new_mixer(chip);
- if (err < 0) {
- snd_card_free(card);
+ if (err < 0)
return err;
- }
strscpy(card->driver, "SGI O2 Audio");
strscpy(card->shortname, "SGI O2 Audio");
@@ -891,24 +886,16 @@ static int snd_sgio2audio_probe(struct platform_device *pdev)
MACEISA_AUDIO3_MERR_IRQ);
err = snd_card_register(card);
- if (err < 0) {
- snd_card_free(card);
+ if (err < 0)
return err;
- }
+
platform_set_drvdata(pdev, card);
+ card = NULL; /* probe succeeded, don't release as error */
return 0;
}
-static void snd_sgio2audio_remove(struct platform_device *pdev)
-{
- struct snd_card *card = platform_get_drvdata(pdev);
-
- snd_card_free(card);
-}
-
static struct platform_driver sgio2audio_driver = {
.probe = snd_sgio2audio_probe,
- .remove = snd_sgio2audio_remove,
.driver = {
.name = "sgio2audio",
}
diff --git a/sound/mips/snd-n64.c b/sound/mips/snd-n64.c
index f17e63f2ff5a..da889570cebc 100644
--- a/sound/mips/snd-n64.c
+++ b/sound/mips/snd-n64.c
@@ -273,6 +273,15 @@ static const struct snd_pcm_ops n64audio_pcm_ops = {
.close = n64audio_pcm_close,
};
+static void n64audio_free(struct snd_card *card)
+{
+ struct n64audio *priv = card->private_data;
+
+ if (priv->ring_base)
+ dma_free_coherent(card->dev, 32 * 1024, priv->ring_base,
+ priv->ring_base_dma);
+}
+
/*
* The target device is embedded and RAM-constrained. We save RAM
* by initializing in __init code that gets dropped late in boot.
@@ -280,14 +289,14 @@ static const struct snd_pcm_ops n64audio_pcm_ops = {
*/
static int __init n64audio_probe(struct platform_device *pdev)
{
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
struct snd_pcm *pcm;
struct n64audio *priv;
int err, irq;
- err = snd_card_new(&pdev->dev, SNDRV_DEFAULT_IDX1,
- SNDRV_DEFAULT_STR1,
- THIS_MODULE, sizeof(*priv), &card);
+ err = snd_devm_card_new(&pdev->dev, SNDRV_DEFAULT_IDX1,
+ SNDRV_DEFAULT_STR1,
+ THIS_MODULE, sizeof(*priv), &card);
if (err < 0)
return err;
@@ -296,29 +305,24 @@ static int __init n64audio_probe(struct platform_device *pdev)
spin_lock_init(&priv->chan.lock);
priv->card = card;
+ card->private_free = n64audio_free;
priv->ring_base = dma_alloc_coherent(card->dev, 32 * 1024, &priv->ring_base_dma,
- GFP_DMA|GFP_KERNEL);
- if (!priv->ring_base) {
- err = -ENOMEM;
- goto fail_card;
- }
+ GFP_KERNEL);
+ if (!priv->ring_base)
+ return -ENOMEM;
priv->mi_reg_base = devm_platform_ioremap_resource(pdev, 0);
- if (IS_ERR(priv->mi_reg_base)) {
- err = PTR_ERR(priv->mi_reg_base);
- goto fail_dma_alloc;
- }
+ if (IS_ERR(priv->mi_reg_base))
+ return PTR_ERR(priv->mi_reg_base);
priv->ai_reg_base = devm_platform_ioremap_resource(pdev, 1);
- if (IS_ERR(priv->ai_reg_base)) {
- err = PTR_ERR(priv->ai_reg_base);
- goto fail_dma_alloc;
- }
+ if (IS_ERR(priv->ai_reg_base))
+ return PTR_ERR(priv->ai_reg_base);
err = snd_pcm_new(card, "N64 Audio", 0, 1, 0, &pcm);
if (err < 0)
- goto fail_dma_alloc;
+ return err;
pcm->private_data = priv;
strscpy(pcm->name, "N64 Audio");
@@ -331,28 +335,19 @@ static int __init n64audio_probe(struct platform_device *pdev)
strscpy(card->longname, "N64 Audio");
irq = platform_get_irq(pdev, 0);
- if (irq < 0) {
- err = -EINVAL;
- goto fail_dma_alloc;
- }
+ if (irq < 0)
+ return -EINVAL;
+
if (devm_request_irq(&pdev->dev, irq, n64audio_isr,
- IRQF_SHARED, "N64 Audio", priv)) {
- err = -EBUSY;
- goto fail_dma_alloc;
- }
+ IRQF_SHARED, "N64 Audio", priv))
+ return -EBUSY;
err = snd_card_register(card);
if (err < 0)
- goto fail_dma_alloc;
+ return err;
+ card = NULL; /* probe succeeded, don't release as error */
return 0;
-
-fail_dma_alloc:
- dma_free_coherent(card->dev, 32 * 1024, priv->ring_base, priv->ring_base_dma);
-
-fail_card:
- snd_card_free(card);
- return err;
}
static struct platform_driver n64audio_driver = {
diff --git a/sound/oss/dmasound/dmasound_q40.c b/sound/oss/dmasound/dmasound_q40.c
index e25a78dd1bf2..339311e5d807 100644
--- a/sound/oss/dmasound/dmasound_q40.c
+++ b/sound/oss/dmasound/dmasound_q40.c
@@ -49,9 +49,8 @@ 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);
+static irqreturn_t q40_audio_interrupt(int irq, void *dummy);
+static void q40_frame_done(void);
/*** Mid level stuff *********************************************************/
@@ -372,8 +371,8 @@ static void Q40Free(void *ptr, unsigned int size)
static int __init Q40IrqInit(void)
{
/* Register interrupt handler. */
- if (request_irq(Q40_IRQ_SAMPLE, Q40StereoInterrupt, 0,
- "DMA sound", Q40Interrupt))
+ if (request_irq(Q40_IRQ_SAMPLE, q40_audio_interrupt, 0,
+ "DMA sound", q40_frame_done))
return 0;
return(1);
@@ -384,7 +383,7 @@ static int __init Q40IrqInit(void)
static void Q40IrqCleanUp(void)
{
master_outb(0,SAMPLE_ENABLE_REG);
- free_irq(Q40_IRQ_SAMPLE, Q40Interrupt);
+ free_irq(Q40_IRQ_SAMPLE, q40_frame_done);
}
#endif /* MODULE */
@@ -403,7 +402,6 @@ 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.
@@ -420,15 +418,6 @@ static void Q40PlayNextFrame(int index)
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);
@@ -456,31 +445,35 @@ static void Q40Play(void)
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)
+static irqreturn_t q40_audio_interrupt(int irq, void *dummy)
{
+ bool frame_done = false;
+
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();
+ if (dmasound.hard.stereo) {
+ if (q40_sc > 1) {
+ *DAC_LEFT = *q40_pp++;
+ *DAC_RIGHT = *q40_pp++;
+ q40_sc -= 2;
+ master_outb(1, SAMPLE_CLEAR_REG);
+ } else {
+ frame_done = true;
+ }
+ } else if (q40_sc > 0) {
+ *DAC_LEFT = *q40_pp;
+ *DAC_RIGHT = *q40_pp++;
+ q40_sc--;
+ master_outb(1, SAMPLE_CLEAR_REG);
+ } else {
+ frame_done = true;
+ }
spin_unlock(&dmasound.lock);
+ if (frame_done)
+ q40_frame_done();
return IRQ_HANDLED;
}
-static void Q40Interrupt(void)
+
+static void q40_frame_done(void)
{
if (!write_sq.active) {
/* playing was interrupted and sq_reset() has already cleared
diff --git a/sound/parisc/harmony.c b/sound/parisc/harmony.c
index a9625aedf2a4..6cdd09f5855b 100644
--- a/sound/parisc/harmony.c
+++ b/sound/parisc/harmony.c
@@ -897,24 +897,24 @@ static int __init
snd_harmony_probe(struct parisc_device *padev)
{
int err;
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
struct snd_harmony *h;
- err = snd_card_new(&padev->dev, index, id, THIS_MODULE, 0, &card);
+ err = snd_devm_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;
+ return err;
err = snd_harmony_pcm_init(h);
if (err < 0)
- goto free_and_ret;
+ return err;
err = snd_harmony_mixer_init(h);
if (err < 0)
- goto free_and_ret;
+ return err;
strscpy(card->driver, "harmony");
strscpy(card->shortname, "Harmony");
@@ -923,27 +923,17 @@ snd_harmony_probe(struct parisc_device *padev)
err = snd_card_register(card);
if (err < 0)
- goto free_and_ret;
+ return err;
parisc_set_drvdata(padev, card);
+ card = NULL; /* probe succeeded, don't release as error */
return 0;
-
-free_and_ret:
- snd_card_free(card);
- return err;
-}
-
-static void __exit
-snd_harmony_remove(struct parisc_device *padev)
-{
- snd_card_free(parisc_get_drvdata(padev));
}
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
diff --git a/sound/pci/ac97/ac97_codec.c b/sound/pci/ac97/ac97_codec.c
index 0bb65be021d9..207ce083593f 100644
--- a/sound/pci/ac97/ac97_codec.c
+++ b/sound/pci/ac97/ac97_codec.c
@@ -2960,7 +2960,7 @@ int snd_ac97_tune_hardware(struct snd_ac97 *ac97,
{
int result;
- /* quirk overriden? */
+ /* quirk overridden? */
if (override && strcmp(override, "-1") && strcmp(override, "default")) {
result = apply_quirk_str(ac97, override);
if (result < 0)
diff --git a/sound/pci/ac97/ac97_patch.c b/sound/pci/ac97/ac97_patch.c
index 64cc39dd2008..8577a2e0510c 100644
--- a/sound/pci/ac97/ac97_patch.c
+++ b/sound/pci/ac97/ac97_patch.c
@@ -430,7 +430,7 @@ 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.
+ 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.
*/
diff --git a/sound/pci/ad1889.c b/sound/pci/ad1889.c
index f4ec404c0d15..42a8fa3f4fdc 100644
--- a/sound/pci/ad1889.c
+++ b/sound/pci/ad1889.c
@@ -837,12 +837,12 @@ snd_ad1889_create(struct snd_card *card, struct pci_dev *pci)
}
static int
-__snd_ad1889_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
+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_card *card __free(snd_card_free) = NULL;
struct snd_ad1889 *chip;
/* (1) */
@@ -892,17 +892,12 @@ __snd_ad1889_probe(struct pci_dev *pci,
/* (7) */
pci_set_drvdata(pci, card);
+ card = NULL; /* probe succeeded, don't release as error */
devno++;
return 0;
}
-static int snd_ad1889_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
-{
- return snd_card_free_on_error(&pci->dev, __snd_ad1889_probe(pci, pci_id));
-}
-
static const struct pci_device_id snd_ad1889_ids[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_ANALOG_DEVICES, PCI_DEVICE_ID_AD1889JS) },
{ 0, },
diff --git a/sound/pci/ali5451/ali5451.c b/sound/pci/ali5451/ali5451.c
index f07c5f4c2380..8295eee9b977 100644
--- a/sound/pci/ali5451/ali5451.c
+++ b/sound/pci/ali5451/ali5451.c
@@ -2086,10 +2086,10 @@ static int snd_ali_create(struct snd_card *card,
return 0;
}
-static int __snd_ali_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
+static int snd_ali_probe(struct pci_dev *pci,
+ const struct pci_device_id *pci_id)
{
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
struct snd_ali *codec;
int err;
@@ -2129,15 +2129,10 @@ static int __snd_ali_probe(struct pci_dev *pci,
return err;
pci_set_drvdata(pci, card);
+ card = NULL; /* probe succeeded, don't release as error */
return 0;
}
-static int snd_ali_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
-{
- return snd_card_free_on_error(&pci->dev, __snd_ali_probe(pci, pci_id));
-}
-
static struct pci_driver ali5451_driver = {
.name = KBUILD_MODNAME,
.id_table = snd_ali_ids,
diff --git a/sound/pci/als300.c b/sound/pci/als300.c
index 00f0720169c3..5832f4d38c4e 100644
--- a/sound/pci/als300.c
+++ b/sound/pci/als300.c
@@ -675,7 +675,7 @@ 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_card *card __free(snd_card_free) = NULL;
struct snd_als300 *chip;
int err, chip_type;
@@ -696,7 +696,7 @@ static int snd_als300_probe(struct pci_dev *pci,
err = snd_als300_create(card, pci, chip_type);
if (err < 0)
- goto error;
+ return err;
strscpy(card->driver, "ALS300");
if (chip->chip_type == DEVICE_ALS300_PLUS)
@@ -711,15 +711,12 @@ static int snd_als300_probe(struct pci_dev *pci,
err = snd_card_register(card);
if (err < 0)
- goto error;
+ return err;
pci_set_drvdata(pci, card);
+ card = NULL; /* probe succeeded, don't release as error */
dev++;
return 0;
-
- error:
- snd_card_free(card);
- return err;
}
static struct pci_driver als300_driver = {
diff --git a/sound/pci/als4000.c b/sound/pci/als4000.c
index 6396aa6c3bf0..12bb868fb9ce 100644
--- a/sound/pci/als4000.c
+++ b/sound/pci/als4000.c
@@ -798,11 +798,11 @@ static void snd_card_als4000_free( struct snd_card *card )
snd_als4000_free_gameport(acard);
}
-static int __snd_card_als4000_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
+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 *card __free(snd_card_free) = NULL;
struct snd_card_als4000 *acard;
unsigned long iobase;
struct snd_sb *chip;
@@ -918,16 +918,11 @@ static int __snd_card_als4000_probe(struct pci_dev *pci,
return err;
pci_set_drvdata(pci, card);
+ card = NULL; /* probe succeeded, don't release as error */
dev++;
return 0;
}
-static int snd_card_als4000_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
-{
- return snd_card_free_on_error(&pci->dev, __snd_card_als4000_probe(pci, pci_id));
-}
-
static int snd_als4000_suspend(struct device *dev)
{
struct snd_card *card = dev_get_drvdata(dev);
diff --git a/sound/pci/atiixp.c b/sound/pci/atiixp.c
index b738295b41e5..e1d36cd7945b 100644
--- a/sound/pci/atiixp.c
+++ b/sound/pci/atiixp.c
@@ -1550,10 +1550,10 @@ static int snd_atiixp_init(struct snd_card *card, struct pci_dev *pci)
}
-static int __snd_atiixp_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
+static int snd_atiixp_probe(struct pci_dev *pci,
+ const struct pci_device_id *pci_id)
{
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
struct atiixp *chip;
int err;
@@ -1598,15 +1598,10 @@ static int __snd_atiixp_probe(struct pci_dev *pci,
return err;
pci_set_drvdata(pci, card);
+ card = NULL; /* probe succeeded, don't release as error */
return 0;
}
-static int snd_atiixp_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
-{
- return snd_card_free_on_error(&pci->dev, __snd_atiixp_probe(pci, pci_id));
-}
-
static struct pci_driver atiixp_driver = {
.name = KBUILD_MODNAME,
.id_table = snd_atiixp_ids,
diff --git a/sound/pci/atiixp_modem.c b/sound/pci/atiixp_modem.c
index 8aaeb197ce45..4984a345b4b2 100644
--- a/sound/pci/atiixp_modem.c
+++ b/sound/pci/atiixp_modem.c
@@ -1182,10 +1182,10 @@ static int snd_atiixp_init(struct snd_card *card, struct pci_dev *pci)
}
-static int __snd_atiixp_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
+static int snd_atiixp_probe(struct pci_dev *pci,
+ const struct pci_device_id *pci_id)
{
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
struct atiixp_modem *chip;
int err;
@@ -1225,15 +1225,10 @@ static int __snd_atiixp_probe(struct pci_dev *pci,
return err;
pci_set_drvdata(pci, card);
+ card = NULL; /* probe succeeded, don't release as error */
return 0;
}
-static int snd_atiixp_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
-{
- return snd_card_free_on_error(&pci->dev, __snd_atiixp_probe(pci, pci_id));
-}
-
static struct pci_driver atiixp_modem_driver = {
.name = KBUILD_MODNAME,
.id_table = snd_atiixp_ids,
diff --git a/sound/pci/au88x0/au88x0.c b/sound/pci/au88x0/au88x0.c
index bb02945793f0..87a24f0c86e7 100644
--- a/sound/pci/au88x0/au88x0.c
+++ b/sound/pci/au88x0/au88x0.c
@@ -192,10 +192,10 @@ snd_vortex_create(struct snd_card *card, struct pci_dev *pci)
// constructor -- see "Constructor" sub-section
static int
-__snd_vortex_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
+snd_vortex_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
{
static int dev;
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
vortex_t *chip;
int err;
@@ -303,18 +303,13 @@ __snd_vortex_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
return err;
// (7)
pci_set_drvdata(pci, card);
+ card = NULL; /* probe succeeded, don't release as error */
dev++;
vortex_connect_default(chip, 1);
vortex_enable_int(chip);
return 0;
}
-static int
-snd_vortex_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
-{
- return snd_card_free_on_error(&pci->dev, __snd_vortex_probe(pci, pci_id));
-}
-
// pci_driver definition
static struct pci_driver vortex_driver = {
.name = KBUILD_MODNAME,
diff --git a/sound/pci/au88x0/au88x0.h b/sound/pci/au88x0/au88x0.h
index 6cbb2bc4a048..f97a0303da3e 100644
--- a/sound/pci/au88x0/au88x0.h
+++ b/sound/pci/au88x0/au88x0.h
@@ -28,7 +28,7 @@
/* Vortex MPU401 defines. */
#define MIDI_CLOCK_DIV 0x61
-/* Standart MPU401 defines. */
+/* Standard MPU401 defines. */
#define MPU401_RESET 0xff
#define MPU401_ENTER_UART 0x3f
#define MPU401_ACK 0xfe
diff --git a/sound/pci/au88x0/au88x0_mpu401.c b/sound/pci/au88x0/au88x0_mpu401.c
index 164f6b7039ab..dff4bf80ab96 100644
--- a/sound/pci/au88x0/au88x0_mpu401.c
+++ b/sound/pci/au88x0/au88x0_mpu401.c
@@ -22,7 +22,7 @@
/* Vortex MPU401 defines. */
#define MIDI_CLOCK_DIV 0x61
-/* Standart MPU401 defines. */
+/* Standard MPU401 defines. */
#define MPU401_RESET 0xff
#define MPU401_ENTER_UART 0x3f
#define MPU401_ACK 0xfe
diff --git a/sound/pci/aw2/aw2-alsa.c b/sound/pci/aw2/aw2-alsa.c
index 60a87322eae4..513249b9bb78 100644
--- a/sound/pci/aw2/aw2-alsa.c
+++ b/sound/pci/aw2/aw2-alsa.c
@@ -251,7 +251,7 @@ static int snd_aw2_probe(struct pci_dev *pci,
const struct pci_device_id *pci_id)
{
static int dev;
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
struct aw2 *chip;
int err;
@@ -273,7 +273,7 @@ static int snd_aw2_probe(struct pci_dev *pci,
/* (3) Create main component */
err = snd_aw2_create(card, pci);
if (err < 0)
- goto error;
+ return err;
/* initialize mutex */
mutex_init(&chip->mtx);
@@ -292,17 +292,14 @@ static int snd_aw2_probe(struct pci_dev *pci,
/* (6) Register card instance */
err = snd_card_register(card);
if (err < 0)
- goto error;
+ return err;
/* (7) Set PCI driver data */
pci_set_drvdata(pci, card);
+ card = NULL; /* probe succeeded, don't release as error */
dev++;
return 0;
-
- error:
- snd_card_free(card);
- return err;
}
/* open callback */
diff --git a/sound/pci/aw2/aw2-saa7146.c b/sound/pci/aw2/aw2-saa7146.c
index c84f1a45194f..6857a0ec2924 100644
--- a/sound/pci/aw2/aw2-saa7146.c
+++ b/sound/pci/aw2/aw2-saa7146.c
@@ -147,7 +147,7 @@ void snd_aw2_saa7146_pcm_init_playback(struct snd_aw2_saa7146 *chip,
unsigned long dw_page, dw_limit;
/* Configure DMA for substream
- Configuration informations: ALSA has allocated continuous memory
+ Configuration information: ALSA has allocated continuous memory
pages. So we don't need to use MMU of saa7146.
*/
@@ -204,7 +204,7 @@ void snd_aw2_saa7146_pcm_init_capture(struct snd_aw2_saa7146 *chip,
unsigned long dw_page, dw_limit;
/* Configure DMA for substream
- Configuration informations: ALSA has allocated continuous memory
+ Configuration information: ALSA has allocated continuous memory
pages. So we don't need to use MMU of saa7146.
*/
diff --git a/sound/pci/azt3328.c b/sound/pci/azt3328.c
index ccca82417657..8cf94c41a54c 100644
--- a/sound/pci/azt3328.c
+++ b/sound/pci/azt3328.c
@@ -2403,10 +2403,10 @@ snd_azf3328_create(struct snd_card *card,
}
static int
-__snd_azf3328_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
+snd_azf3328_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
{
static int dev;
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
struct snd_azf3328 *chip;
struct snd_opl3 *opl3;
int err;
@@ -2492,16 +2492,11 @@ __snd_azf3328_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
snd_azf3328_gameport(chip, dev);
pci_set_drvdata(pci, card);
+ card = NULL; /* probe succeeded, don't release as error */
dev++;
return 0;
}
-static int
-snd_azf3328_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
-{
- return snd_card_free_on_error(&pci->dev, __snd_azf3328_probe(pci, pci_id));
-}
-
static inline void
snd_azf3328_suspend_regs(const struct snd_azf3328 *chip,
unsigned long io_addr, unsigned count, u32 *saved_regs)
diff --git a/sound/pci/bt87x.c b/sound/pci/bt87x.c
index 383def1f2af7..db8f60261a0f 100644
--- a/sound/pci/bt87x.c
+++ b/sound/pci/bt87x.c
@@ -798,11 +798,11 @@ static int snd_bt87x_detect_card(struct pci_dev *pci)
return SND_BT87X_BOARD_UNKNOWN;
}
-static int __snd_bt87x_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
+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_card *card __free(snd_card_free) = NULL;
struct snd_bt87x *chip;
int err;
enum snd_bt87x_boardid boardid;
@@ -878,16 +878,11 @@ static int __snd_bt87x_probe(struct pci_dev *pci,
return err;
pci_set_drvdata(pci, card);
+ card = NULL; /* probe succeeded, don't release as error */
++dev;
return 0;
}
-static int snd_bt87x_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
-{
- return snd_card_free_on_error(&pci->dev, __snd_bt87x_probe(pci, pci_id));
-}
-
/* default entries for all Bt87x cards - it's not exported */
/* driver_data is set to 0 to call detection */
static const struct pci_device_id snd_bt87x_default_ids[] = {
diff --git a/sound/pci/ca0106/ca0106_main.c b/sound/pci/ca0106/ca0106_main.c
index cf923f551211..6429c2bfc310 100644
--- a/sound/pci/ca0106/ca0106_main.c
+++ b/sound/pci/ca0106/ca0106_main.c
@@ -1710,11 +1710,11 @@ static int snd_ca0106_midi(struct snd_ca0106 *chip, unsigned int channel)
}
-static int __snd_ca0106_probe(struct pci_dev *pci,
+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_card *card __free(snd_card_free) = NULL;
struct snd_ca0106 *chip;
int i, err;
@@ -1767,16 +1767,11 @@ static int __snd_ca0106_probe(struct pci_dev *pci,
return err;
pci_set_drvdata(pci, card);
+ card = NULL; /* probe succeeded, don't release as error */
dev++;
return 0;
}
-static int snd_ca0106_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
-{
- return snd_card_free_on_error(&pci->dev, __snd_ca0106_probe(pci, pci_id));
-}
-
#ifdef CONFIG_PM_SLEEP
static int snd_ca0106_suspend(struct device *dev)
{
diff --git a/sound/pci/cmipci.c b/sound/pci/cmipci.c
index 9d9f784e3a8c..d120b7bbe024 100644
--- a/sound/pci/cmipci.c
+++ b/sound/pci/cmipci.c
@@ -3170,7 +3170,7 @@ static int snd_cmipci_probe(struct pci_dev *pci,
const struct pci_device_id *pci_id)
{
static int dev;
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
int err;
if (dev >= SNDRV_CARDS)
@@ -3201,19 +3201,16 @@ static int snd_cmipci_probe(struct pci_dev *pci,
err = snd_cmipci_create(card, pci, dev);
if (err < 0)
- goto error;
+ return err;
err = snd_card_register(card);
if (err < 0)
- goto error;
+ return err;
pci_set_drvdata(pci, card);
+ card = NULL; /* probe succeeded, don't release as error */
dev++;
return 0;
-
- error:
- snd_card_free(card);
- return err;
}
/*
diff --git a/sound/pci/cs4281.c b/sound/pci/cs4281.c
index f51f4bb63766..b831f1d50750 100644
--- a/sound/pci/cs4281.c
+++ b/sound/pci/cs4281.c
@@ -324,7 +324,7 @@ MODULE_PARM_DESC(dual_codec, "Secondary Codec ID (0 = disabled).");
#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_SSCR_HVC (1<<3) /* Hardware Volume Control Enable */
#define BA0_FMLVC 0x0754 /* FM Synthesis Left Volume Control */
#define BA0_FMRVC 0x0758 /* FM Synthesis Right Volume Control */
@@ -1811,11 +1811,11 @@ static void snd_cs4281_opl3_command(struct snd_opl3 *opl3, unsigned short cmd,
udelay(30);
}
-static int __snd_cs4281_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
+static int snd_cs4281_probe(struct pci_dev *pci,
+ const struct pci_device_id *pci_id)
{
static int dev;
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
struct cs4281 *chip;
struct snd_opl3 *opl3;
int err;
@@ -1868,16 +1868,11 @@ static int __snd_cs4281_probe(struct pci_dev *pci,
return err;
pci_set_drvdata(pci, card);
+ card = NULL; /* probe succeeded, don't release as error */
dev++;
return 0;
}
-static int snd_cs4281_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
-{
- return snd_card_free_on_error(&pci->dev, __snd_cs4281_probe(pci, pci_id));
-}
-
/*
* Power Management
*/
diff --git a/sound/pci/cs46xx/cs46xx.c b/sound/pci/cs46xx/cs46xx.c
index 0cb7c2a2929f..a4a0b36290d9 100644
--- a/sound/pci/cs46xx/cs46xx.c
+++ b/sound/pci/cs46xx/cs46xx.c
@@ -55,7 +55,7 @@ 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_card *card __free(snd_card_free) = NULL;
struct snd_cs46xx *chip;
int err;
@@ -74,36 +74,36 @@ static int snd_card_cs46xx_probe(struct pci_dev *pci,
err = snd_cs46xx_create(card, pci,
external_amp[dev], thinkpad[dev]);
if (err < 0)
- goto error;
+ return err;
card->private_data = chip;
chip->accept_valid = mmap_valid[dev];
err = snd_cs46xx_pcm(chip, 0);
if (err < 0)
- goto error;
+ return err;
#ifdef CONFIG_SND_CS46XX_NEW_DSP
err = snd_cs46xx_pcm_rear(chip, 1);
if (err < 0)
- goto error;
+ return err;
err = snd_cs46xx_pcm_iec958(chip, 2);
if (err < 0)
- goto error;
+ return err;
#endif
err = snd_cs46xx_mixer(chip, 2);
if (err < 0)
- goto error;
+ return err;
#ifdef CONFIG_SND_CS46XX_NEW_DSP
if (chip->nr_ac97_codecs ==2) {
err = snd_cs46xx_pcm_center_lfe(chip, 3);
if (err < 0)
- goto error;
+ return err;
}
#endif
err = snd_cs46xx_midi(chip, 0);
if (err < 0)
- goto error;
+ return err;
err = snd_cs46xx_start_dsp(chip);
if (err < 0)
- goto error;
+ return err;
snd_cs46xx_gameport(chip);
@@ -117,15 +117,12 @@ static int snd_card_cs46xx_probe(struct pci_dev *pci,
err = snd_card_register(card);
if (err < 0)
- goto error;
+ return err;
pci_set_drvdata(pci, card);
+ card = NULL; /* probe succeeded, don't release as error */
dev++;
return 0;
-
- error:
- snd_card_free(card);
- return err;
}
static struct pci_driver cs46xx_driver = {
diff --git a/sound/pci/cs46xx/cs46xx_lib.c b/sound/pci/cs46xx/cs46xx_lib.c
index f6db08b75649..797a5f09cade 100644
--- a/sound/pci/cs46xx/cs46xx_lib.c
+++ b/sound/pci/cs46xx/cs46xx_lib.c
@@ -315,7 +315,7 @@ int snd_cs46xx_download(struct snd_cs46xx *chip,
dst = chip->region.idx[bank+1].remap_addr + offset;
len /= sizeof(u32);
- /* writel already converts 32-bit value to right endianess */
+ /* writel already converts 32-bit value to right endianness */
while (len-- > 0) {
writel(*src++, dst);
dst += sizeof(u32);
@@ -474,7 +474,7 @@ int snd_cs46xx_clear_BA1(struct snd_cs46xx *chip,
dst = chip->region.idx[bank+1].remap_addr + offset;
len /= sizeof(u32);
- /* writel already converts 32-bit value to right endianess */
+ /* writel already converts 32-bit value to right endianness */
while (len-- > 0) {
writel(0, dst);
dst += sizeof(u32);
@@ -3039,7 +3039,7 @@ static int snd_cs46xx_chip_init(struct snd_cs46xx *chip)
#endif
/*
- * Assert the vaid frame signal so that we can start sending commands
+ * Assert the valid 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);
diff --git a/sound/pci/cs46xx/dsp_spos.c b/sound/pci/cs46xx/dsp_spos.c
index 6fd4f629d1d5..a77e6f5a14ad 100644
--- a/sound/pci/cs46xx/dsp_spos.c
+++ b/sound/pci/cs46xx/dsp_spos.c
@@ -393,7 +393,7 @@ int cs46xx_dsp_load_module (struct snd_cs46xx * chip, struct dsp_module_desc * m
dev_dbg(chip->card->dev,
"dsp_spos: module got no code segment\n");
} else {
- if (ins->code.offset + code->size > DSP_CODE_BYTE_SIZE) {
+ if ((u64)ins->code.offset + code->size > DSP_CODE_BYTE_SIZE / 4) {
dev_err(chip->card->dev,
"dsp_spos: no space available in DSP\n");
return -ENOMEM;
diff --git a/sound/pci/cs46xx/dsp_spos_scb_lib.c b/sound/pci/cs46xx/dsp_spos_scb_lib.c
index fd19365026b4..71eee5cb478f 100644
--- a/sound/pci/cs46xx/dsp_spos_scb_lib.c
+++ b/sound/pci/cs46xx/dsp_spos_scb_lib.c
@@ -160,15 +160,15 @@ void cs46xx_dsp_remove_scb (struct snd_cs46xx *chip, struct dsp_scb_descriptor *
{
struct dsp_spos_instance * ins = chip->dsp_spos_instance;
- /* check integrety */
+ /* check integrity */
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 */
+ /* can't remove a SCB with children before
+ removing children first */
if (snd_BUG_ON(scb->sub_list_ptr != ins->the_null_scb ||
scb->next_scb_ptr != ins->the_null_scb))
goto _end;
@@ -1656,7 +1656,7 @@ int cs46xx_dsp_disable_spdif_out (struct snd_cs46xx *chip)
return -EBUSY;
}
- /* check integrety */
+ /* check integrity */
if (snd_BUG_ON(!ins->asynch_tx_scb))
return -EINVAL;
if (snd_BUG_ON(!ins->spdif_pcm_input_scb))
diff --git a/sound/pci/cs5535audio/cs5535audio.c b/sound/pci/cs5535audio/cs5535audio.c
index 0ebf6c02b1ef..e09f7bc9e23a 100644
--- a/sound/pci/cs5535audio/cs5535audio.c
+++ b/sound/pci/cs5535audio/cs5535audio.c
@@ -283,11 +283,11 @@ static int snd_cs5535audio_create(struct snd_card *card,
return 0;
}
-static int __snd_cs5535audio_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
+static int snd_cs5535audio_probe(struct pci_dev *pci,
+ const struct pci_device_id *pci_id)
{
static int dev;
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
struct cs5535audio *cs5535au;
int err;
@@ -329,16 +329,11 @@ static int __snd_cs5535audio_probe(struct pci_dev *pci,
return err;
pci_set_drvdata(pci, card);
+ card = NULL; /* probe succeeded, don't release as error */
dev++;
return 0;
}
-static int snd_cs5535audio_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
-{
- return snd_card_free_on_error(&pci->dev, __snd_cs5535audio_probe(pci, pci_id));
-}
-
static struct pci_driver cs5535audio_driver = {
.name = KBUILD_MODNAME,
.id_table = snd_cs5535audio_ids,
diff --git a/sound/pci/ctxfi/xfi.c b/sound/pci/ctxfi/xfi.c
index d8dd84d41c87..99319d75e963 100644
--- a/sound/pci/ctxfi/xfi.c
+++ b/sound/pci/ctxfi/xfi.c
@@ -56,7 +56,7 @@ static int
ct_card_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
{
static int dev;
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
struct ct_atc *atc;
int err;
@@ -67,8 +67,8 @@ ct_card_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
dev++;
return -ENOENT;
}
- err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
- 0, &card);
+ err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
+ 0, &card);
if (err)
return err;
if ((reference_rate != 48000) && (reference_rate != 44100)) {
@@ -89,14 +89,14 @@ ct_card_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
err = ct_atc_create(card, pci, reference_rate, multiple,
pci_id->driver_data, subsystem[dev], &atc);
if (err < 0)
- goto error;
+ return err;
card->private_data = atc;
/* Create alsa devices supported by this card */
err = ct_atc_create_alsa_devs(atc);
if (err < 0)
- goto error;
+ return err;
strscpy(card->driver, "SB-XFi");
strscpy(card->shortname, "Creative X-Fi");
@@ -105,21 +105,13 @@ ct_card_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
err = snd_card_register(card);
if (err < 0)
- goto error;
+ return err;
pci_set_drvdata(pci, card);
+ card = NULL; /* probe succeeded, don't release as error */
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
@@ -149,7 +141,6 @@ 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,
},
diff --git a/sound/pci/echoaudio/echoaudio.c b/sound/pci/echoaudio/echoaudio.c
index 214d68657707..4dcad05fd57e 100644
--- a/sound/pci/echoaudio/echoaudio.c
+++ b/sound/pci/echoaudio/echoaudio.c
@@ -270,7 +270,7 @@ static int pcm_open(struct snd_pcm_substream *substream,
return -ENOMEM;
pipe->index = -1; /* Not configured yet */
- /* Set up hw capabilities and contraints */
+ /* Set up hw capabilities and constraints */
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;
@@ -1720,7 +1720,7 @@ static const struct snd_kcontrol_new snd_echo_vumeters = {
-/*** Channels info - it exports informations about the number of channels ***/
+/*** Channels info - it exports information about the number of channels ***/
static int snd_echo_channels_info_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
@@ -1934,11 +1934,11 @@ static int snd_echo_create(struct snd_card *card,
}
/* constructor */
-static int __snd_echo_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
+static int snd_echo_probe(struct pci_dev *pci,
+ const struct pci_device_id *pci_id)
{
static int dev;
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
struct echoaudio *chip;
char *dsp;
int err;
@@ -2097,17 +2097,11 @@ static int __snd_echo_probe(struct pci_dev *pci,
dev_info(card->dev, "Card registered: %s\n", card->longname);
pci_set_drvdata(pci, chip);
+ card = NULL; /* probe succeeded, don't release as error */
dev++;
return 0;
}
-static int snd_echo_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
-{
- return snd_card_free_on_error(&pci->dev, __snd_echo_probe(pci, pci_id));
-}
-
-
static int snd_echo_suspend(struct device *dev)
{
struct echoaudio *chip = dev_get_drvdata(dev);
diff --git a/sound/pci/echoaudio/echoaudio.h b/sound/pci/echoaudio/echoaudio.h
index 7a5744bf3155..ac4507b7cc78 100644
--- a/sound/pci/echoaudio/echoaudio.h
+++ b/sound/pci/echoaudio/echoaudio.h
@@ -394,8 +394,8 @@ struct echoaudio {
u16 digital_modes; /* Bitmask of supported modes
* (see ECHOCAPS_HAS_DIGITAL_MODE_*) */
- u16 input_clock_types; /* Suppoted input clock types */
- u16 output_clock_types; /* Suppoted output clock types -
+ u16 input_clock_types; /* Supported input clock types */
+ u16 output_clock_types; /* Supported output clock types -
* Layla20 only */
u16 device_id, subdevice_id;
u16 *dsp_code; /* Current DSP code loaded,
diff --git a/sound/pci/emu10k1/emu10k1x.c b/sound/pci/emu10k1/emu10k1x.c
index ed4630c8342b..03d277dd02d9 100644
--- a/sound/pci/emu10k1/emu10k1x.c
+++ b/sound/pci/emu10k1/emu10k1x.c
@@ -1447,11 +1447,11 @@ static int snd_emu10k1x_midi(struct emu10k1x *emu)
return 0;
}
-static int __snd_emu10k1x_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
+static int snd_emu10k1x_probe(struct pci_dev *pci,
+ const struct pci_device_id *pci_id)
{
static int dev;
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
struct emu10k1x *chip;
int err;
@@ -1506,16 +1506,11 @@ static int __snd_emu10k1x_probe(struct pci_dev *pci,
return err;
pci_set_drvdata(pci, card);
+ card = NULL; /* probe succeeded, don't release as error */
dev++;
return 0;
}
-static int snd_emu10k1x_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
-{
- return snd_card_free_on_error(&pci->dev, __snd_emu10k1x_probe(pci, pci_id));
-}
-
// PCI IDs
static const struct pci_device_id snd_emu10k1x_ids[] = {
{ PCI_VDEVICE(CREATIVE, 0x0006) }, /* Dell OEM version (EMU10K1) */
diff --git a/sound/pci/ens1370.c b/sound/pci/ens1370.c
index 0b9eb55a2121..c2af7eab633c 100644
--- a/sound/pci/ens1370.c
+++ b/sound/pci/ens1370.c
@@ -2267,11 +2267,11 @@ static irqreturn_t snd_audiopci_interrupt(int irq, void *dev_id)
return IRQ_HANDLED;
}
-static int __snd_audiopci_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
+static int snd_audiopci_probe(struct pci_dev *pci,
+ const struct pci_device_id *pci_id)
{
static int dev;
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
struct ensoniq *ensoniq;
int err;
@@ -2328,16 +2328,11 @@ static int __snd_audiopci_probe(struct pci_dev *pci,
return err;
pci_set_drvdata(pci, card);
+ card = NULL; /* probe succeeded, don't release as error */
dev++;
return 0;
}
-static int snd_audiopci_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
-{
- return snd_card_free_on_error(&pci->dev, __snd_audiopci_probe(pci, pci_id));
-}
-
static struct pci_driver ens137x_driver = {
.name = KBUILD_MODNAME,
.id_table = snd_audiopci_ids,
diff --git a/sound/pci/es1938.c b/sound/pci/es1938.c
index 217beb9376ac..a612368fd0b1 100644
--- a/sound/pci/es1938.c
+++ b/sound/pci/es1938.c
@@ -1683,11 +1683,11 @@ static int snd_es1938_mixer(struct es1938 *chip)
}
-static int __snd_es1938_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
+static int snd_es1938_probe(struct pci_dev *pci,
+ const struct pci_device_id *pci_id)
{
static int dev;
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
struct es1938 *chip;
struct snd_opl3 *opl3;
int idx, err;
@@ -1759,16 +1759,11 @@ static int __snd_es1938_probe(struct pci_dev *pci,
return err;
pci_set_drvdata(pci, card);
+ card = NULL; /* probe succeeded, don't release as error */
dev++;
return 0;
}
-static int snd_es1938_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
-{
- return snd_card_free_on_error(&pci->dev, __snd_es1938_probe(pci, pci_id));
-}
-
static struct pci_driver es1938_driver = {
.name = KBUILD_MODNAME,
.id_table = snd_es1938_ids,
diff --git a/sound/pci/es1968.c b/sound/pci/es1968.c
index f04628c8cbb7..b0abbea2a992 100644
--- a/sound/pci/es1968.c
+++ b/sound/pci/es1968.c
@@ -2692,11 +2692,11 @@ static int snd_es1968_create(struct snd_card *card,
/*
*/
-static int __snd_es1968_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
+static int snd_es1968_probe(struct pci_dev *pci,
+ const struct pci_device_id *pci_id)
{
static int dev;
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
struct es1968 *chip;
unsigned int i;
int err;
@@ -2795,16 +2795,11 @@ static int __snd_es1968_probe(struct pci_dev *pci,
if (err < 0)
return err;
pci_set_drvdata(pci, card);
+ card = NULL; /* probe succeeded, don't release as error */
dev++;
return 0;
}
-static int snd_es1968_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
-{
- return snd_card_free_on_error(&pci->dev, __snd_es1968_probe(pci, pci_id));
-}
-
static struct pci_driver es1968_driver = {
.name = KBUILD_MODNAME,
.id_table = snd_es1968_ids,
diff --git a/sound/pci/fm801.c b/sound/pci/fm801.c
index 9cc96b807dd7..507ea0fe22a9 100644
--- a/sound/pci/fm801.c
+++ b/sound/pci/fm801.c
@@ -1264,11 +1264,11 @@ static int snd_fm801_create(struct snd_card *card,
return 0;
}
-static int __snd_card_fm801_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
+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 snd_card *card __free(snd_card_free) = NULL;
struct fm801 *chip;
struct snd_opl3 *opl3;
int err;
@@ -1325,16 +1325,11 @@ static int __snd_card_fm801_probe(struct pci_dev *pci,
if (err < 0)
return err;
pci_set_drvdata(pci, card);
+ card = NULL; /* probe succeeded, don't release as error */
dev++;
return 0;
}
-static int snd_card_fm801_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
-{
- return snd_card_free_on_error(&pci->dev, __snd_card_fm801_probe(pci, pci_id));
-}
-
static const unsigned char saved_regs[] = {
FM801_PCM_VOL, FM801_I2S_VOL, FM801_FM_VOL, FM801_REC_SRC,
FM801_PLY_CTRL, FM801_PLY_COUNT, FM801_PLY_BUF1, FM801_PLY_BUF2,
diff --git a/sound/pci/ice1712/ice1724.c b/sound/pci/ice1712/ice1724.c
index 859bb87393b4..651d92663567 100644
--- a/sound/pci/ice1712/ice1724.c
+++ b/sound/pci/ice1712/ice1724.c
@@ -2524,11 +2524,11 @@ static int snd_vt1724_create(struct snd_card *card,
*
*/
-static int __snd_vt1724_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
+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_card *card __free(snd_card_free) = NULL;
struct snd_ice1712 *ice;
int pcm_dev = 0, err;
const struct snd_ice1712_card_info *c;
@@ -2663,16 +2663,11 @@ static int __snd_vt1724_probe(struct pci_dev *pci,
if (err < 0)
return err;
pci_set_drvdata(pci, card);
+ card = NULL; /* probe succeeded, don't release as error */
dev++;
return 0;
}
-static int snd_vt1724_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
-{
- return snd_card_free_on_error(&pci->dev, __snd_vt1724_probe(pci, pci_id));
-}
-
#ifdef CONFIG_PM_SLEEP
static int snd_vt1724_suspend(struct device *dev)
{
diff --git a/sound/pci/intel8x0.c b/sound/pci/intel8x0.c
index e2ea9016a73e..339024182a26 100644
--- a/sound/pci/intel8x0.c
+++ b/sound/pci/intel8x0.c
@@ -3167,10 +3167,10 @@ static int check_default_spdif_aclink(struct pci_dev *pci)
return 0;
}
-static int __snd_intel8x0_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
+static int snd_intel8x0_probe(struct pci_dev *pci,
+ const struct pci_device_id *pci_id)
{
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
struct intel8x0 *chip;
int err;
struct shortname_table *name;
@@ -3244,13 +3244,9 @@ static int __snd_intel8x0_probe(struct pci_dev *pci,
return err;
pci_set_drvdata(pci, card);
- return 0;
-}
+ card = NULL; /* probe succeeded, don't release as error */
-static int snd_intel8x0_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
-{
- return snd_card_free_on_error(&pci->dev, __snd_intel8x0_probe(pci, pci_id));
+ return 0;
}
static struct pci_driver intel8x0_driver = {
diff --git a/sound/pci/intel8x0m.c b/sound/pci/intel8x0m.c
index 27dbf6125427..38a15ebf94b2 100644
--- a/sound/pci/intel8x0m.c
+++ b/sound/pci/intel8x0m.c
@@ -1218,10 +1218,10 @@ static struct shortname_table {
{ 0 },
};
-static int __snd_intel8x0m_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
+static int snd_intel8x0m_probe(struct pci_dev *pci,
+ const struct pci_device_id *pci_id)
{
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
struct intel8x0m *chip;
int err;
struct shortname_table *name;
@@ -1262,15 +1262,10 @@ static int __snd_intel8x0m_probe(struct pci_dev *pci,
if (err < 0)
return err;
pci_set_drvdata(pci, card);
+ card = NULL; /* probe succeeded, don't release as error */
return 0;
}
-static int snd_intel8x0m_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
-{
- return snd_card_free_on_error(&pci->dev, __snd_intel8x0m_probe(pci, pci_id));
-}
-
static struct pci_driver intel8x0m_driver = {
.name = KBUILD_MODNAME,
.id_table = snd_intel8x0m_ids,
diff --git a/sound/pci/korg1212/korg1212.c b/sound/pci/korg1212/korg1212.c
index f4d8402f835f..bb442b556158 100644
--- a/sound/pci/korg1212/korg1212.c
+++ b/sound/pci/korg1212/korg1212.c
@@ -2216,7 +2216,7 @@ snd_korg1212_probe(struct pci_dev *pci,
{
static int dev;
struct snd_korg1212 *korg1212;
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
int err;
if (dev >= SNDRV_CARDS) {
@@ -2234,7 +2234,7 @@ snd_korg1212_probe(struct pci_dev *pci,
err = snd_korg1212_create(card, pci);
if (err < 0)
- goto error;
+ return err;
strscpy(card->driver, "korg1212");
strscpy(card->shortname, "korg1212");
@@ -2245,14 +2245,11 @@ snd_korg1212_probe(struct pci_dev *pci,
err = snd_card_register(card);
if (err < 0)
- goto error;
+ return err;
pci_set_drvdata(pci, card);
+ card = NULL; /* probe succeeded, don't release as error */
dev++;
return 0;
-
- error:
- snd_card_free(card);
- return err;
}
static struct pci_driver korg1212_driver = {
diff --git a/sound/pci/lola/lola.c b/sound/pci/lola/lola.c
index 34a3ba17deb4..1f7ffca57fc9 100644
--- a/sound/pci/lola/lola.c
+++ b/sound/pci/lola/lola.c
@@ -641,11 +641,11 @@ static int lola_create(struct snd_card *card, struct pci_dev *pci, int dev)
return 0;
}
-static int __lola_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
+static int lola_probe(struct pci_dev *pci,
+ const struct pci_device_id *pci_id)
{
static int dev;
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
struct lola *chip;
int err;
@@ -687,16 +687,11 @@ static int __lola_probe(struct pci_dev *pci,
return err;
pci_set_drvdata(pci, card);
+ card = NULL; /* probe succeeded, don't release as error */
dev++;
return 0;
}
-static int lola_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
-{
- return snd_card_free_on_error(&pci->dev, __lola_probe(pci, pci_id));
-}
-
/* PCI IDs */
static const struct pci_device_id lola_ids[] = {
{ PCI_VDEVICE(DIGIGRAM, 0x0001) },
diff --git a/sound/pci/lx6464es/lx6464es.c b/sound/pci/lx6464es/lx6464es.c
index 3b40ec071379..881f462fa929 100644
--- a/sound/pci/lx6464es/lx6464es.c
+++ b/sound/pci/lx6464es/lx6464es.c
@@ -981,7 +981,7 @@ static int snd_lx6464es_probe(struct pci_dev *pci,
const struct pci_device_id *pci_id)
{
static int dev;
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
struct lx6464es *chip;
int err;
@@ -1003,7 +1003,7 @@ static int snd_lx6464es_probe(struct pci_dev *pci,
err = snd_lx6464es_create(card, pci);
if (err < 0) {
dev_err(card->dev, "error during snd_lx6464es_create\n");
- goto error;
+ return err;
}
strscpy(card->driver, "LX6464ES");
@@ -1020,16 +1020,13 @@ static int snd_lx6464es_probe(struct pci_dev *pci,
err = snd_card_register(card);
if (err < 0)
- goto error;
+ return err;
dev_dbg(chip->card->dev, "initialization successful\n");
pci_set_drvdata(pci, card);
+ card = NULL; /* probe succeeded, don't release as error */
dev++;
return 0;
-
- error:
- snd_card_free(card);
- return err;
}
static struct pci_driver lx6464es_driver = {
diff --git a/sound/pci/lx6464es/lx_core.c b/sound/pci/lx6464es/lx_core.c
index 9909f1067871..ae265dd19333 100644
--- a/sound/pci/lx6464es/lx_core.c
+++ b/sound/pci/lx6464es/lx_core.c
@@ -364,7 +364,7 @@ int lx_dsp_get_mac(struct lx6464es *chip)
macmsb = lx_dsp_reg_read(chip, eReg_ADMACESMSB) & 0x00FFFFFF;
maclsb = lx_dsp_reg_read(chip, eReg_ADMACESLSB) & 0x00FFFFFF;
- /* todo: endianess handling */
+ /* todo: endianness handling */
chip->mac_address[5] = ((u8 *)(&maclsb))[0];
chip->mac_address[4] = ((u8 *)(&maclsb))[1];
chip->mac_address[3] = ((u8 *)(&maclsb))[2];
diff --git a/sound/pci/lx6464es/lx_defs.h b/sound/pci/lx6464es/lx_defs.h
index eca5367ba561..89e433d3f7a2 100644
--- a/sound/pci/lx6464es/lx_defs.h
+++ b/sound/pci/lx6464es/lx_defs.h
@@ -315,7 +315,7 @@ enum stream_flags {
#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 */
+/* Complete BOARD error code for the invalid standard object class */
#define EB_ISO (ERROR_VALUE | E_SOURCE_BOARD | \
E_CLASS_INVALID_STD_OBJECT)
#define EB_INVALID_EFFECT (EB_ISO | 0x00)
diff --git a/sound/pci/maestro3.c b/sound/pci/maestro3.c
index dd45ffa171e1..2114f9f16a34 100644
--- a/sound/pci/maestro3.c
+++ b/sound/pci/maestro3.c
@@ -2629,10 +2629,10 @@ snd_m3_create(struct snd_card *card, struct pci_dev *pci,
/*
*/
static int
-__snd_m3_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
+snd_m3_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
{
static int dev;
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
struct snd_m3 *chip;
int err;
@@ -2690,16 +2690,11 @@ __snd_m3_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
#endif
pci_set_drvdata(pci, card);
+ card = NULL; /* probe succeeded, don't release as error */
dev++;
return 0;
}
-static int
-snd_m3_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
-{
- return snd_card_free_on_error(&pci->dev, __snd_m3_probe(pci, pci_id));
-}
-
static struct pci_driver m3_driver = {
.name = KBUILD_MODNAME,
.id_table = snd_m3_ids,
diff --git a/sound/pci/oxygen/oxygen_lib.c b/sound/pci/oxygen/oxygen_lib.c
index 6b096d654f9f..587785d5451b 100644
--- a/sound/pci/oxygen/oxygen_lib.c
+++ b/sound/pci/oxygen/oxygen_lib.c
@@ -572,7 +572,7 @@ static void oxygen_card_free(struct snd_card *card)
mutex_destroy(&chip->mutex);
}
-static int __oxygen_pci_probe(struct pci_dev *pci, int index, char *id,
+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,
@@ -580,7 +580,7 @@ static int __oxygen_pci_probe(struct pci_dev *pci, int index, char *id,
)
)
{
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
struct oxygen *chip;
const struct pci_device_id *pci_id;
int err;
@@ -695,18 +695,9 @@ static int __oxygen_pci_probe(struct pci_dev *pci, int index, char *id,
return err;
pci_set_drvdata(pci, card);
+ card = NULL; /* probe succeeded, don't release as error */
return 0;
}
-
-int oxygen_pci_probe(struct pci_dev *pci, int index, char *id,
- struct module *owner,
- const struct pci_device_id *ids,
- int (*get_model)(struct oxygen *chip,
- const struct pci_device_id *id))
-{
- return snd_card_free_on_error(&pci->dev,
- __oxygen_pci_probe(pci, index, id, owner, ids, get_model));
-}
EXPORT_SYMBOL(oxygen_pci_probe);
static int oxygen_pci_suspend(struct device *dev)
diff --git a/sound/pci/oxygen/oxygen_pcm.c b/sound/pci/oxygen/oxygen_pcm.c
index b716356010b8..75efbf59a675 100644
--- a/sound/pci/oxygen/oxygen_pcm.c
+++ b/sound/pci/oxygen/oxygen_pcm.c
@@ -171,16 +171,14 @@ static int oxygen_open(struct snd_pcm_substream *substream,
snd_pcm_set_sync(substream);
chip->streams[channel] = substream;
- guard(mutex)(&chip->mutex);
- chip->pcm_active |= 1 << channel;
- if (channel == PCM_SPDIF) {
- chip->spdif_pcm_bits = chip->spdif_bits;
- chip->controls[CONTROL_SPDIF_PCM]->vd[0].access &=
- ~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
- snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE |
- SNDRV_CTL_EVENT_MASK_INFO,
- &chip->controls[CONTROL_SPDIF_PCM]->id);
+ scoped_guard(mutex, &chip->mutex) {
+ chip->pcm_active |= 1 << channel;
+ if (channel == PCM_SPDIF)
+ chip->spdif_pcm_bits = chip->spdif_bits;
}
+ if (channel == PCM_SPDIF)
+ snd_ctl_activate_id(chip->card,
+ &chip->controls[CONTROL_SPDIF_PCM]->id, 1);
return 0;
}
@@ -220,19 +218,16 @@ static int oxygen_close(struct snd_pcm_substream *substream)
struct oxygen *chip = snd_pcm_substream_chip(substream);
unsigned int channel = oxygen_substream_channel(substream);
- guard(mutex)(&chip->mutex);
- chip->pcm_active &= ~(1 << channel);
- if (channel == PCM_SPDIF) {
- chip->controls[CONTROL_SPDIF_PCM]->vd[0].access |=
- SNDRV_CTL_ELEM_ACCESS_INACTIVE;
- snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE |
- SNDRV_CTL_EVENT_MASK_INFO,
- &chip->controls[CONTROL_SPDIF_PCM]->id);
- }
- if (channel == PCM_SPDIF || channel == PCM_MULTICH)
- oxygen_update_spdif_source(chip);
+ scoped_guard(mutex, &chip->mutex) {
+ chip->pcm_active &= ~(1 << channel);
+ if (channel == PCM_SPDIF || channel == PCM_MULTICH)
+ oxygen_update_spdif_source(chip);
- chip->streams[channel] = NULL;
+ chip->streams[channel] = NULL;
+ }
+ if (channel == PCM_SPDIF)
+ snd_ctl_activate_id(chip->card,
+ &chip->controls[CONTROL_SPDIF_PCM]->id, 0);
return 0;
}
diff --git a/sound/pci/pcxhr/pcxhr_core.c b/sound/pci/pcxhr/pcxhr_core.c
index b3b9ab4f303e..f855d3bfad4b 100644
--- a/sound/pci/pcxhr/pcxhr_core.c
+++ b/sound/pci/pcxhr/pcxhr_core.c
@@ -802,7 +802,7 @@ static int pcxhr_prepair_pipe_start(struct pcxhr_mgr *mgr,
err);
return err;
}
- /* if the pipe couldn't be prepaired for start,
+ /* if the pipe couldn't be prepared for start,
* retry it later
*/
if (rmh.stat[0] == 0)
@@ -913,7 +913,7 @@ int pcxhr_set_pipe_state(struct pcxhr_mgr *mgr, int playback_mask,
if (err)
return err;
if (state == 0)
- break; /* success, all pipes prepaired */
+ break; /* success, all pipes prepared */
mdelay(1); /* wait 1 millisecond and retry */
}
} else {
diff --git a/sound/pci/riptide/riptide.c b/sound/pci/riptide/riptide.c
index 99c00e46ce23..0d9110558ce4 100644
--- a/sound/pci/riptide/riptide.c
+++ b/sound/pci/riptide/riptide.c
@@ -264,7 +264,7 @@ MODULE_PARM_DESC(opl3_port, "OPL3 port # for Riptide driver.");
#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 EOS_STATUS 0x40000000 /* : stopped */
#define EOC_STATUS 0x20000000 /* : stream end */
#define ERR_STATUS 0x10000000
#define EMPTY_STATUS 0x08000000
@@ -453,7 +453,7 @@ struct snd_riptide {
int in_suspend;
};
-struct sgd { /* scatter gather desriptor */
+struct sgd { /* scatter gather descriptor */
__le32 dwNextLink;
__le32 dwSegPtrPhys;
__le32 dwSegLen;
@@ -2020,10 +2020,10 @@ static void snd_riptide_joystick_remove(struct pci_dev *pci)
#endif
static int
-__snd_card_riptide_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
+snd_card_riptide_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
{
static int dev;
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
struct snd_riptide *chip;
unsigned short val;
int err;
@@ -2117,16 +2117,11 @@ __snd_card_riptide_probe(struct pci_dev *pci, const struct pci_device_id *pci_id
if (err < 0)
return err;
pci_set_drvdata(pci, card);
+ card = NULL; /* probe succeeded, don't release as error */
dev++;
return 0;
}
-static int
-snd_card_riptide_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
-{
- return snd_card_free_on_error(&pci->dev, __snd_card_riptide_probe(pci, pci_id));
-}
-
static struct pci_driver driver = {
.name = KBUILD_MODNAME,
.id_table = snd_riptide_ids,
diff --git a/sound/pci/rme32.c b/sound/pci/rme32.c
index 454a30a2c07e..7ea9a71aaab9 100644
--- a/sound/pci/rme32.c
+++ b/sound/pci/rme32.c
@@ -8,7 +8,7 @@
* Thanks to : Anders Torger <torger@ludd.luth.se>,
* Henk Hesselink <henk@anda.nl>
* for writing the digi96-driver
- * and RME for all informations.
+ * and RME for all information.
*
* ****************************************************************************
*
@@ -1810,11 +1810,11 @@ static void snd_rme32_card_free(struct snd_card *card)
}
static int
-__snd_rme32_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
+snd_rme32_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
{
static int dev;
struct rme32 *rme32;
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
int err;
if (dev >= SNDRV_CARDS) {
@@ -1858,16 +1858,11 @@ __snd_rme32_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
if (err < 0)
return err;
pci_set_drvdata(pci, card);
+ card = NULL; /* probe succeeded, don't release as error */
dev++;
return 0;
}
-static int
-snd_rme32_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
-{
- return snd_card_free_on_error(&pci->dev, __snd_rme32_probe(pci, pci_id));
-}
-
static struct pci_driver rme32_driver = {
.name = KBUILD_MODNAME,
.id_table = snd_rme32_ids,
diff --git a/sound/pci/rme96.c b/sound/pci/rme96.c
index 892fcc598557..926783f0410e 100644
--- a/sound/pci/rme96.c
+++ b/sound/pci/rme96.c
@@ -2356,12 +2356,12 @@ static void snd_rme96_card_free(struct snd_card *card)
}
static int
-__snd_rme96_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
+snd_rme96_probe(struct pci_dev *pci,
+ const struct pci_device_id *pci_id)
{
static int dev;
struct rme96 *rme96;
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
int err;
u8 val;
@@ -2420,16 +2420,11 @@ __snd_rme96_probe(struct pci_dev *pci,
return err;
pci_set_drvdata(pci, card);
+ card = NULL; /* probe succeeded, don't release as error */
dev++;
return 0;
}
-static int snd_rme96_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
-{
- return snd_card_free_on_error(&pci->dev, __snd_rme96_probe(pci, pci_id));
-}
-
static struct pci_driver rme96_driver = {
.name = KBUILD_MODNAME,
.id_table = snd_rme96_ids,
diff --git a/sound/pci/rme9652/hdsp.c b/sound/pci/rme9652/hdsp.c
index 31cc2d91c8d2..7dce548b9f36 100644
--- a/sound/pci/rme9652/hdsp.c
+++ b/sound/pci/rme9652/hdsp.c
@@ -3664,7 +3664,7 @@ snd_hdsp_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
snd_iprintf(buffer, "\n");
- /* Informations about H9632 specific controls */
+ /* Information about H9632 specific controls */
if (hdsp->io_type == H9632) {
char *tmp;
@@ -5328,7 +5328,7 @@ static int snd_hdsp_probe(struct pci_dev *pci,
{
static int dev;
struct hdsp *hdsp;
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
int err;
if (dev >= SNDRV_CARDS)
@@ -5349,21 +5349,18 @@ static int snd_hdsp_probe(struct pci_dev *pci,
hdsp->pci = pci;
err = snd_hdsp_create(card, hdsp);
if (err)
- goto error;
+ return err;
strscpy(card->shortname, "Hammerfall DSP");
sprintf(card->longname, "%s at 0x%lx, irq %d", hdsp->card_name,
hdsp->port, hdsp->irq);
err = snd_card_register(card);
if (err)
- goto error;
+ return err;
pci_set_drvdata(pci, card);
+ card = NULL; /* probe succeeded, don't release as error */
dev++;
return 0;
-
- error:
- snd_card_free(card);
- return err;
}
static struct pci_driver hdsp_driver = {
diff --git a/sound/pci/rme9652/hdspm.c b/sound/pci/rme9652/hdspm.c
index 95fd82bb1c6d..cc4cf43e5a5b 100644
--- a/sound/pci/rme9652/hdspm.c
+++ b/sound/pci/rme9652/hdspm.c
@@ -6796,7 +6796,7 @@ static int snd_hdspm_probe(struct pci_dev *pci,
{
static int dev;
struct hdspm *hdspm;
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
int err;
if (dev >= SNDRV_CARDS)
@@ -6818,7 +6818,7 @@ static int snd_hdspm_probe(struct pci_dev *pci,
err = snd_hdspm_create(card, hdspm);
if (err < 0)
- goto error;
+ return err;
if (hdspm->io_type != MADIface) {
snprintf(card->shortname, sizeof(card->shortname), "%s_%x",
@@ -6837,16 +6837,12 @@ static int snd_hdspm_probe(struct pci_dev *pci,
err = snd_card_register(card);
if (err < 0)
- goto error;
+ return err;
pci_set_drvdata(pci, card);
-
+ card = NULL; /* probe succeeded, don't release as error */
dev++;
return 0;
-
- error:
- snd_card_free(card);
- return err;
}
static struct pci_driver hdspm_driver = {
diff --git a/sound/pci/rme9652/rme9652.c b/sound/pci/rme9652/rme9652.c
index 3be30cb57a2e..46ed6b7a2c53 100644
--- a/sound/pci/rme9652/rme9652.c
+++ b/sound/pci/rme9652/rme9652.c
@@ -2472,7 +2472,7 @@ static int snd_rme9652_probe(struct pci_dev *pci,
{
static int dev;
struct snd_rme9652 *rme9652;
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
int err;
if (dev >= SNDRV_CARDS)
@@ -2494,7 +2494,7 @@ static int snd_rme9652_probe(struct pci_dev *pci,
rme9652->pci = pci;
err = snd_rme9652_create(card, rme9652, precise_ptr[dev]);
if (err)
- goto error;
+ return err;
strscpy(card->shortname, rme9652->card_name);
@@ -2502,14 +2502,11 @@ static int snd_rme9652_probe(struct pci_dev *pci,
card->shortname, rme9652->port, rme9652->irq);
err = snd_card_register(card);
if (err)
- goto error;
+ return err;
pci_set_drvdata(pci, card);
+ card = NULL; /* probe succeeded, don't release as error */
dev++;
return 0;
-
- error:
- snd_card_free(card);
- return err;
}
static struct pci_driver rme9652_driver = {
diff --git a/sound/pci/sis7019.c b/sound/pci/sis7019.c
index 55c1b11a2900..fe5bda5af6a5 100644
--- a/sound/pci/sis7019.c
+++ b/sound/pci/sis7019.c
@@ -1309,10 +1309,10 @@ static int sis_chip_create(struct snd_card *card,
return 0;
}
-static int __snd_sis7019_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
+static int snd_sis7019_probe(struct pci_dev *pci,
+ const struct pci_device_id *pci_id)
{
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
struct sis7019 *sis;
int rc;
@@ -1361,15 +1361,10 @@ static int __snd_sis7019_probe(struct pci_dev *pci,
return rc;
pci_set_drvdata(pci, card);
+ card = NULL; /* probe succeeded, don't release as error */
return 0;
}
-static int snd_sis7019_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
-{
- return snd_card_free_on_error(&pci->dev, __snd_sis7019_probe(pci, pci_id));
-}
-
static struct pci_driver sis7019_driver = {
.name = KBUILD_MODNAME,
.id_table = snd_sis7019_ids,
diff --git a/sound/pci/sonicvibes.c b/sound/pci/sonicvibes.c
index a885d544acd6..b5bb20440c6e 100644
--- a/sound/pci/sonicvibes.c
+++ b/sound/pci/sonicvibes.c
@@ -1362,11 +1362,11 @@ static int snd_sonicvibes_midi(struct sonicvibes *sonic,
return 0;
}
-static int __snd_sonic_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
+static int snd_sonic_probe(struct pci_dev *pci,
+ const struct pci_device_id *pci_id)
{
static int dev;
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
struct sonicvibes *sonic;
struct snd_rawmidi *midi_uart;
struct snd_opl3 *opl3;
@@ -1430,16 +1430,11 @@ static int __snd_sonic_probe(struct pci_dev *pci,
return err;
pci_set_drvdata(pci, card);
+ card = NULL; /* probe succeeded, don't release as error */
dev++;
return 0;
}
-static int snd_sonic_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
-{
- return snd_card_free_on_error(&pci->dev, __snd_sonic_probe(pci, pci_id));
-}
-
static struct pci_driver sonicvibes_driver = {
.name = KBUILD_MODNAME,
.id_table = snd_sonic_ids,
diff --git a/sound/pci/trident/trident_memory.c b/sound/pci/trident/trident_memory.c
index 81f6348191dc..4ed0e7d68322 100644
--- a/sound/pci/trident/trident_memory.c
+++ b/sound/pci/trident/trident_memory.c
@@ -27,7 +27,7 @@
#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 */
+#define MAX_ALIGN_PAGES SNDRV_TRIDENT_MAX_PAGES /* maximum aligned pages */
/* fill TLB entrie(s) corresponding to page with ptr */
#define set_tlb_bus(trident,page,addr) __set_tlb_bus(trident,page,addr)
/* fill TLB entrie(s) corresponding to page with silence pointer */
diff --git a/sound/pci/via82xx.c b/sound/pci/via82xx.c
index 24ee1302f27d..c13fc554c0c8 100644
--- a/sound/pci/via82xx.c
+++ b/sound/pci/via82xx.c
@@ -1242,11 +1242,7 @@ static int snd_via8233_playback_open(struct snd_pcm_substream *substream)
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);
+ snd_ctl_activate_id(chip->card, &chip->dxs_controls[stream]->id, 1);
}
return 0;
}
@@ -1333,10 +1329,7 @@ static int snd_via8233_playback_close(struct snd_pcm_substream *substream)
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);
+ snd_ctl_activate_id(chip->card, &chip->dxs_controls[stream]->id, 0);
}
return snd_via82xx_pcm_close(substream);
}
@@ -2435,10 +2428,10 @@ static int check_dxs_list(struct pci_dev *pci, int revision)
return VIA_DXS_48K;
};
-static int __snd_via82xx_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
+static int snd_via82xx_probe(struct pci_dev *pci,
+ const struct pci_device_id *pci_id)
{
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
struct via82xx *chip;
int chip_type = 0, card_type;
unsigned int i;
@@ -2543,15 +2536,10 @@ static int __snd_via82xx_probe(struct pci_dev *pci,
if (err < 0)
return err;
pci_set_drvdata(pci, card);
+ card = NULL; /* probe succeeded, don't release as error */
return 0;
}
-static int snd_via82xx_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
-{
- return snd_card_free_on_error(&pci->dev, __snd_via82xx_probe(pci, pci_id));
-}
-
static struct pci_driver via82xx_driver = {
.name = KBUILD_MODNAME,
.id_table = snd_via82xx_ids,
diff --git a/sound/pci/via82xx_modem.c b/sound/pci/via82xx_modem.c
index b32f84ac17cc..7934bc5d6463 100644
--- a/sound/pci/via82xx_modem.c
+++ b/sound/pci/via82xx_modem.c
@@ -1088,10 +1088,10 @@ static int snd_via82xx_create(struct snd_card *card,
}
-static int __snd_via82xx_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
+static int snd_via82xx_probe(struct pci_dev *pci,
+ const struct pci_device_id *pci_id)
{
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
struct via82xx_modem *chip;
int chip_type = 0, card_type;
unsigned int i;
@@ -1139,15 +1139,10 @@ static int __snd_via82xx_probe(struct pci_dev *pci,
if (err < 0)
return err;
pci_set_drvdata(pci, card);
+ card = NULL; /* probe succeeded, don't release as error */
return 0;
}
-static int snd_via82xx_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
-{
- return snd_card_free_on_error(&pci->dev, __snd_via82xx_probe(pci, pci_id));
-}
-
static struct pci_driver via82xx_modem_driver = {
.name = KBUILD_MODNAME,
.id_table = snd_via82xx_modem_ids,
diff --git a/sound/pci/vx222/vx222_ops.c b/sound/pci/vx222/vx222_ops.c
index b6459dbdb1b0..40f3716c478f 100644
--- a/sound/pci/vx222/vx222_ops.c
+++ b/sound/pci/vx222/vx222_ops.c
@@ -431,7 +431,7 @@ static int vx2_load_dsp(struct vx_core *vx, int index, const struct firmware *ds
/*
* vx_test_and_ack - test and acknowledge interrupt
*
- * called from irq hander, too
+ * called from irq handler, too
*
* spinlock held!
*/
diff --git a/sound/pci/ymfpci/ymfpci.c b/sound/pci/ymfpci/ymfpci.c
index d3fb047c9a45..5e8c7e318138 100644
--- a/sound/pci/ymfpci/ymfpci.c
+++ b/sound/pci/ymfpci/ymfpci.c
@@ -152,11 +152,11 @@ static inline int snd_ymfpci_create_gameport(struct snd_ymfpci *chip, int dev, i
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 snd_card_ymfpci_probe(struct pci_dev *pci,
+ const struct pci_device_id *pci_id)
{
static int dev;
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
struct resource *fm_res = NULL;
struct resource *mpu_res = NULL;
struct snd_ymfpci *chip;
@@ -344,16 +344,11 @@ static int __snd_card_ymfpci_probe(struct pci_dev *pci,
return err;
pci_set_drvdata(pci, card);
+ card = NULL; /* probe succeeded, don't release as error */
dev++;
return 0;
}
-static int snd_card_ymfpci_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
-{
- return snd_card_free_on_error(&pci->dev, __snd_card_ymfpci_probe(pci, pci_id));
-}
-
static struct pci_driver ymfpci_driver = {
.name = KBUILD_MODNAME,
.id_table = snd_ymfpci_ids,
diff --git a/sound/pcmcia/vx/vxp_ops.c b/sound/pcmcia/vx/vxp_ops.c
index 4211e7239138..e52b61125629 100644
--- a/sound/pcmcia/vx/vxp_ops.c
+++ b/sound/pcmcia/vx/vxp_ops.c
@@ -265,7 +265,7 @@ static int vxp_load_dsp(struct vx_core *vx, int index, const struct firmware *fw
/*
* vx_test_and_ack - test and acknowledge interrupt
*
- * called from irq hander, too
+ * called from irq handler, too
*
* spinlock held!
*/
diff --git a/sound/ppc/powermac.c b/sound/ppc/powermac.c
index e685d245883e..30f4cd1a6d32 100644
--- a/sound/ppc/powermac.c
+++ b/sound/ppc/powermac.c
@@ -39,18 +39,18 @@ static struct platform_device *device;
static int snd_pmac_probe(struct platform_device *devptr)
{
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
struct snd_pmac *chip;
char *name_ext;
int err;
- err = snd_card_new(&devptr->dev, index, id, THIS_MODULE, 0, &card);
+ err = snd_devm_card_new(&devptr->dev, index, id, THIS_MODULE, 0, &card);
if (err < 0)
return err;
err = snd_pmac_new(card, &chip);
if (err < 0)
- goto __error;
+ return err;
card->private_data = chip;
switch (chip->model) {
@@ -61,7 +61,7 @@ static int snd_pmac_probe(struct platform_device *devptr)
card->shortname, chip->device_id, chip->subframe);
err = snd_pmac_burgundy_init(chip);
if (err < 0)
- goto __error;
+ return err;
break;
case PMAC_DACA:
strscpy(card->driver, "PMac DACA");
@@ -70,7 +70,7 @@ static int snd_pmac_probe(struct platform_device *devptr)
card->shortname, chip->device_id, chip->subframe);
err = snd_pmac_daca_init(chip);
if (err < 0)
- goto __error;
+ return err;
break;
case PMAC_TUMBLER:
case PMAC_SNAPPER:
@@ -81,10 +81,10 @@ static int snd_pmac_probe(struct platform_device *devptr)
card->shortname, chip->device_id, chip->subframe);
err = snd_pmac_tumbler_init(chip);
if (err < 0)
- goto __error;
+ return err;
err = snd_pmac_tumbler_post_init();
if (err < 0)
- goto __error;
+ return err;
break;
case PMAC_AWACS:
case PMAC_SCREAMER:
@@ -101,17 +101,16 @@ static int snd_pmac_probe(struct platform_device *devptr)
card->shortname, name_ext, chip->revision);
err = snd_pmac_awacs_init(chip);
if (err < 0)
- goto __error;
+ return err;
break;
default:
dev_err(&devptr->dev, "unsupported hardware %d\n", chip->model);
- err = -EINVAL;
- goto __error;
+ return -EINVAL;
}
err = snd_pmac_pcm_new(chip);
if (err < 0)
- goto __error;
+ return err;
chip->initialized = 1;
if (enable_beep)
@@ -119,20 +118,11 @@ static int snd_pmac_probe(struct platform_device *devptr)
err = snd_card_register(card);
if (err < 0)
- goto __error;
+ return err;
platform_set_drvdata(devptr, card);
+ card = NULL; /* probe succeeded, don't release as error */
return 0;
-
-__error:
- snd_card_free(card);
- return err;
-}
-
-
-static void snd_pmac_remove(struct platform_device *devptr)
-{
- snd_card_free(platform_get_drvdata(devptr));
}
#ifdef CONFIG_PM_SLEEP
@@ -160,7 +150,6 @@ static SIMPLE_DEV_PM_OPS(snd_pmac_pm, snd_pmac_driver_suspend, snd_pmac_driver_r
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,
diff --git a/sound/ppc/tumbler.c b/sound/ppc/tumbler.c
index 139c7b25927a..7ba52e7a24d7 100644
--- a/sound/ppc/tumbler.c
+++ b/sound/ppc/tumbler.c
@@ -1134,7 +1134,7 @@ static long tumbler_find_device(const char *device, const char *platform,
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
+ * open collector information out of the do-platform script
* as we don't yet have an interpreter for these things
*/
if (platform)
diff --git a/sound/sh/sh_dac_audio.c b/sound/sh/sh_dac_audio.c
index 2d9a2d5b824e..5896cce424af 100644
--- a/sound/sh/sh_dac_audio.c
+++ b/sound/sh/sh_dac_audio.c
@@ -236,12 +236,6 @@ static int snd_sh_dac_pcm(struct snd_sh_dac *chip, int device)
/* END OF PCM INTERFACE */
-/* driver .remove -- destructor */
-static void snd_sh_dac_remove(struct platform_device *devptr)
-{
- snd_card_free(platform_get_drvdata(devptr));
-}
-
/* free -- it has been defined by create */
static int snd_sh_dac_free(struct snd_sh_dac *chip)
{
@@ -341,10 +335,10 @@ static int snd_sh_dac_create(struct snd_card *card,
static int snd_sh_dac_probe(struct platform_device *devptr)
{
struct snd_sh_dac *chip;
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
int err;
- err = snd_card_new(&devptr->dev, index, id, THIS_MODULE, 0, &card);
+ err = snd_devm_card_new(&devptr->dev, index, id, THIS_MODULE, 0, &card);
if (err < 0) {
dev_err(&devptr->dev, "cannot allocate the card\n");
return err;
@@ -352,11 +346,11 @@ static int snd_sh_dac_probe(struct platform_device *devptr)
err = snd_sh_dac_create(card, devptr, &chip);
if (err < 0)
- goto probe_error;
+ return err;
err = snd_sh_dac_pcm(chip, 0);
if (err < 0)
- goto probe_error;
+ return err;
strscpy(card->driver, "snd_sh_dac");
strscpy(card->shortname, "SuperH DAC audio driver");
@@ -364,16 +358,13 @@ static int snd_sh_dac_probe(struct platform_device *devptr)
err = snd_card_register(card);
if (err < 0)
- goto probe_error;
+ return err;
dev_info(&devptr->dev, "ALSA driver for SuperH DAC audio\n");
platform_set_drvdata(devptr, card);
+ card = NULL; /* probe succeeded, don't release as error */
return 0;
-
-probe_error:
- snd_card_free(card);
- return err;
}
/*
@@ -381,7 +372,6 @@ probe_error:
*/
static struct platform_driver sh_dac_driver = {
.probe = snd_sh_dac_probe,
- .remove = snd_sh_dac_remove,
.driver = {
.name = "dac_audio",
},
diff --git a/sound/soc/codecs/hda.c b/sound/soc/codecs/hda.c
index 237b0b060457..5188c9d74c8b 100644
--- a/sound/soc/codecs/hda.c
+++ b/sound/soc/codecs/hda.c
@@ -218,6 +218,9 @@ static int hda_codec_probe(struct snd_soc_component *component)
goto err;
}
+ /* unsol events are still blocked until registered */
+ codec->core.unsol_disabled = false;
+
ret = driver->ops->probe(codec, codec->preset);
if (ret < 0) {
dev_err(&hdev->dev, "codec init failed: %d\n", ret);
@@ -276,6 +279,10 @@ static void hda_codec_remove(struct snd_soc_component *component)
/* Don't allow any more runtime suspends */
pm_runtime_forbid(&hdev->dev);
+ /* stop asynchronous jack handling before freeing driver resources */
+ snd_hdac_device_disable_unsol(hdev);
+ cancel_delayed_work_sync(&codec->jackpoll_work);
+
hda_codec_unregister_dais(codec, component);
if (driver->ops->remove)
diff --git a/sound/soc/codecs/tas675x.c b/sound/soc/codecs/tas675x.c
index 404706b62156..82eeaceb1dac 100644
--- a/sound/soc/codecs/tas675x.c
+++ b/sound/soc/codecs/tas675x.c
@@ -1379,10 +1379,7 @@ static int tas675x_mute_stream(struct snd_soc_dai *dai, int mute, int direction)
int ret;
if (direction == SNDRV_PCM_STREAM_CAPTURE) {
- if (mute)
- clear_bit(dai->id, &tas->active_capture_dais);
- else
- set_bit(dai->id, &tas->active_capture_dais);
+ assign_bit(dai->id, &tas->active_capture_dais, !mute);
return 0;
}
@@ -1391,10 +1388,7 @@ static int tas675x_mute_stream(struct snd_soc_dai *dai, int mute, int direction)
* The TAS675x has two playback DAIs (main audio and LLP).
* Only transition to SLEEP when ALL are muted.
*/
- if (mute)
- clear_bit(dai->id, &tas->active_playback_dais);
- else
- set_bit(dai->id, &tas->active_playback_dais);
+ assign_bit(dai->id, &tas->active_playback_dais, !mute);
/* Last playback stream */
if (mute && !READ_ONCE(tas->active_playback_dais)) {
diff --git a/sound/soc/fsl/fsl_asrc_m2m.c b/sound/soc/fsl/fsl_asrc_m2m.c
index 4bc40f328f58..42b0e21f281c 100644
--- a/sound/soc/fsl/fsl_asrc_m2m.c
+++ b/sound/soc/fsl/fsl_asrc_m2m.c
@@ -475,6 +475,7 @@ static int fsl_asrc_m2m_comp_task_create(struct snd_compr_stream *stream,
task->input = dma_buf_export(&exp_info_in);
if (IS_ERR(task->input)) {
ret = PTR_ERR(task->input);
+ task->input = NULL;
return ret;
}
@@ -485,6 +486,7 @@ static int fsl_asrc_m2m_comp_task_create(struct snd_compr_stream *stream,
task->output = dma_buf_export(&exp_info_out);
if (IS_ERR(task->output)) {
ret = PTR_ERR(task->output);
+ task->output = NULL;
return ret;
}
diff --git a/sound/soc/sof/intel/hda-codec.c b/sound/soc/sof/intel/hda-codec.c
index fd371850b0d6..91f7ca22a4a7 100644
--- a/sound/soc/sof/intel/hda-codec.c
+++ b/sound/soc/sof/intel/hda-codec.c
@@ -89,7 +89,7 @@ void hda_codec_jack_wake_enable(struct snd_sof_dev *sdev, bool enable)
list_for_each_codec(codec, hbus) {
/* only set WAKEEN when needed for HDaudio codecs */
mask |= BIT(codec->core.addr);
- if (codec->jacktbl.used)
+ if (codec->jacktbl_used)
val |= BIT(codec->core.addr);
}
} else {
@@ -118,7 +118,7 @@ void hda_codec_jack_check(struct snd_sof_dev *sdev)
* Wake up all jack-detecting codecs regardless whether an event
* has been recorded in STATESTS
*/
- if (codec->jacktbl.used)
+ if (codec->jacktbl_used)
pm_request_resume(&codec->core.dev);
}
EXPORT_SYMBOL_NS_GPL(hda_codec_jack_check, "SND_SOC_SOF_HDA_AUDIO_CODEC");
diff --git a/sound/sparc/cs4231.c b/sound/sparc/cs4231.c
index d9e5cca94c73..0489eac165c9 100644
--- a/sound/sparc/cs4231.c
+++ b/sound/sparc/cs4231.c
@@ -1497,8 +1497,8 @@ static int cs4231_attach_begin(struct platform_device *op,
return -ENOENT;
}
- err = snd_card_new(&op->dev, index[dev], id[dev], THIS_MODULE,
- sizeof(struct snd_cs4231), &card);
+ err = snd_devm_card_new(&op->dev, index[dev], id[dev], THIS_MODULE,
+ sizeof(struct snd_cs4231), &card);
if (err < 0)
return err;
@@ -1519,28 +1519,24 @@ static int cs4231_attach_finish(struct snd_card *card)
err = snd_cs4231_pcm(card);
if (err < 0)
- goto out_err;
+ return err;
err = snd_cs4231_mixer(card);
if (err < 0)
- goto out_err;
+ return err;
err = snd_cs4231_timer(card);
if (err < 0)
- goto out_err;
+ return err;
err = snd_card_register(card);
if (err < 0)
- goto out_err;
+ return err;
dev_set_drvdata(&chip->op->dev, chip);
dev++;
return 0;
-
-out_err:
- snd_card_free(card);
- return err;
}
#ifdef SBUS_SUPPORT
@@ -1771,7 +1767,7 @@ static int snd_cs4231_sbus_create(struct snd_card *card,
static int cs4231_sbus_probe(struct platform_device *op)
{
struct resource *rp = &op->resource[0];
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
int err;
err = cs4231_attach_begin(op, &card);
@@ -1785,12 +1781,14 @@ static int cs4231_sbus_probe(struct platform_device *op)
op->archdata.irqs[0]);
err = snd_cs4231_sbus_create(card, op, dev);
- if (err < 0) {
- snd_card_free(card);
+ if (err < 0)
return err;
- }
- return cs4231_attach_finish(card);
+ err = cs4231_attach_finish(card);
+ if (err < 0)
+ return err;
+ card = NULL; /* probe succeeded, don't release as error */
+ return 0;
}
#endif
@@ -1968,7 +1966,7 @@ static int snd_cs4231_ebus_create(struct snd_card *card,
static int cs4231_ebus_probe(struct platform_device *op)
{
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
int err;
err = cs4231_attach_begin(op, &card);
@@ -1981,12 +1979,15 @@ static int cs4231_ebus_probe(struct platform_device *op)
op->archdata.irqs[0]);
err = snd_cs4231_ebus_create(card, op, dev);
- if (err < 0) {
- snd_card_free(card);
+ if (err < 0)
+ return err;
+
+ err = cs4231_attach_finish(card);
+ if (err < 0)
return err;
- }
- return cs4231_attach_finish(card);
+ card = NULL; /* probe succeeded, don't release as error */
+ return 0;
}
#endif
@@ -2004,13 +2005,6 @@ static int cs4231_probe(struct platform_device *op)
return -ENODEV;
}
-static void cs4231_remove(struct platform_device *op)
-{
- struct snd_cs4231 *chip = dev_get_drvdata(&op->dev);
-
- snd_card_free(chip->card);
-}
-
static const struct of_device_id cs4231_match[] = {
{
.name = "SUNW,CS4231",
@@ -2030,7 +2024,6 @@ static struct platform_driver cs4231_driver = {
.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
index ccaa36525f8d..9fad4102dc70 100644
--- a/sound/sparc/dbri.c
+++ b/sound/sparc/dbri.c
@@ -2576,11 +2576,16 @@ static void snd_dbri_free(struct snd_dbri *dbri)
(void *)dbri->dma, dbri->dma_dvma);
}
+static void dbri_card_free(struct snd_card *card)
+{
+ snd_dbri_free(card->private_data);
+}
+
static int dbri_probe(struct platform_device *op)
{
struct snd_dbri *dbri;
struct resource *rp;
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
static int dev;
int irq;
int err;
@@ -2598,8 +2603,8 @@ static int dbri_probe(struct platform_device *op)
return -ENODEV;
}
- err = snd_card_new(&op->dev, index[dev], id[dev], THIS_MODULE,
- sizeof(struct snd_dbri), &card);
+ err = snd_devm_card_new(&op->dev, index[dev], id[dev], THIS_MODULE,
+ sizeof(struct snd_dbri), &card);
if (err < 0)
return err;
@@ -2609,21 +2614,20 @@ static int dbri_probe(struct platform_device *op)
sprintf(card->longname, "%s at 0x%02lx:0x%016llx, irq %d",
card->shortname,
rp->flags & 0xffL, (unsigned long long)rp->start, irq);
+ card->private_free = dbri_card_free;
err = snd_dbri_create(card, op, irq, dev);
- if (err < 0) {
- snd_card_free(card);
+ if (err < 0)
return err;
- }
dbri = card->private_data;
err = snd_dbri_pcm(card);
if (err < 0)
- goto _err;
+ return err;
err = snd_dbri_mixer(card);
if (err < 0)
- goto _err;
+ return err;
/* /proc file handling */
snd_dbri_proc(card);
@@ -2631,27 +2635,15 @@ static int dbri_probe(struct platform_device *op)
err = snd_card_register(card);
if (err < 0)
- goto _err;
+ return 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++;
+ card = NULL; /* probe succeeded, don't release as error */
return 0;
-
-_err:
- snd_dbri_free(dbri);
- snd_card_free(card);
- return err;
-}
-
-static void 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);
}
static const struct of_device_id dbri_match[] = {
@@ -2672,7 +2664,6 @@ static struct platform_driver dbri_sbus_driver = {
.of_match_table = dbri_match,
},
.probe = dbri_probe,
- .remove = dbri_remove,
};
module_platform_driver(dbri_sbus_driver);
diff --git a/sound/synth/emux/emux_oss.c b/sound/synth/emux/emux_oss.c
index 1145c7956afd..30e353e7323d 100644
--- a/sound/synth/emux/emux_oss.c
+++ b/sound/synth/emux/emux_oss.c
@@ -124,7 +124,7 @@ snd_emux_open_seq_oss(struct snd_seq_oss_arg *arg, void *closure)
arg->private_data = p;
arg->addr.client = p->chset.client;
arg->addr.port = p->chset.port;
- p->oss_arg = arg;
+ WRITE_ONCE(p->oss_arg, arg);
reset_port_mode(p, arg->seq_mode);
@@ -141,16 +141,24 @@ snd_emux_open_seq_oss(struct snd_seq_oss_arg *arg, void *closure)
static void
reset_port_mode(struct snd_emux_port *port, int midi_mode)
{
+ struct snd_seq_oss_arg *arg;
+
+ /* OSS events can arrive before the open path has set oss_arg. */
+ arg = READ_ONCE(port->oss_arg);
+
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;
+ if (arg)
+ 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;
+ if (arg)
+ arg->event_passing = SNDRV_SEQ_OSS_PROCESS_EVENTS;
}
}
diff --git a/sound/usb/6fire/control.c b/sound/usb/6fire/control.c
index 10104cb42165..da5a2d7ac107 100644
--- a/sound/usb/6fire/control.c
+++ b/sound/usb/6fire/control.c
@@ -20,6 +20,7 @@
#include "control.h"
#include "comm.h"
#include "chip.h"
+#include "pcm.h"
static const char * const opt_coax_texts[2] = { "Optical", "Coax" };
static const char * const line_phono_texts[2] = { "Line", "Phono" };
@@ -148,9 +149,14 @@ static int usb6fire_control_set_channels(
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);
+ /*
+ * digital (S/PDIF) input and output: bit 0 adds four slots (4-7) to
+ * the capture frame, which carry the subframes of the receiver; up to
+ * two receiver frames arrive per card frame; bit 1 enables the
+ * transmitter, which encodes the playback pair in slots 6/7 (pcm.c)
+ */
+ ret = comm_rt->write16(comm_rt, 0x02, 0x03, 0x00,
+ (spdif_in ? 0x01 : 0x00) | (spdif_out ? 0x02 : 0x00));
if (ret < 0)
return ret;
@@ -160,14 +166,26 @@ static int usb6fire_control_set_channels(
static int usb6fire_control_streaming_update(struct control_runtime *rt)
{
struct comm_runtime *comm_rt = rt->chip->comm;
+ int err = 0;
+ int ret;
if (comm_rt) {
+ /*
+ * the digital pairs are enabled only while a stream runs; the
+ * streaming register is written even if this write fails.
+ * usb6fire_control_digital_thru_put() holds stream_mutex, so
+ * this write cannot run after set_channels() and before
+ * streaming starts in usb6fire_pcm_set_rate()
+ */
+ if (!rt->usb_streaming)
+ err = comm_rt->write16(comm_rt, 0x02, 0x03, 0x00, 0x00);
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,
+ ret = comm_rt->write16(comm_rt, 0x02, 0x00, 0x00,
(rt->usb_streaming ? 0x01 : 0x00) |
(rt->digital_thru_switch ? 0x08 : 0x00));
+ return err < 0 ? err : ret;
}
return -EINVAL;
}
@@ -383,6 +401,12 @@ static int usb6fire_control_digital_thru_put(struct snd_kcontrol *kcontrol,
struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
int changed = 0;
+ /*
+ * with Digital Thru on, streaming_update() selects the altsetting and
+ * writes the clock register of an idle card; prepare does the same
+ * under stream_mutex
+ */
+ guard(mutex)(&rt->chip->pcm->stream_mutex);
if (rt->digital_thru_switch != ucontrol->value.integer.value[0]) {
rt->digital_thru_switch = ucontrol->value.integer.value[0];
usb6fire_control_streaming_update(rt);
diff --git a/sound/usb/6fire/pcm.c b/sound/usb/6fire/pcm.c
index 0285d79ace0f..f5dfb8c3096a 100644
--- a/sound/usb/6fire/pcm.c
+++ b/sound/usb/6fire/pcm.c
@@ -9,13 +9,34 @@
* Copyright: (C) Torsten Schenk
*/
+#include <linux/bitops.h>
+#include <sound/asoundef.h>
+#include <sound/control.h>
+
#include "pcm.h"
#include "chip.h"
#include "comm.h"
#include "control.h"
+/*
+ * Every USB frame is a 4 byte header followed by 32-bit slots, one per
+ * channel: 3 bytes of sample and a marker byte. Analog slots are marked
+ * 0x40. Up to 96 kHz (altsettings 1 and 2) the playback frame carries two
+ * more slots, 6/7, which carry the data for the S/PDIF transmitter in
+ * the FPGA. Their marker byte tells the transmitter how to frame the
+ * sample: 0x10 marks the left subframe and 0x00 the right one; 0x20 marks
+ * the start of the channel status block (left subframe, every 192
+ * frames); 0x04 is the channel status bit of the subframe; 0x01 is the
+ * validity bit, set for non-audio data; 0x08 is the parity bit, which
+ * makes the number of ones in the sample, the validity and channel status
+ * bits and itself even.
+ * At altsetting 3 (176.4/192 kHz) the packets hold the analog slots only.
+ */
enum {
- OUT_N_CHANNELS = 6, IN_N_CHANNELS = 4
+ OUT_N_ANALOG = 6, N_SPDIF = 2,
+ OUT_N_CHANNELS = OUT_N_ANALOG + N_SPDIF, IN_N_CHANNELS = 4,
+ SPDIF_OUT_SLOT = OUT_N_ANALOG,
+ SPDIF_BLOCK_FRAMES = 192
};
/* keep next two synced with
@@ -24,6 +45,15 @@ enum {
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 u8 rates_iec958_fs[] = {
+ IEC958_AES3_CON_FS_44100, IEC958_AES3_CON_FS_48000,
+ IEC958_AES3_CON_FS_88200, IEC958_AES3_CON_FS_96000,
+ IEC958_AES3_CON_FS_176400, IEC958_AES3_CON_FS_192000 };
+/* channel status bits that "IEC958 Playback Default" sets, bytes 0 and 1 */
+static const u8 spdif_ctl_mask[] = {
+ IEC958_AES0_NONAUDIO | IEC958_AES0_CON_NOT_COPYRIGHT |
+ IEC958_AES0_CON_EMPHASIS,
+ IEC958_AES1_CON_CATEGORY | IEC958_AES1_CON_ORIGINAL };
static const int rates_alsaid[] = {
SNDRV_PCM_RATE_44100, SNDRV_PCM_RATE_48000,
SNDRV_PCM_RATE_88200, SNDRV_PCM_RATE_96000,
@@ -71,6 +101,7 @@ static int usb6fire_pcm_set_rate(struct pcm_runtime *rt)
{
int ret;
struct control_runtime *ctrl_rt = rt->chip->control;
+ bool spdif;
ctrl_rt->usb_streaming = false;
ret = ctrl_rt->update_streaming(ctrl_rt);
@@ -89,8 +120,9 @@ static int usb6fire_pcm_set_rate(struct pcm_runtime *rt)
return ret;
}
- ret = ctrl_rt->set_channels(ctrl_rt, OUT_N_CHANNELS, IN_N_CHANNELS,
- false, false);
+ spdif = rt->rate <= CONTROL_RATE_96KHZ;
+ ret = ctrl_rt->set_channels(ctrl_rt, OUT_N_ANALOG, IN_N_CHANNELS,
+ spdif, false);
if (ret < 0) {
dev_err(&rt->chip->dev->dev,
"error initializing channels while setting samplerate %d.\n",
@@ -104,16 +136,108 @@ static int usb6fire_pcm_set_rate(struct pcm_runtime *rt)
dev_err(&rt->chip->dev->dev,
"error starting streaming while setting samplerate %d.\n",
rates[rt->rate]);
+ /* stream_stop() will not run; clear 0x02/0x03 here */
+ ctrl_rt->usb_streaming = false;
+ ctrl_rt->update_streaming(ctrl_rt);
return ret;
}
rt->in_n_analog = IN_N_CHANNELS;
- rt->out_n_analog = OUT_N_CHANNELS;
+ rt->out_n_slots = spdif ? OUT_N_CHANNELS : OUT_N_ANALOG;
rt->in_packet_size = rates_in_packet_size[rt->rate];
rt->out_packet_size = rates_out_packet_size[rt->rate];
+
+ /*
+ * consumer channel status, 24 bit samples; bytes 0 and 1 are taken
+ * from rt->spdif_bits at the start of every block
+ */
+ memset(rt->spdif_status, 0, sizeof(rt->spdif_status));
+ rt->spdif_status[3] = rates_iec958_fs[rt->rate];
+ rt->spdif_status[4] = IEC958_AES4_CON_MAX_WORDLEN_24 |
+ IEC958_AES4_CON_WORDLEN_24_20;
+ rt->spdif_frame = 0;
return 0;
}
+/*
+ * Return the marker byte of one playback slot whose 3 sample bytes are
+ * already in place. The marker is 0x40 for an analog slot and the S/PDIF
+ * framing for a digital slot. With "IEC958 Playback Switch" off, the
+ * function also clears the sample bytes of a digital slot. After the right
+ * subframe, the function advances the channel status position.
+ */
+static u8 usb6fire_pcm_marker(struct pcm_runtime *rt, int slot,
+ u8 *sample)
+{
+ bool right = slot == SPDIF_OUT_SLOT + 1;
+ unsigned int n = rt->spdif_frame;
+ u8 marker = right ? 0x00 : 0x10;
+ u8 status;
+
+ if (slot < SPDIF_OUT_SLOT)
+ return 0x40;
+ if (!right && n == 0) {
+ /* a new block takes the bits of "IEC958 Playback Default" */
+ u32 bits = READ_ONCE(rt->spdif_bits);
+
+ rt->spdif_status[0] = bits & 0xff;
+ rt->spdif_status[1] = bits >> 8;
+ /*
+ * "IEC958 Playback Switch" off omits the block start flag and
+ * clears the sample bytes of the digital slots
+ */
+ rt->spdif_on = READ_ONCE(rt->spdif_switch);
+ if (rt->spdif_on)
+ marker |= 0x20;
+ }
+ if (!rt->spdif_on)
+ memset(sample, 0, 3);
+ status = rt->spdif_status[n >> 3];
+ /* byte 2 holds the channel number: 1 = A (left), 2 = B (right) */
+ if ((n >> 3) == 2)
+ status |= right ? (2 << 4) : (1 << 4);
+ if (status & (1 << (n & 7)))
+ marker |= 0x04;
+ /* V = 1 for non-audio data (AC3, DTS), as the vendor driver does */
+ if (rt->spdif_status[0] & IEC958_AES0_NONAUDIO)
+ marker |= 0x01;
+ /* P makes the number of ones in bits 0-27 (sample, V, U, C, P) even */
+ if (parity8(sample[0] ^ sample[1] ^ sample[2] ^ (marker & 0x07)))
+ marker |= 0x08;
+ if (right)
+ rt->spdif_frame = (n + 1) % SPDIF_BLOCK_FRAMES;
+ return marker;
+}
+
+/* more than OUT_N_ANALOG playback channels need a rate up to 96 kHz */
+static int usb6fire_pcm_rule_channels(struct snd_pcm_hw_params *params,
+ struct snd_pcm_hw_rule *rule)
+{
+ const struct snd_interval *r =
+ hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval t = { .min = 1, .max = OUT_N_ANALOG, .integer = 1 };
+
+ if (r->min <= rates[CONTROL_RATE_96KHZ])
+ return 0;
+ return snd_interval_refine(hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_CHANNELS), &t);
+}
+
+static int usb6fire_pcm_rule_rate(struct snd_pcm_hw_params *params,
+ struct snd_pcm_hw_rule *rule)
+{
+ const struct snd_interval *c =
+ hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+ struct snd_interval t = {
+ .min = 0, .max = rates[CONTROL_RATE_96KHZ], .integer = 1
+ };
+
+ if (c->min <= OUT_N_ANALOG)
+ return 0;
+ return snd_interval_refine(hw_param_interval(params,
+ SNDRV_PCM_HW_PARAM_RATE), &t);
+}
+
static struct pcm_substream *usb6fire_pcm_get_substream(
struct snd_pcm_substream *alsa_sub)
{
@@ -264,19 +388,21 @@ static void usb6fire_pcm_playback(struct pcm_substream *sub,
}
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 */
+ /*
+ * at least 4 header bytes for valid packet.
+ * after that: 32 bits per slot
+ */
isoc = &urb->instance->iso_frame_desc[i];
if (isoc->length > 4)
frame_count = (isoc->length - 4)
- / (rt->out_n_analog << 2);
+ / (rt->out_n_slots << 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;
+ dest += rt->out_n_slots;
sub->dma_off++;
sub->period_off++;
if (src == src_end) {
@@ -301,6 +427,7 @@ static void usb6fire_pcm_in_urb_handler(struct urb *usb_urb)
int channel;
int i;
u8 *dest;
+ unsigned int spdif_frame;
if (usb_urb->status || rt->panic || rt->stream_state == STREAM_STOPPING)
return;
@@ -344,10 +471,10 @@ static void usb6fire_pcm_in_urb_handler(struct urb *usb_urb)
/ (rt->in_n_analog << 2);
frames = min_t(unsigned int, frames,
(rt->out_packet_size - 4)
- / (rt->out_n_analog << 2));
+ / (rt->out_n_slots << 2));
isoc_out->offset = total_length;
- isoc_out->length = frames * (rt->out_n_analog << 2) + 4;
+ isoc_out->length = frames * (rt->out_n_slots << 2) + 4;
isoc_out->status = 0;
total_length += isoc_out->length;
}
@@ -368,28 +495,38 @@ static void usb6fire_pcm_in_urb_handler(struct urb *usb_urb)
if (period_elapsed)
snd_pcm_period_elapsed(sub->instance);
- /* setup the 4th byte of each sample (0x40 for analog channels) */
+ /*
+ * set the 4th byte of each sample (0x40 for analog channels,
+ * S/PDIF framing for the digital pair)
+ */
+ spdif_frame = rt->spdif_frame;
dest = out_urb->buffer;
for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) {
isoc_out = &out_urb->instance->iso_frame_desc[i];
if (isoc_out->length >= 4) {
frame_count = (isoc_out->length - 4)
- / (rt->out_n_analog << 2);
+ / (rt->out_n_slots << 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 < rt->out_n_slots;
channel++) {
- dest += 3; /* skip sample data */
- *(dest++) = 0x40;
+ dest[3] = usb6fire_pcm_marker(rt,
+ channel, dest);
+ dest += 4;
}
}
}
- usb_submit_urb(out_urb->instance, GFP_ATOMIC);
+ /*
+ * the frames of an out urb that was not submitted never reach the
+ * transmitter; the next urb continues the block at the same position
+ */
+ if (usb_submit_urb(out_urb->instance, GFP_ATOMIC) < 0)
+ rt->spdif_frame = spdif_frame;
usb_submit_urb(in_urb->instance, GFP_ATOMIC);
}
@@ -409,6 +546,7 @@ 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;
+ int ret;
if (rt->panic)
return -EPIPE;
@@ -420,6 +558,17 @@ static int usb6fire_pcm_open(struct snd_pcm_substream *alsa_sub)
if (rt->rate < ARRAY_SIZE(rates))
alsa_rt->hw.rates = rates_alsaid[rt->rate];
alsa_rt->hw.channels_max = OUT_N_CHANNELS;
+ ret = snd_pcm_hw_rule_add(alsa_rt, 0,
+ SNDRV_PCM_HW_PARAM_CHANNELS,
+ usb6fire_pcm_rule_channels, NULL,
+ SNDRV_PCM_HW_PARAM_RATE, -1);
+ if (ret < 0)
+ return ret;
+ ret = snd_pcm_hw_rule_add(alsa_rt, 0, SNDRV_PCM_HW_PARAM_RATE,
+ usb6fire_pcm_rule_rate, NULL,
+ SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+ if (ret < 0)
+ return ret;
sub = &rt->playback;
} else if (alsa_sub->stream == SNDRV_PCM_STREAM_CAPTURE) {
if (rt->rate < ARRAY_SIZE(rates))
@@ -489,7 +638,17 @@ static int usb6fire_pcm_prepare(struct snd_pcm_substream *alsa_sub)
alsa_rt->rate);
return -EINVAL;
}
-
+ }
+ /* the digital pair is not available at 176.4 and 192 kHz */
+ if (rt->rate > CONTROL_RATE_96KHZ &&
+ alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK &&
+ alsa_rt->channels > OUT_N_ANALOG) {
+ dev_err(&rt->chip->dev->dev,
+ "%d channels not available at %d Hz.\n",
+ alsa_rt->channels, rates[rt->rate]);
+ return -EINVAL;
+ }
+ if (rt->stream_state == STREAM_DISABLED) {
ret = usb6fire_pcm_set_rate(rt);
if (ret)
return ret;
@@ -612,6 +771,128 @@ static void usb6fire_pcm_free(struct pcm_runtime *rt)
kfree(rt);
}
+static int usb6fire_pcm_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 usb6fire_pcm_spdif_mask_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ memset(ucontrol->value.iec958.status, 0,
+ sizeof(ucontrol->value.iec958.status));
+ ucontrol->value.iec958.status[0] = spdif_ctl_mask[0];
+ ucontrol->value.iec958.status[1] = spdif_ctl_mask[1];
+ return 0;
+}
+
+static int usb6fire_pcm_spdif_default_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct pcm_runtime *rt = snd_kcontrol_chip(kcontrol);
+ u32 bits = READ_ONCE(rt->spdif_bits);
+
+ memset(ucontrol->value.iec958.status, 0,
+ sizeof(ucontrol->value.iec958.status));
+ ucontrol->value.iec958.status[0] = bits & 0xff;
+ ucontrol->value.iec958.status[1] = bits >> 8;
+ return 0;
+}
+
+static int usb6fire_pcm_spdif_default_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct pcm_runtime *rt = snd_kcontrol_chip(kcontrol);
+ u32 bits = (ucontrol->value.iec958.status[0] & spdif_ctl_mask[0]) |
+ (ucontrol->value.iec958.status[1] & spdif_ctl_mask[1]) << 8;
+
+ /* the control core serializes puts; the urb handler uses READ_ONCE */
+ if (rt->spdif_bits == bits)
+ return 0;
+ WRITE_ONCE(rt->spdif_bits, bits);
+ return 1;
+}
+
+static int usb6fire_pcm_spdif_switch_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct pcm_runtime *rt = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.integer.value[0] = READ_ONCE(rt->spdif_switch);
+ return 0;
+}
+
+static int usb6fire_pcm_spdif_switch_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct pcm_runtime *rt = snd_kcontrol_chip(kcontrol);
+ bool on = ucontrol->value.integer.value[0];
+
+ /* the control core serializes puts; the urb handler uses READ_ONCE */
+ if (rt->spdif_switch == on)
+ return 0;
+ WRITE_ONCE(rt->spdif_switch, on);
+ return 1;
+}
+
+/*
+ * playback channel map: channel n goes to analog output n, which the
+ * manual of the card assigns to FL FR FC LFE RL RR for n = 1..6; a
+ * 1-channel stream is MONO, as in snd_pcm_std_chmaps, and plays on
+ * analog output 1 only; channels 7 and 8 are the S/PDIF pair and have
+ * no position
+ */
+static const struct snd_pcm_chmap_elem usb6fire_pcm_chmaps[] = {
+ { .channels = 1, .map = { SNDRV_CHMAP_MONO } },
+ { .channels = 2, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } },
+ { .channels = 3, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
+ SNDRV_CHMAP_FC } },
+ { .channels = 4, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
+ SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE } },
+ { .channels = 5, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
+ SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE,
+ SNDRV_CHMAP_RL } },
+ { .channels = 6, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
+ SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE,
+ SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } },
+ { .channels = 7, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
+ SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE,
+ SNDRV_CHMAP_RL, SNDRV_CHMAP_RR,
+ SNDRV_CHMAP_UNKNOWN } },
+ { .channels = 8, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
+ SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE,
+ SNDRV_CHMAP_RL, SNDRV_CHMAP_RR,
+ SNDRV_CHMAP_UNKNOWN, SNDRV_CHMAP_UNKNOWN } },
+ { }
+};
+
+static const struct snd_kcontrol_new usb6fire_pcm_spdif_controls[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
+ .info = usb6fire_pcm_spdif_info,
+ .get = usb6fire_pcm_spdif_default_get,
+ .put = usb6fire_pcm_spdif_default_put,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, CON_MASK),
+ .access = SNDRV_CTL_ELEM_ACCESS_READ,
+ .info = usb6fire_pcm_spdif_info,
+ .get = usb6fire_pcm_spdif_mask_get,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, SWITCH),
+ .info = snd_ctl_boolean_mono_info,
+ .get = usb6fire_pcm_spdif_switch_get,
+ .put = usb6fire_pcm_spdif_switch_put,
+ },
+};
+
int usb6fire_pcm_init(struct sfire_chip *chip)
{
int i;
@@ -630,6 +911,9 @@ int usb6fire_pcm_init(struct sfire_chip *chip)
rt->chip = chip;
rt->stream_state = STREAM_DISABLED;
rt->rate = ARRAY_SIZE(rates);
+ rt->spdif_switch = true;
+ rt->spdif_bits = IEC958_AES0_CON_NOT_COPYRIGHT |
+ IEC958_AES1_CON_GENERAL << 8;
init_waitqueue_head(&rt->stream_wait_queue);
mutex_init(&rt->stream_mutex);
@@ -665,6 +949,22 @@ int usb6fire_pcm_init(struct sfire_chip *chip)
rt->instance = pcm;
chip->pcm = rt;
+
+ /* on error, rt and the controls added so far are freed with the card */
+ for (i = 0; i < ARRAY_SIZE(usb6fire_pcm_spdif_controls); i++) {
+ ret = snd_ctl_add(chip->card,
+ snd_ctl_new1(&usb6fire_pcm_spdif_controls[i], rt));
+ if (ret < 0) {
+ dev_err(&chip->dev->dev, "cannot add S/PDIF control.\n");
+ return ret;
+ }
+ }
+ ret = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+ usb6fire_pcm_chmaps, OUT_N_CHANNELS, 0, NULL);
+ if (ret < 0) {
+ dev_err(&chip->dev->dev, "cannot add channel map.\n");
+ return ret;
+ }
return 0;
error:
diff --git a/sound/usb/6fire/pcm.h b/sound/usb/6fire/pcm.h
index b586fe220fd1..f1b867b9c468 100644
--- a/sound/usb/6fire/pcm.h
+++ b/sound/usb/6fire/pcm.h
@@ -53,7 +53,12 @@ struct pcm_runtime {
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 */
+ int out_n_slots; /* 32-bit slots per frame the soundcard receives */
+ bool spdif_switch; /* "IEC958 Playback Switch" */
+ bool spdif_on; /* copy of spdif_switch, read at the start of each block */
+ unsigned int spdif_frame; /* position in the channel status block */
+ u8 spdif_status[24]; /* IEC 60958 channel status of the S/PDIF output */
+ u32 spdif_bits; /* status bytes 0 and 1, "IEC958 Playback Default" */
struct mutex stream_mutex;
u8 stream_state; /* one of STREAM_XXX (pcm.c) */
diff --git a/sound/usb/Makefile b/sound/usb/Makefile
index e62794a87e73..2e842dcc73de 100644
--- a/sound/usb/Makefile
+++ b/sound/usb/Makefile
@@ -11,6 +11,7 @@ snd-usb-audio-y := card.o \
helper.o \
implicit.o \
mixer.o \
+ mixer_evo.o \
mixer_quirks.o \
mixer_scarlett.o \
mixer_scarlett2.o \
diff --git a/sound/usb/caiaq/Makefile b/sound/usb/caiaq/Makefile
index 9a99c17a2c1b..ffa5a7edf95c 100644
--- a/sound/usb/caiaq/Makefile
+++ b/sound/usb/caiaq/Makefile
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-only
-snd-usb-caiaq-y := device.o audio.o midi.o control.o
+snd-usb-caiaq-y := device.o audio.o midi.o control.o lcd.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/device.c b/sound/usb/caiaq/device.c
index f604846ec6e6..634dc36f2ead 100644
--- a/sound/usb/caiaq/device.c
+++ b/sound/usb/caiaq/device.c
@@ -22,6 +22,7 @@
#include "midi.h"
#include "control.h"
#include "input.h"
+#include "lcd.h"
MODULE_AUTHOR("Daniel Mack <daniel@caiaq.de>");
MODULE_DESCRIPTION("caiaq USB audio");
@@ -142,7 +143,7 @@ static void usb_ep1_command_reply_dispatch (struct urb* urb)
return;
}
if (urb->actual_length < 1)
- return;
+ goto resubmit;
payload_len = urb->actual_length - 1;
@@ -192,6 +193,7 @@ static void usb_ep1_command_reply_dispatch (struct urb* urb)
break;
}
+ resubmit:
cdev->ep1_in_urb->actual_length = 0;
ret = usb_submit_urb(cdev->ep1_in_urb, GFP_ATOMIC);
if (ret < 0)
@@ -212,6 +214,8 @@ int snd_usb_caiaq_send_command(struct snd_usb_caiaqdev *cdev,
if (len > EP1_BUFSIZE - 1)
len = EP1_BUFSIZE - 1;
+ guard(mutex)(&cdev->ep1_out_mutex);
+
if (buffer && len > 0)
memcpy(cdev->ep1_out_buf+1, buffer, len);
@@ -235,6 +239,8 @@ int snd_usb_caiaq_send_command_bank(struct snd_usb_caiaqdev *cdev,
if (len > EP1_BUFSIZE - 2)
len = EP1_BUFSIZE - 2;
+ guard(mutex)(&cdev->ep1_out_mutex);
+
if (buffer && len > 0)
memcpy(cdev->ep1_out_buf+2, buffer, len);
@@ -383,10 +389,9 @@ static int setup_card(struct snd_usb_caiaqdev *cdev)
}
#endif
- /* finally, register the card and all its sub-instances */
- ret = snd_card_register(cdev->chip.card);
+ ret = snd_usb_caiaq_lcd_init(cdev);
if (ret < 0) {
- dev_err(dev, "snd_card_register() returned %d\n", ret);
+ dev_err(dev, "Unable to set up LCD (ret=%d)\n", ret);
return ret;
}
@@ -396,6 +401,13 @@ static int setup_card(struct snd_usb_caiaqdev *cdev)
return ret;
}
+ /* 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);
+ return ret;
+ }
+
return 0;
}
@@ -443,6 +455,8 @@ static int create_card(struct usb_device *usb_dev,
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);
+ spin_lock_init(&cdev->midi_lock);
+ mutex_init(&cdev->ep1_out_mutex);
*cardp = card;
return 0;
diff --git a/sound/usb/caiaq/device.h b/sound/usb/caiaq/device.h
index 1c6f34693fa8..16f65e7867d1 100644
--- a/sound/usb/caiaq/device.h
+++ b/sound/usb/caiaq/device.h
@@ -2,6 +2,8 @@
#ifndef CAIAQ_DEVICE_H
#define CAIAQ_DEVICE_H
+#include <linux/mutex.h>
+
#include "../usbaudio.h"
#define USB_VID_NATIVEINSTRUMENTS 0x17cc
@@ -28,6 +30,8 @@
#define MODNAME "snd-usb-caiaq"
+#define CAIAQ_LCD_FRAME_SIZE 1024
+
#define EP1_CMD_GET_DEVICE_INFO 0x1
#define EP1_CMD_READ_ERP 0x2
#define EP1_CMD_READ_ANALOG 0x3
@@ -35,6 +39,7 @@
#define EP1_CMD_WRITE_IO 0x5
#define EP1_CMD_MIDI_READ 0x6
#define EP1_CMD_MIDI_WRITE 0x7
+#define EP1_CMD_LCD 0x8
#define EP1_CMD_AUDIO_PARAMS 0x9
#define EP1_CMD_AUTO_MSG 0xb
#define EP1_CMD_DIMM_LEDS 0xc
@@ -68,10 +73,12 @@ struct snd_usb_caiaqdev {
unsigned char ep1_in_buf[EP1_BUFSIZE];
unsigned char ep1_out_buf[EP1_BUFSIZE];
+ struct mutex ep1_out_mutex; /* protects ep1_out_buf */
unsigned char midi_out_buf[EP1_BUFSIZE];
struct caiaq_device_spec spec;
- spinlock_t spinlock;
+ spinlock_t spinlock; /* for PCM audio */
+ spinlock_t midi_lock; /* midi_x_stream, midi_out_active */
wait_queue_head_t ep1_wait_queue;
wait_queue_head_t prepare_wait_queue;
int spec_received, audio_parm_answer;
@@ -98,6 +105,13 @@ struct snd_usb_caiaqdev {
unsigned char control_state[256];
unsigned char ep8_out_buf[2];
+ /* Kore LCD */
+ struct mutex lcd_mutex; /* protects the lcd_* fields */
+ unsigned char lcd_frame[CAIAQ_LCD_FRAME_SIZE]; /* shown frame */
+ unsigned char lcd_contrast;
+ bool lcd_ready; /* controller set up */
+ bool lcd_frame_valid; /* lcd_frame matches the display */
+
/* Linux input */
#ifdef CONFIG_SND_USB_CAIAQ_INPUT
struct input_dev *input_dev;
diff --git a/sound/usb/caiaq/input.c b/sound/usb/caiaq/input.c
index 8d924330c54c..856235c3398e 100644
--- a/sound/usb/caiaq/input.c
+++ b/sound/usb/caiaq/input.c
@@ -41,11 +41,11 @@ static const unsigned short keycode_kore[] = {
BTN_3,
BTN_2,
BTN_1,
- BTN_8,
- BTN_7,
- BTN_6,
BTN_5,
- KEY_BRL_DOT4, /* touch sensitive knobs */
+ BTN_6,
+ BTN_7,
+ BTN_8,
+ KEY_BRL_DOT4, /* touch sensitive knobs (Kore 2 order) */
KEY_BRL_DOT3,
KEY_BRL_DOT2,
KEY_BRL_DOT1,
@@ -55,6 +55,21 @@ static const unsigned short keycode_kore[] = {
KEY_BRL_DOT5
};
+/* index of the first touch sensor in keycode_kore */
+#define KORE_TOUCH_KEYS 24
+
+/* the first Kore controller reports its touch sensors in this order */
+static const unsigned short keycode_kore1_touch[] = {
+ KEY_BRL_DOT4,
+ KEY_BRL_DOT8,
+ KEY_BRL_DOT3,
+ KEY_BRL_DOT7,
+ KEY_BRL_DOT2,
+ KEY_BRL_DOT6,
+ KEY_BRL_DOT1,
+ KEY_BRL_DOT5
+};
+
#define MASCHINE_BUTTONS (42)
#define MASCHINE_BUTTON(X) ((X) + BTN_MISC)
#define MASCHINE_PADS (16)
@@ -666,6 +681,12 @@ int snd_usb_caiaq_input_init(struct snd_usb_caiaqdev *cdev)
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));
+ BUILD_BUG_ON(KORE_TOUCH_KEYS + ARRAY_SIZE(keycode_kore1_touch) >
+ ARRAY_SIZE(keycode_kore));
+ if (cdev->chip.usb_id ==
+ USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER))
+ memcpy(cdev->keycode + KORE_TOUCH_KEYS, keycode_kore1_touch,
+ sizeof(keycode_kore1_touch));
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);
diff --git a/sound/usb/caiaq/lcd.c b/sound/usb/caiaq/lcd.c
new file mode 100644
index 000000000000..dd2a3b949a43
--- /dev/null
+++ b/sound/usb/caiaq/lcd.c
@@ -0,0 +1,258 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * LCD support for the Native Instruments Kore controllers
+ *
+ * Copyright (c) 2026 Niko Huuskonen <niko.huuskonen.00@gmail.com>
+ *
+ * Both Kore controllers have a 128x64 pixel monochrome LCD behind an
+ * ST7565-style controller. The firmware passes EP1 packets with the
+ * command byte EP1_CMD_LCD on to that controller:
+ *
+ * EP1_CMD_LCD 0x00 <n> <n controller command bytes>
+ * EP1_CMD_LCD 0x01 <n> <n display RAM bytes>
+ *
+ * The display is exposed as an exclusive hwdep device. Each write carries
+ * a whole frame of CAIAQ_LCD_FRAME_SIZE bytes: 8 pages of 128 columns,
+ * one byte per column and page, with bit 0 as the top pixel of the page.
+ * Only the pages that changed since the previous frame are sent to the
+ * device. The contrast is an ALSA control; the backlight is the existing
+ * "LED lcd" control.
+ *
+ * The controller setup and the packet layout follow the USB traffic of
+ * the vendor software, as documented by the OpenKoreBridge project.
+ */
+
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/usb.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/hwdep.h>
+#include <sound/pcm.h>
+
+#include "device.h"
+#include "lcd.h"
+
+#define LCD_WIDTH 128
+#define LCD_PAGES (CAIAQ_LCD_FRAME_SIZE / LCD_WIDTH)
+#define LCD_COLUMN_OFFSET 4 /* first visible controller column */
+#define LCD_CHUNK 32 /* display RAM bytes per packet */
+#define LCD_CONTRAST_MAX 0x3f
+#define LCD_CONTRAST_DEFAULT 0x1c
+
+#define LCD_KIND_COMMAND 0x00
+#define LCD_KIND_DATA 0x01
+
+static int lcd_send(struct snd_usb_caiaqdev *cdev, u8 kind,
+ const u8 *bytes, unsigned int len)
+{
+ u8 buf[2 + LCD_CHUNK];
+
+ if (WARN_ON(len > LCD_CHUNK))
+ return -EINVAL;
+
+ buf[0] = kind;
+ buf[1] = len;
+ memcpy(buf + 2, bytes, len);
+ return snd_usb_caiaq_send_command(cdev, EP1_CMD_LCD, buf, len + 2);
+}
+
+static int lcd_command(struct snd_usb_caiaqdev *cdev, u8 cmd)
+{
+ return lcd_send(cdev, LCD_KIND_COMMAND, &cmd, 1);
+}
+
+static int lcd_set_contrast(struct snd_usb_caiaqdev *cdev)
+{
+ const u8 cmd[] = { 0x81, cdev->lcd_contrast };
+
+ return lcd_send(cdev, LCD_KIND_COMMAND, cmd, sizeof(cmd));
+}
+
+/* controller setup, in the order used by the vendor software */
+static int lcd_setup(struct snd_usb_caiaqdev *cdev)
+{
+ static const u8 head[] = {
+ 0xe2, /* reset */
+ 0xa1, /* reverse column direction */
+ 0xc8, /* reverse row direction */
+ 0xa2, /* 1/9 bias */
+ 0x2c, 0x2e, 0x2f, /* power up in three steps */
+ 0x27, /* regulator resistor ratio */
+ };
+ static const u8 tail[] = {
+ 0xa6, /* normal, non-inverted display */
+ 0x88, 0xef, /* sent by the vendor software */
+ 0xaf, /* display on */
+ };
+ int i, ret;
+
+ for (i = 0; i < ARRAY_SIZE(head); i++) {
+ ret = lcd_command(cdev, head[i]);
+ if (ret)
+ return ret;
+ }
+
+ ret = lcd_set_contrast(cdev);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < ARRAY_SIZE(tail); i++) {
+ ret = lcd_command(cdev, tail[i]);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int lcd_write_page(struct snd_usb_caiaqdev *cdev, unsigned int page,
+ const u8 *data)
+{
+ unsigned int col;
+ int ret;
+
+ for (col = 0; col < LCD_WIDTH; col += LCD_CHUNK) {
+ unsigned int addr = LCD_COLUMN_OFFSET + col;
+ const u8 column[] = { 0x10 | (addr >> 4), addr & 0x0f };
+
+ ret = lcd_command(cdev, 0xb0 | page);
+ if (!ret)
+ ret = lcd_send(cdev, LCD_KIND_COMMAND,
+ column, sizeof(column));
+ if (!ret)
+ ret = lcd_send(cdev, LCD_KIND_DATA,
+ data + col, LCD_CHUNK);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static long lcd_hwdep_write(struct snd_hwdep *hw, const char __user *buf,
+ long count, loff_t *offset)
+{
+ struct snd_usb_caiaqdev *cdev = hw->private_data;
+ unsigned int page;
+ int ret;
+
+ if (count != CAIAQ_LCD_FRAME_SIZE)
+ return -EINVAL;
+
+ u8 *frame __free(kfree) = memdup_user(buf, count);
+ if (IS_ERR(frame))
+ return PTR_ERR(frame);
+
+ guard(mutex)(&cdev->lcd_mutex);
+
+ if (!cdev->lcd_ready) {
+ ret = lcd_setup(cdev);
+ if (ret)
+ return ret;
+ cdev->lcd_ready = true;
+ cdev->lcd_frame_valid = false;
+ }
+
+ for (page = 0; page < LCD_PAGES; page++) {
+ u8 *shown = cdev->lcd_frame + page * LCD_WIDTH;
+ const u8 *next = frame + page * LCD_WIDTH;
+
+ if (cdev->lcd_frame_valid && !memcmp(shown, next, LCD_WIDTH))
+ continue;
+
+ ret = lcd_write_page(cdev, page, next);
+ if (ret) {
+ cdev->lcd_frame_valid = false;
+ return ret;
+ }
+ memcpy(shown, next, LCD_WIDTH);
+ }
+ cdev->lcd_frame_valid = true;
+
+ return count;
+}
+
+static int lcd_contrast_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 = LCD_CONTRAST_MAX;
+ return 0;
+}
+
+static int lcd_contrast_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_usb_caiaqdev *cdev = snd_kcontrol_chip(kcontrol);
+
+ guard(mutex)(&cdev->lcd_mutex);
+ ucontrol->value.integer.value[0] = cdev->lcd_contrast;
+ return 0;
+}
+
+static int lcd_contrast_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_usb_caiaqdev *cdev = snd_kcontrol_chip(kcontrol);
+ long val = ucontrol->value.integer.value[0];
+ int ret;
+
+ if (val < 0 || val > LCD_CONTRAST_MAX)
+ return -EINVAL;
+
+ guard(mutex)(&cdev->lcd_mutex);
+ if (val == cdev->lcd_contrast)
+ return 0;
+
+ cdev->lcd_contrast = val;
+ if (cdev->lcd_ready) {
+ ret = lcd_set_contrast(cdev);
+ if (ret)
+ return ret;
+ }
+
+ return 1;
+}
+
+static const struct snd_kcontrol_new lcd_contrast_control = {
+ .iface = SNDRV_CTL_ELEM_IFACE_HWDEP,
+ .name = "LCD Contrast",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = lcd_contrast_info,
+ .get = lcd_contrast_get,
+ .put = lcd_contrast_put,
+};
+
+int snd_usb_caiaq_lcd_init(struct snd_usb_caiaqdev *cdev)
+{
+ struct snd_card *card = cdev->chip.card;
+ struct snd_hwdep *hw;
+ int ret;
+
+ switch (cdev->chip.usb_id) {
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER):
+ case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2):
+ break;
+ default:
+ return 0;
+ }
+
+ mutex_init(&cdev->lcd_mutex);
+ cdev->lcd_contrast = LCD_CONTRAST_DEFAULT;
+
+ ret = snd_hwdep_new(card, "Kore LCD", 0, &hw);
+ if (ret < 0)
+ return ret;
+
+ strscpy(hw->name, "Kore LCD", sizeof(hw->name));
+ hw->iface = SNDRV_HWDEP_IFACE_CAIAQ;
+ hw->private_data = cdev;
+ hw->exclusive = 1;
+ hw->ops.write = lcd_hwdep_write;
+
+ return snd_ctl_add(card, snd_ctl_new1(&lcd_contrast_control, cdev));
+}
diff --git a/sound/usb/caiaq/lcd.h b/sound/usb/caiaq/lcd.h
new file mode 100644
index 000000000000..423464750278
--- /dev/null
+++ b/sound/usb/caiaq/lcd.h
@@ -0,0 +1,7 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef CAIAQ_LCD_H
+#define CAIAQ_LCD_H
+
+int snd_usb_caiaq_lcd_init(struct snd_usb_caiaqdev *cdev);
+
+#endif /* CAIAQ_LCD_H */
diff --git a/sound/usb/caiaq/midi.c b/sound/usb/caiaq/midi.c
index 18529484c8dc..b7d7d24937a7 100644
--- a/sound/usb/caiaq/midi.c
+++ b/sound/usb/caiaq/midi.c
@@ -79,6 +79,7 @@ static void snd_usb_caiaq_midi_output_trigger(struct snd_rawmidi_substream *subs
{
struct snd_usb_caiaqdev *cdev = substream->rmidi->private_data;
+ guard(spinlock_irqsave)(&cdev->midi_lock);
if (up) {
cdev->midi_out_substream = substream;
if (!cdev->midi_out_active)
@@ -151,6 +152,7 @@ void snd_usb_caiaq_midi_output_done(struct urb* urb)
{
struct snd_usb_caiaqdev *cdev = urb->context;
+ guard(spinlock_irqsave)(&cdev->midi_lock);
cdev->midi_out_active = 0;
if (urb->status != 0)
return;
diff --git a/sound/usb/card.h b/sound/usb/card.h
index 8299ac241c60..f956bcf00f77 100644
--- a/sound/usb/card.h
+++ b/sound/usb/card.h
@@ -123,6 +123,7 @@ struct snd_usb_endpoint {
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 snap_fb_quirk:1; /* ignore implausible feedback */
unsigned int datainterval; /* log_2 of data packet interval */
unsigned int syncinterval; /* P for adaptive mode, 0 otherwise */
unsigned char silence_value;
diff --git a/sound/usb/clock.c b/sound/usb/clock.c
index 34832183a1f0..cc77eace7b59 100644
--- a/sound/usb/clock.c
+++ b/sound/usb/clock.c
@@ -550,6 +550,30 @@ static int get_sample_rate_v2v3(struct snd_usb_audio *chip, int iface,
}
/*
+ * Return the partner rate in the other base-rate family.
+ * See QUIRK_FLAG_SWAP_RATES.
+ */
+static unsigned int swap_base_rate(unsigned int rate)
+{
+ switch (rate) {
+ case 44100:
+ return 48000;
+ case 48000:
+ return 44100;
+ case 88200:
+ return 96000;
+ case 96000:
+ return 88200;
+ case 176400:
+ return 192000;
+ case 192000:
+ return 176400;
+ default:
+ return rate;
+ }
+}
+
+/*
* Try to set the given sample rate:
*
* Return 0 if the clock source is read-only, the actual rate on success,
@@ -585,6 +609,9 @@ int snd_usb_set_sample_rate_v2v3(struct snd_usb_audio *chip,
if (!writeable)
return 0;
+ if (chip->quirk_flags & QUIRK_FLAG_SWAP_RATES)
+ rate = swap_base_rate(rate);
+
data = cpu_to_le32(rate);
err = snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), UAC2_CS_CUR,
USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT,
diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c
index 879d0451536e..9c844c55ad3f 100644
--- a/sound/usb/endpoint.c
+++ b/sound/usb/endpoint.c
@@ -1908,6 +1908,22 @@ static void snd_usb_handle_sync_urb(struct snd_usb_endpoint *ep,
else
f >>= -ep->freqshift;
+ if (unlikely(sender->snap_fb_quirk) &&
+ (f < ep->freqn - ep->freqn / 128 ||
+ f > ep->freqn + ep->freqn / 128)) {
+ /*
+ * Feedback deviates from nominal far beyond what real
+ * clock differences can explain. This firmware can get
+ * stuck at nominal+1 sample per frame, causing audible
+ * pops. Trust the nominal rate instead.
+ */
+ dev_warn_ratelimited(&ep->chip->dev->dev,
+ "EP 0x%x: ignoring implausible feedback 0x%x.%04x (nominal 0x%x.%04x)\n",
+ ep->ep_num, f >> 16, f & 0xffff,
+ ep->freqn >> 16, ep->freqn & 0xffff);
+ f = ep->freqn;
+ }
+
if (likely(f >= ep->freqn - ep->freqn / 8 && f <= ep->freqmax)) {
/*
* If the frequency looks valid, set it.
diff --git a/sound/usb/line6/capture.c b/sound/usb/line6/capture.c
index 6dbaf30232f9..3e1f40d6b481 100644
--- a/sound/usb/line6/capture.c
+++ b/sound/usb/line6/capture.c
@@ -117,7 +117,8 @@ void line6_capture_copy(struct snd_line6_pcm *line6pcm, char *fbuf, int fsize)
} else {
/* copy single chunk */
memcpy(runtime->dma_area +
- line6pcm->in.pos_done * bytes_per_frame, fbuf, fsize);
+ line6pcm->in.pos_done * bytes_per_frame, fbuf,
+ frames * bytes_per_frame);
}
line6pcm->in.pos_done += frames;
diff --git a/sound/usb/line6/pcm.c b/sound/usb/line6/pcm.c
index 2932eaf157f4..d17baf7761c6 100644
--- a/sound/usb/line6/pcm.c
+++ b/sound/usb/line6/pcm.c
@@ -155,7 +155,7 @@ static int line6_buffer_acquire(struct snd_line6_pcm *line6pcm,
/* 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,
+ kzalloc(array3_size(line6pcm->line6->iso_buffers,
LINE6_ISO_PACKETS, pkt_size),
GFP_KERNEL);
if (!pstr->buffer)
@@ -173,6 +173,14 @@ static void line6_buffer_release(struct snd_line6_pcm *line6pcm,
clear_bit(type, &pstr->opened);
if (!pstr->opened) {
line6_wait_clear_audio_urbs(line6pcm, pstr);
+ if (pstr == &line6pcm->in) {
+ /* prev_fbuf points into the capture buffer we are about
+ * to free; drop it while no capture URB is in flight.
+ */
+ guard(spinlock_irqsave)(&line6pcm->in.lock);
+ line6pcm->prev_fbuf = NULL;
+ line6pcm->prev_fsize = 0;
+ }
kfree(pstr->buffer);
pstr->buffer = NULL;
}
@@ -554,7 +562,11 @@ int line6_init_pcm(struct usb_line6 *line6,
line6pcm->max_packet_size_out =
usb_maxpacket(line6->usbdev,
usb_sndisocpipe(line6->usbdev, ep_write));
- if (!line6pcm->max_packet_size_in || !line6pcm->max_packet_size_out) {
+ /* reject max packet sizes smaller than bytes-per-frame;
+ * (the magic number 6 is taken from the playback case)
+ */
+ if (line6pcm->max_packet_size_in < 6 ||
+ line6pcm->max_packet_size_out < 6) {
dev_err(line6pcm->line6->ifcdev,
"cannot get proper max packet size\n");
return -EINVAL;
diff --git a/sound/usb/midi.c b/sound/usb/midi.c
index 7e5b1b13360f..e3d157554061 100644
--- a/sound/usb/midi.c
+++ b/sound/usb/midi.c
@@ -113,6 +113,7 @@ struct snd_usb_midi {
unsigned int opened[2];
unsigned char disconnected;
unsigned char input_running;
+ unsigned char keep_input_running;
struct snd_kcontrol *roland_load_ctl;
};
@@ -1149,41 +1150,42 @@ 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;
+ struct snd_ctl_elem_id ctl_id;
+ bool activate_ctl = false;
+ bool active;
guard(rwsem_read)(&umidi->disc_rwsem);
if (umidi->disconnected)
return open ? -ENODEV : 0;
- guard(mutex)(&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);
+ scoped_guard(mutex, &umidi->mutex) {
+ if (open) {
+ if (!umidi->opened[0] && !umidi->opened[1]) {
+ if (umidi->roland_load_ctl) {
+ ctl_id = umidi->roland_load_ctl->id;
+ activate_ctl = true;
+ active = false;
+ 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);
+ umidi->opened[dir]++;
+ if (umidi->opened[1])
+ snd_usbmidi_input_start(&umidi->list);
+ } else {
+ umidi->opened[dir]--;
+ if (!umidi->opened[1] && !umidi->keep_input_running)
+ snd_usbmidi_input_stop(&umidi->list);
+ if (!umidi->opened[0] && !umidi->opened[1]) {
+ if (umidi->roland_load_ctl) {
+ ctl_id = umidi->roland_load_ctl->id;
+ activate_ctl = true;
+ active = true;
+ }
}
}
}
+ if (activate_ctl)
+ snd_ctl_activate_id(umidi->card, &ctl_id, active);
return 0;
}
@@ -1281,10 +1283,7 @@ static void snd_usbmidi_input_trigger(struct snd_rawmidi_substream *substream,
{
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);
+ assign_bit(substream->number, &umidi->input_triggered, up);
}
static const struct snd_rawmidi_ops snd_usbmidi_output_ops = {
@@ -2262,6 +2261,10 @@ static int snd_usbmidi_detect_roland(struct snd_usb_midi *umidi,
struct usb_host_interface *hostif;
u8 *cs_desc;
+ /* TD-11 becomes unusable if input isn't serviced continuously */
+ if (umidi->usb_id == USB_ID(0x0582, 0x0151))
+ umidi->keep_input_running = 1;
+
intf = umidi->iface;
if (!intf)
return -ENOENT;
@@ -2469,7 +2472,7 @@ void snd_usbmidi_input_start(struct list_head *p)
int i;
umidi = list_entry(p, struct snd_usb_midi, list);
- if (umidi->input_running || !umidi->opened[1])
+ if (umidi->input_running || (!umidi->opened[1] && !umidi->keep_input_running))
return;
for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i)
snd_usbmidi_input_start_ep(umidi, umidi->endpoints[i].in);
@@ -2644,6 +2647,10 @@ int __snd_usbmidi_create(struct snd_card *card,
if (err < 0)
goto exit;
+ /* start input for devices that need continuous servicing */
+ if (umidi->keep_input_running)
+ snd_usbmidi_input_start(&umidi->list);
+
usb_autopm_get_interface_no_resume(umidi->iface);
list_add_tail(&umidi->list, midi_list);
diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c
index ecaa8bc08d7c..a95f26a3cfa7 100644
--- a/sound/usb/mixer.c
+++ b/sound/usb/mixer.c
@@ -304,8 +304,9 @@ static inline int mixer_ctrl_intf(struct usb_mixer_interface *mixer)
return get_iface_desc(mixer->hostif)->bInterfaceNumber;
}
-static int get_ctl_value_v1(struct usb_mixer_elem_info *cval, int request,
- int validx, int *value_ret)
+/* send a request for UAC1 feature & mixer unit */
+static int request_get_ctl_v1(struct usb_mixer_elem_info *cval,
+ u8 request, int validx, int *value_ret)
{
struct snd_usb_audio *chip = cval->head.mixer->chip;
unsigned char buf[2];
@@ -317,6 +318,8 @@ static int get_ctl_value_v1(struct usb_mixer_elem_info *cval, int request,
if (pm.err < 0)
return -EIO;
+ validx += cval->idx_off;
+
while (timeout-- > 0) {
idx = mixer_ctrl_intf(cval->head.mixer) | (cval->head.id << 8);
err = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), request,
@@ -335,94 +338,84 @@ static int get_ctl_value_v1(struct usb_mixer_elem_info *cval, int request,
return -EINVAL;
}
-static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request,
- int validx, int *value_ret)
+/* convert the given UAC1 wValue (ICN|OCN) to UAC2 MCN */
+static unsigned char to_mcn(const struct usb_mixer_elem_info *cval,
+ unsigned int validx)
{
- 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;
+ unsigned char m = (validx >> 8) & 0xff; /* 1-based input channel */
+ unsigned char v = validx & 0xff; /* 1-based output channel */
- val_size = uac2_ctl_value_size(cval->val_type);
+ /* num_inputs * num_outputs is guaranteed to be < 256 */
+ return (m - 1) * cval->num_outputs + (v - 1);
+}
- if (request == UAC_GET_CUR) {
- bRequest = UAC2_CS_CUR;
- size = val_size;
- } else {
- bRequest = UAC2_CS_RANGE;
- size = sizeof(__u16) + 3 * val_size;
- }
+/* send a request for UAC2 feature & mixer unit */
+static int request_get_ctl_v2(struct usb_mixer_elem_info *cval,
+ u8 request, int validx, unsigned char *buf,
+ int size)
+{
+ struct snd_usb_audio *chip = cval->head.mixer->chip;
+ int idx, ret;
- memset(buf, 0, sizeof(buf));
+ CLASS(snd_usb_lock, pm)(chip);
+ if (pm.err)
+ return -EIO;
- {
- CLASS(snd_usb_lock, pm)(chip);
- if (pm.err)
- return -EIO;
+ validx += cval->idx_off;
- idx = mixer_ctrl_intf(cval->head.mixer) | (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);
- }
+ /* correct wValue for UAC2 mixer control with MCN */
+ if (cval->v2_mixer)
+ validx = (UAC2_MU_MIXER << 8) | to_mcn(cval, validx);
- if (ret < 0) {
+ memset(buf, 0, size);
+ idx = mixer_ctrl_intf(cval->head.mixer) | (cval->head.id << 8);
+ ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0),
+ request,
+ USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
+ validx, idx, buf, size);
+ if (ret < 0)
usb_audio_dbg(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;
+ return ret;
}
-static int get_ctl_value(struct usb_mixer_elem_info *cval, int request,
- int validx, int *value_ret)
+/* read the current value for UAC2 */
+static int get_ctl_value_v2(struct usb_mixer_elem_info *cval,
+ int validx, int *value_ret)
{
- validx += cval->idx_off;
+ /* enough space for one value */
+ unsigned char buf[sizeof(__u32)];
+ int ret, val_size;
- 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);
+ val_size = uac2_ctl_value_size(cval->val_type);
+
+ ret = request_get_ctl_v2(cval, UAC2_CS_CUR, validx, buf, val_size);
+ if (ret < 0)
+ return ret;
+
+ *value_ret = convert_signed_value(cval,
+ snd_usb_combine_bytes(buf, val_size));
+ return 0;
}
+/* read the current value */
static int get_cur_ctl_value(struct usb_mixer_elem_info *cval,
- int validx, int *value)
+ int validx, int *value_ret)
{
- return get_ctl_value(cval, UAC_GET_CUR, validx, value);
+ return (cval->head.mixer->protocol == UAC_VERSION_1) ?
+ request_get_ctl_v1(cval, UAC_GET_CUR, validx, value_ret) :
+ get_ctl_value_v2(cval, validx, value_ret);
}
/* 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);
+ return get_cur_ctl_value(cval,
+ (cval->control << 8) | channel,
+ value);
}
int snd_usb_get_cur_mix_value(struct usb_mixer_elem_info *cval,
@@ -430,7 +423,7 @@ int snd_usb_get_cur_mix_value(struct usb_mixer_elem_info *cval,
{
int err;
- if (cval->cached & BIT(channel)) {
+ if (test_bit(channel, cval->cached)) {
*value = cval->cache_val[index];
return 0;
}
@@ -447,11 +440,86 @@ int snd_usb_get_cur_mix_value(struct usb_mixer_elem_info *cval,
cval->control, channel, err);
return err;
}
- cval->cached |= BIT(channel);
+ set_bit(channel, cval->cached);
cval->cache_val[index] = *value;
return 0;
}
+/* extract the mixer min/max/res info from UAC1 feature / mixer unit */
+static int get_ctl_range_v1(struct usb_mixer_elem_info *cval, int validx)
+{
+ int last_valid_res;
+
+ if (request_get_ctl_v1(cval, UAC_GET_MAX, validx, &cval->max) < 0 ||
+ request_get_ctl_v1(cval, UAC_GET_MIN, validx, &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, mixer_ctrl_intf(cval->head.mixer),
+ cval->control, cval->head.id);
+ return -EAGAIN; /* handled by the caller later again */
+ }
+
+ if (request_get_ctl_v1(cval, UAC_GET_RES, validx, &cval->res) < 0) {
+ cval->res = 1;
+ return 0;
+ }
+
+ last_valid_res = cval->res;
+ while (cval->res > 1) {
+ if (snd_usb_mixer_set_ctl_value(cval, UAC_SET_RES,
+ validx, cval->res / 2) < 0)
+ break;
+ cval->res /= 2;
+ }
+ if (request_get_ctl_v1(cval, UAC_GET_RES, validx, &cval->res) < 0)
+ cval->res = last_valid_res;
+
+ return 0;
+}
+
+/* extract the mixer min/max/res info from UAC2 feature / mixer unit */
+static int get_ctl_range_v2(struct usb_mixer_elem_info *cval, int validx)
+{
+ /* enough space for one range */
+ unsigned char buf[sizeof(__u16) + 3 * sizeof(__u32)];
+ unsigned char *val;
+ int val_size, size;
+
+ val_size = uac2_ctl_value_size(cval->val_type);
+ size = sizeof(__u16) + 3 * val_size;
+
+ if (request_get_ctl_v2(cval, UAC2_CS_RANGE, validx, buf, size) < 0) {
+ usb_audio_err(cval->head.mixer->chip,
+ "%d:%d: cannot get RANGE values for control %d (id %d)\n",
+ cval->head.id, mixer_ctrl_intf(cval->head.mixer),
+ cval->control, cval->head.id);
+ return -EAGAIN; /* handled by the caller later again */
+ }
+
+ /* FIXME: how should we handle multiple triplets here? */
+ val = buf + 2;
+ cval->min = convert_signed_value(cval, snd_usb_combine_bytes(val, val_size));
+ val += val_size;
+ cval->max = convert_signed_value(cval, snd_usb_combine_bytes(val, val_size));
+ val += val_size;
+ cval->res = convert_signed_value(cval, snd_usb_combine_bytes(val, val_size));
+ return 0;
+}
+
+/* extract the mixer min/max/res info */
+static int get_ctl_range(struct usb_mixer_elem_info *cval, int validx)
+{
+ switch (cval->head.mixer->protocol) {
+ case UAC_VERSION_1:
+ return get_ctl_range_v1(cval, validx);
+ case UAC_VERSION_2:
+ case UAC_VERSION_3:
+ return get_ctl_range_v2(cval, validx);
+ default:
+ return -EINVAL;
+ }
+}
+
/*
* set a mixer value
*/
@@ -478,6 +546,10 @@ int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval,
}
request = UAC2_CS_CUR;
+
+ /* correct wValue for UAC2 mixer control with MCN */
+ if (cval->v2_mixer)
+ validx = (UAC2_MU_MIXER << 8) | to_mcn(cval, validx);
}
value_set = convert_bytes_value(cval, value_set);
@@ -532,7 +604,7 @@ int snd_usb_set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel,
value);
if (err < 0)
return err;
- cval->cached |= BIT(channel);
+ set_bit(channel, cval->cached);
cval->cache_val[index] = value;
return 0;
}
@@ -577,20 +649,25 @@ static int check_matrix_bitmap(unsigned char *bmap,
/*
* add an alsa control element
- * search and increment the index until an empty slot is found.
+ * when @look_for_empty is set, 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)
+ bool is_std_info,
+ bool look_for_empty)
{
struct usb_mixer_interface *mixer = list->mixer;
int err;
- while (snd_ctl_find_id(mixer->chip->card, &kctl->id))
- kctl->id.index++;
+ if (look_for_empty) {
+ 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",
@@ -786,7 +863,7 @@ static int parse_term_uac2_iterm_unit(struct mixer_build *state,
return err;
/* save input term properties after recursion,
- * to ensure they are not overriden by the recursion calls
+ * to ensure they are not overridden by the recursion calls
*/
term->id = id;
term->type = le16_to_cpu(d->wTerminalType);
@@ -809,7 +886,7 @@ static int parse_term_uac3_iterm_unit(struct mixer_build *state,
return err;
/* save input term properties after recursion,
- * to ensure they are not overriden by the recursion calls
+ * to ensure they are not overridden by the recursion calls
*/
term->id = id;
term->type = le16_to_cpu(d->wTerminalType);
@@ -1344,32 +1421,11 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval,
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, mixer_ctrl_intf(cval->head.mixer),
- cval->control, cval->head.id);
- return -EAGAIN;
- }
- if (get_ctl_value(cval, UAC_GET_RES,
- (cval->control << 8) | minchn,
- &cval->res) < 0) {
- cval->res = 1;
- } else if (cval->head.mixer->protocol == UAC_VERSION_1) {
- 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;
- }
+ ret = get_ctl_range(cval, (cval->control << 8) | minchn);
+ if (ret < 0)
+ return ret;
+
if (cval->res == 0)
cval->res = 1;
@@ -1398,7 +1454,7 @@ no_checks:
* properly.
*/
if (ret)
- cval->cached = 0;
+ bitmap_zero(cval->cached, MAX_CHANNELS + 1);
cval->initialized = 1;
}
@@ -1480,6 +1536,7 @@ static int mixer_ctl_feature_info(struct snd_kcontrol *kcontrol,
struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol);
int ret;
+ guard(mutex)(&cval->head.mixer->lock);
if (cval->val_type == USB_MIXER_BOOLEAN ||
cval->val_type == USB_MIXER_INV_BOOLEAN)
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
@@ -1514,6 +1571,7 @@ static int mixer_ctl_feature_get(struct snd_kcontrol *kcontrol,
struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol);
int c, cnt, val, err;
+ guard(mutex)(&cval->head.mixer->lock);
ucontrol->value.integer.value[0] = cval->min;
if (cval->cmask) {
cnt = 0;
@@ -1544,10 +1602,12 @@ static int mixer_ctl_feature_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol);
- int max_val = get_max_exposed(cval);
+ int max_val;
int c, cnt, val, oval, err;
int changed = 0;
+ guard(mutex)(&cval->head.mixer->lock);
+ max_val = get_max_exposed(cval);
if (cval->cmask) {
cnt = 0;
for (c = 0; c < MAX_CHANNELS; c++) {
@@ -1595,6 +1655,7 @@ static int mixer_ctl_master_bool_get(struct snd_kcontrol *kcontrol,
struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol);
int val, err;
+ guard(mutex)(&cval->head.mixer->lock);
err = snd_usb_get_cur_mix_value(cval, 0, 0, &val);
if (err < 0)
return filter_error(cval, err);
@@ -2326,7 +2387,7 @@ static bool mixer_bitmap_overflow(struct uac_mixer_unit_descriptor *desc,
*/
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 in_ch, int num_outs,
int unitid, struct usb_audio_term *iterm)
{
struct usb_mixer_elem_info *cval;
@@ -2345,6 +2406,10 @@ static void build_mixer_unit_ctl(struct mixer_build *state,
snd_usb_mixer_elem_init_std(&cval->head, state->mixer, unitid);
cval->control = in_ch + 1; /* based on 1 */
+ if (state->mixer->protocol == UAC_VERSION_2 ||
+ state->mixer->protocol == UAC_VERSION_3)
+ cval->v2_mixer = true;
+ cval->num_outputs = num_outs;
cval->val_type = USB_MIXER_S16;
for (i = 0; i < num_outs; i++) {
__u8 *c = uac_mixer_unit_bmControls(desc, state->mixer->protocol);
@@ -2417,24 +2482,13 @@ static int parse_audio_input_terminal(struct mixer_build *state, int unitid,
/*
* parse a mixer unit
*/
-static int parse_audio_mixer_unit(struct mixer_build *state, int unitid,
- void *raw_desc)
+/* UAC1 mixer unit */
+static int parse_audio_mixer_unit_v1(struct mixer_build *state, int unitid,
+ int input_pins, int num_outs,
+ struct uac_mixer_unit_descriptor *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;
+ int num_ins, pin, ich, och, err;
num_ins = 0;
ich = 0;
@@ -2453,25 +2507,90 @@ static int parse_audio_mixer_unit(struct mixer_build *state, int unitid,
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;
+ if (check_matrix_bitmap(c, ich, och, num_outs))
break;
- }
}
- if (ich_has_controls)
- build_mixer_unit_ctl(state, desc, pin, ich, num_outs,
+ if (och < num_outs)
+ build_mixer_unit_ctl(state, desc, ich, num_outs,
unitid, &iterm);
}
}
return 0;
}
+/* UAC2/UAC3 mixer unit */
+static int parse_audio_mixer_unit_v2(struct mixer_build *state, int unitid,
+ int input_pins, int num_outs,
+ struct uac_mixer_unit_descriptor *desc)
+{
+ struct usb_audio_term iterm;
+ int pin, och, err;
+
+ if (input_pins * num_outs > 256 ||
+ mixer_bitmap_overflow(desc, state->mixer->protocol,
+ input_pins, num_outs)) {
+ usb_audio_err(state->chip,
+ "invalid channels for MIXER UNIT %d: input=%d, output=%d\n",
+ unitid, input_pins, num_outs);
+ return -EINVAL;
+ }
+
+ for (pin = 0; pin < input_pins; pin++) {
+ err = parse_audio_unit(state, desc->baSourceID[pin]);
+ if (err < 0)
+ continue;
+ if (!num_outs)
+ continue;
+ err = check_input_term(state, desc->baSourceID[pin], &iterm);
+ if (err < 0)
+ return err;
+
+ for (och = 0; och < num_outs; och++) {
+ __u8 *c = uac_mixer_unit_bmControls(desc,
+ state->mixer->protocol);
+
+ if (check_matrix_bitmap(c, pin, och, num_outs))
+ break;
+ }
+ if (och < num_outs)
+ build_mixer_unit_ctl(state, desc, pin, num_outs,
+ unitid, &iterm);
+ }
+ return 0;
+}
+
+static int parse_audio_mixer_unit(struct mixer_build *state, int unitid,
+ void *raw_desc)
+{
+ struct uac_mixer_unit_descriptor *desc = raw_desc;
+ int num_outs;
+
+ num_outs = uac_mixer_unit_get_channels(state, desc);
+ if (num_outs < 0) {
+ usb_audio_err(state->chip,
+ "invalid MIXER UNIT descriptor %d\n",
+ unitid);
+ return num_outs;
+ }
+
+ switch (state->mixer->protocol) {
+ case UAC_VERSION_1:
+ default:
+ return parse_audio_mixer_unit_v1(state, unitid,
+ desc->bNrInPins, num_outs,
+ desc);
+ case UAC_VERSION_2:
+ case UAC_VERSION_3:
+ return parse_audio_mixer_unit_v2(state, unitid,
+ desc->bNrInPins, num_outs,
+ desc);
+ }
+}
+
/*
* Processing Unit / Extension Unit
*/
@@ -2483,6 +2602,7 @@ static int mixer_ctl_procunit_get(struct snd_kcontrol *kcontrol,
struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol);
int err, val;
+ guard(mutex)(&cval->head.mixer->lock);
err = get_cur_ctl_value(cval, cval->control << 8, &val);
if (err < 0) {
ucontrol->value.integer.value[0] = cval->min;
@@ -2500,6 +2620,7 @@ static int mixer_ctl_procunit_put(struct snd_kcontrol *kcontrol,
struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol);
int val, oval, err;
+ guard(mutex)(&cval->head.mixer->lock);
err = get_cur_ctl_value(cval, cval->control << 8, &oval);
if (err < 0)
return filter_error(cval, err);
@@ -2851,6 +2972,7 @@ static int mixer_ctl_selector_get(struct snd_kcontrol *kcontrol,
struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol);
int val, err;
+ guard(mutex)(&cval->head.mixer->lock);
err = get_cur_ctl_value(cval, cval->control << 8, &val);
if (err < 0) {
ucontrol->value.enumerated.item[0] = 0;
@@ -2868,6 +2990,7 @@ static int mixer_ctl_selector_put(struct snd_kcontrol *kcontrol,
struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol);
int val, oval, err;
+ guard(mutex)(&cval->head.mixer->lock);
err = get_cur_ctl_value(cval, cval->control << 8, &oval);
if (err < 0)
return filter_error(cval, err);
@@ -3140,6 +3263,7 @@ static void snd_usb_mixer_free(struct usb_mixer_interface *mixer)
}
usb_free_urb(mixer->rc_urb);
kfree(mixer->rc_setup_packet);
+ mutex_destroy(&mixer->lock);
kfree(mixer);
}
@@ -3550,7 +3674,7 @@ void snd_usb_mixer_notify_id(struct usb_mixer_interface *mixer, int unitid)
info = mixer_elem_list_to_info(list);
/* invalidate cache, so the value is read from the device */
if (!info->get_cur_broken)
- info->cached = 0;
+ bitmap_zero(info->cached, MAX_CHANNELS + 1);
snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
&list->kctl->id);
}
@@ -3617,7 +3741,7 @@ static void snd_usb_mixer_interrupt_v2(struct usb_mixer_interface *mixer,
__u8 channel = value & 0xff;
unsigned int count = 0;
- if (channel >= MAX_CHANNELS) {
+ if (channel > MAX_CHANNELS) {
usb_audio_dbg(mixer->chip,
"%s(): bogus channel number %d\n",
__func__, channel);
@@ -3649,9 +3773,9 @@ static void snd_usb_mixer_interrupt_v2(struct usb_mixer_interface *mixer,
/* invalidate cache, so the value is read from the device */
if (!info->get_cur_broken) {
if (channel)
- info->cached &= ~BIT(channel);
+ clear_bit(channel, info->cached);
else /* master channel */
- info->cached = 0;
+ bitmap_zero(info->cached, MAX_CHANNELS + 1);
}
snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
@@ -3784,6 +3908,8 @@ int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif)
return -ENOMEM;
}
+ mutex_init(&mixer->lock);
+
mixer->hostif = &usb_ifnum_to_if(chip->dev, ctrlif)->altsetting[0];
switch (get_iface_desc(mixer->hostif)->bInterfaceProtocol) {
case UAC_VERSION_1:
@@ -3887,7 +4013,7 @@ static int restore_mixer_value(struct usb_mixer_elem_list *list)
for (c = 0; c < MAX_CHANNELS; c++) {
if (!(cval->cmask & BIT(c)))
continue;
- if (cval->cached & BIT(c + 1)) {
+ if (test_bit(c + 1, cval->cached)) {
err = snd_usb_set_cur_mix_value(cval, c + 1, idx,
cval->cache_val[idx]);
if (err < 0)
@@ -3897,7 +4023,7 @@ static int restore_mixer_value(struct usb_mixer_elem_list *list)
}
} else {
/* master */
- if (cval->cached)
+ if (test_bit(0, cval->cached))
snd_usb_set_cur_mix_value(cval, 0, 0, *cval->cache_val);
}
diff --git a/sound/usb/mixer.h b/sound/usb/mixer.h
index 037b446d8b6f..c40f832852be 100644
--- a/sound/usb/mixer.h
+++ b/sound/usb/mixer.h
@@ -18,6 +18,7 @@ struct usb_mixer_interface {
struct usb_host_interface *hostif;
struct list_head list;
unsigned int ignore_ctl_error;
+ struct mutex lock; /* lock for feature unit callbacks */
/* UAC2 status interrupt endpoint; owned by mixer.c */
struct urb *urb;
/* array[MAX_ID_ELEMS], indexed by unit id */
@@ -92,11 +93,13 @@ struct usb_mixer_elem_info {
int min, max, res;
int max_exposed; /* control API exposes the value in 0..max_exposed */
int dBmin, dBmax;
- int cached;
+ DECLARE_BITMAP(cached, MAX_CHANNELS + 1);
int cache_val[MAX_CHANNELS];
u8 initialized;
u8 min_mute;
u8 get_cur_broken;
+ u8 num_outputs;
+ bool v2_mixer;
void *private_data;
};
@@ -110,10 +113,11 @@ int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval,
int snd_usb_mixer_add_list(struct usb_mixer_elem_list *list,
struct snd_kcontrol *kctl,
- bool is_std_info);
+ bool is_std_info,
+ bool look_for_empty);
#define snd_usb_mixer_add_control(list, kctl) \
- snd_usb_mixer_add_list(list, kctl, true)
+ snd_usb_mixer_add_list(list, kctl, true, true)
void snd_usb_mixer_elem_init_std(struct usb_mixer_elem_list *list,
struct usb_mixer_interface *mixer,
diff --git a/sound/usb/mixer_evo.c b/sound/usb/mixer_evo.c
new file mode 100644
index 000000000000..c327b82c8d2d
--- /dev/null
+++ b/sound/usb/mixer_evo.c
@@ -0,0 +1,505 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Audient EVO driver for ALSA
+ * Copyright (C) 2026 Christian Ruppert <arc@gmx.li>
+ *
+ * Based on the work of Ivan Hazucha for audient-evo-py.
+ * https://github.com/vanzaho/audient-evo-py/
+ */
+
+
+#include <asm/byteorder.h>
+#include <linux/usb.h>
+#include <linux/usb/audio-v2.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/control.h>
+
+#include "usbaudio.h"
+#include "mixer.h"
+#include "mixer_quirks.h"
+#include "helper.h"
+#include "mixer_evo.h"
+
+
+enum snd_evo_type {
+ SND_EVO_TYPE_MONITOR = 0,
+ SND_EVO_TYPE_PHANTOM,
+ SND_EVO_TYPE_MUTE,
+ SND_EVO_TYPE_MUTE_OUT,
+ SND_EVO_TYPE_MIXER,
+ SND_EVO_TYPE_NCTYPES
+};
+
+static const struct snd_evo_ctrl_type {
+ snd_ctl_elem_type_t type;
+ bool invert;
+ union {
+ int integer;
+ } min;
+ union {
+ int integer;
+ } max;
+ u16 wValue;
+ u16 wIndex;
+} snd_evo_ctypes[SND_EVO_TYPE_NCTYPES] = {
+ [SND_EVO_TYPE_MONITOR] = {
+ .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
+ .min.integer = 0,
+ .max.integer = 127,
+ .wValue = 0x0000,
+ .wIndex = 0x3800,
+ },
+ [SND_EVO_TYPE_PHANTOM] = {
+ .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
+ .invert = false,
+ .min.integer = 0,
+ .max.integer = 1,
+ .wValue = 0x0000,
+ .wIndex = 0x3A00,
+ },
+ [SND_EVO_TYPE_MUTE] = {
+ .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
+ .invert = true,
+ .min.integer = 0,
+ .max.integer = 1,
+ .wValue = 0x0200,
+ .wIndex = 0x3A00,
+ },
+ [SND_EVO_TYPE_MUTE_OUT] = {
+ .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
+ .invert = true,
+ .min.integer = 0,
+ .max.integer = 1,
+ .wValue = 0x0100,
+ .wIndex = 0x3B00,
+ },
+ [SND_EVO_TYPE_MIXER] = {
+ .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
+ .min.integer = -128 * 256,
+ .max.integer = 6 * 256,
+ .wValue = 0x0100,
+ .wIndex = 0x3C00,
+ },
+};
+
+#define SND_EVO_FLAGS_DIRECTION (1L<<0)
+#define SND_EVO_FLAGS_CAPTURE (1L<<0)
+#define SND_EVO_FLAGS_PLAYBACK (0L<<0)
+
+struct snd_evo_ctrl {
+ char *basename;
+ enum snd_evo_type type;
+ unsigned int channels;
+ unsigned int hwchannels;
+ unsigned int val_offs;
+ unsigned long flags;
+};
+
+static const struct snd_evo_ctrl evo4_controls[] = {
+ {
+ .basename = "Master",
+ .type = SND_EVO_TYPE_MUTE_OUT,
+ .channels = 1,
+ .hwchannels = 1,
+ .val_offs = 0,
+ .flags = SND_EVO_FLAGS_PLAYBACK,
+ },
+ {
+ .basename = "Mic",
+ .type = SND_EVO_TYPE_MUTE,
+ .channels = 4,
+ .hwchannels = 2,
+ .val_offs = 0,
+ .flags = SND_EVO_FLAGS_PLAYBACK,
+ },
+ {
+ .basename = "Monitor",
+ .type = SND_EVO_TYPE_MONITOR,
+ .channels = 1,
+ .hwchannels = 1,
+ .val_offs = 0,
+ .flags = SND_EVO_FLAGS_PLAYBACK,
+ },
+ {
+ .basename = "Mic 1",
+ .type = SND_EVO_TYPE_MIXER,
+ .channels = 2,
+ .hwchannels = 2,
+ .val_offs = 0,
+ .flags = SND_EVO_FLAGS_CAPTURE,
+ },
+ {
+ .basename = "Mic 2",
+ .type = SND_EVO_TYPE_MIXER,
+ .channels = 2,
+ .hwchannels = 2,
+ .val_offs = 2,
+ .flags = SND_EVO_FLAGS_CAPTURE,
+ },
+ {
+ .basename = "Master Left",
+ .type = SND_EVO_TYPE_MIXER,
+ .channels = 2,
+ .hwchannels = 2,
+ .val_offs = 4,
+ .flags = SND_EVO_FLAGS_CAPTURE,
+ },
+ {
+ .basename = "Master Right",
+ .type = SND_EVO_TYPE_MIXER,
+ .channels = 2,
+ .hwchannels = 2,
+ .val_offs = 6,
+ .flags = SND_EVO_FLAGS_CAPTURE,
+ },
+ {
+ .basename = "Loopback Left",
+ .type = SND_EVO_TYPE_MIXER,
+ .channels = 2,
+ .hwchannels = 2,
+ .val_offs = 8,
+ .flags = SND_EVO_FLAGS_CAPTURE,
+ },
+ {
+ .basename = "Loopback Right",
+ .type = SND_EVO_TYPE_MIXER,
+ .channels = 2,
+ .hwchannels = 2,
+ .val_offs = 10,
+ .flags = SND_EVO_FLAGS_CAPTURE,
+ },
+ {
+ .basename = "Mic 1 Phantom",
+ .type = SND_EVO_TYPE_PHANTOM,
+ .channels = 1,
+ .hwchannels = 1,
+ .val_offs = 0,
+ .flags = SND_EVO_FLAGS_CAPTURE,
+ },
+ {
+ .basename = "Mic 2 Phantom",
+ .type = SND_EVO_TYPE_PHANTOM,
+ .channels = 1,
+ .hwchannels = 1,
+ .val_offs = 1,
+ .flags = SND_EVO_FLAGS_CAPTURE,
+ },
+ { 0 }, /* sentinel */
+};
+
+static const struct evo_devinfo {
+ u32 usb_id;
+ const struct snd_evo_ctrl *controls;
+} evo_devinfo[] = {
+ {
+ .usb_id = USB_ID(USB_AUDIENT_VID, USB_EVO4_PID),
+ .controls = evo4_controls,
+ },
+ {
+ .usb_id = USB_ID(USB_AUDIENT_VID, USB_EVO4_PID),
+ .controls = NULL,
+ },
+ { 0 } /* sentinel */
+};
+
+
+static inline unsigned long snd_evo_kctl_priv(enum snd_evo_type type,
+ u8 valoffs, u8 hwchannels)
+{
+ unsigned long type_l = type & 0xFF;
+ unsigned long valoffs_l = valoffs & 0xFF;
+ unsigned long hwchannels_l = hwchannels & 0xFF;
+
+ return (hwchannels_l << 16) | (valoffs_l << 8) | type_l;
+}
+
+static __always_inline
+const struct snd_evo_ctrl_type *snd_evo_kctl_type(struct snd_kcontrol *kctl)
+{
+ return &snd_evo_ctypes[kctl->private_value & 0xFF];
+}
+
+static __always_inline u16 snd_evo_kctl_valoffs(struct snd_kcontrol *kctl)
+{
+ return (kctl->private_value >> 8) & 0xFF;
+}
+
+static __always_inline u16 snd_evo_kctl_hwchannels(struct snd_kcontrol *kctl)
+{
+ return (kctl->private_value >> 16) & 0xFF;
+}
+
+static inline u16 snd_evo_kctl_channels(struct snd_kcontrol *kctl)
+{
+ struct usb_mixer_elem_info *info = kctl->private_data;
+
+ return info->channels;
+}
+
+
+#define EVO_REQ_CUR (0x01)
+#define EVO_REQTYPE_SET (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE)
+#define EVO_REQTYPE_GET (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE)
+
+static inline int snd_evo_get_cur(struct usb_mixer_interface *mixer,
+ u16 value, u16 index,
+ void *buf, size_t len)
+{
+ struct usb_device *const dev = mixer->chip->dev;
+
+ return snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0),
+ EVO_REQ_CUR, EVO_REQTYPE_GET,
+ value, index, buf, len);
+}
+
+static inline int snd_evo_set_cur(struct usb_mixer_interface *mixer,
+ u16 value, u16 index,
+ void *buf, size_t len)
+{
+ struct usb_device *const dev = mixer->chip->dev;
+
+ return snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0),
+ EVO_REQ_CUR, EVO_REQTYPE_SET,
+ value, index, buf, len);
+}
+
+
+static int snd_ctl_evo_boolean_get(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *ctl_val)
+{
+ struct usb_mixer_elem_list *const list = snd_kcontrol_chip(kctl);
+ struct usb_mixer_interface *const mixer = list->mixer;
+
+ const unsigned int nchan = snd_evo_kctl_channels(kctl);
+ const unsigned int hwchan = snd_evo_kctl_hwchannels(kctl);
+ const struct snd_evo_ctrl_type *const ctype = snd_evo_kctl_type(kctl);
+ const u16 value = ctype->wValue + snd_evo_kctl_valoffs(kctl);
+ const u16 index = ctype->wIndex;
+
+ int i;
+
+ for (i = 0; i < hwchan; i++) {
+ u32 buf;
+ int ret;
+
+ ret = snd_evo_get_cur(mixer, value + i, index,
+ &buf, sizeof(buf));
+ if (ret < 0)
+ return ret;
+
+ ctl_val->value.integer.value[i] = le32_to_cpu(buf) ? 1 : 0;
+
+ if (ctype->invert)
+ ctl_val->value.integer.value[i] ^= 0x1;
+ }
+
+ for (; i < nchan; i++)
+ ctl_val->value.integer.value[i] = ctype->invert
+ ? ctype->max.integer
+ : ctype->min.integer;
+
+ return 0;
+}
+
+static int snd_ctl_evo_boolean_set(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *ctl_val)
+{
+ struct usb_mixer_elem_list *list = snd_kcontrol_chip(kctl);
+ struct usb_mixer_interface *mixer = list->mixer;
+
+ const unsigned int hwchan = snd_evo_kctl_hwchannels(kctl);
+ const struct snd_evo_ctrl_type *const ctype = snd_evo_kctl_type(kctl);
+ const u16 value = ctype->wValue + snd_evo_kctl_valoffs(kctl);
+ const u16 index = ctype->wIndex;
+
+ int changed = 0;
+ int i;
+
+ for (i = 0; i < hwchan; i++) {
+ u32 rbuf;
+ u32 buf = cpu_to_le32(ctl_val->value.integer.value[i] ? 1 : 0);
+ int ret;
+
+ if (ctype->invert)
+ buf ^= 0x1;
+
+ ret = snd_evo_get_cur(mixer, value + i, index,
+ &rbuf, sizeof(rbuf));
+ if (ret < 0)
+ return ret;
+
+ if (rbuf != buf) {
+ changed = 1;
+
+ ret = snd_evo_set_cur(mixer, value + i, index,
+ &buf, sizeof(buf));
+ if (ret < 0)
+ return ret;
+ }
+ }
+
+ return changed;
+}
+
+static int snd_ctl_evo_integer_get(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *ctl_val)
+{
+ struct usb_mixer_elem_list *const list = snd_kcontrol_chip(kctl);
+ struct usb_mixer_interface *const mixer = list->mixer;
+
+ const unsigned int nchan = snd_evo_kctl_channels(kctl);
+ const unsigned int hwchan = snd_evo_kctl_hwchannels(kctl);
+ const struct snd_evo_ctrl_type *const ctype = snd_evo_kctl_type(kctl);
+ const u16 value = ctype->wValue + snd_evo_kctl_valoffs(kctl);
+ const u16 index = ctype->wIndex;
+
+ int i;
+
+ for (i = 0; i < hwchan; i++) {
+ u16 buf;
+ s16 val;
+ int ret;
+
+ ret = snd_evo_get_cur(mixer, value + i, index,
+ &buf, sizeof(buf));
+ if (ret < 0)
+ return ret;
+
+ val = le16_to_cpu(buf);
+ ctl_val->value.integer.value[i] = val;
+ }
+
+ for (; i < nchan; i++)
+ ctl_val->value.integer.value[i] = ctype->min.integer;
+
+ return 0;
+}
+
+static int snd_ctl_evo_integer_set(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *ctl_val)
+{
+ struct usb_mixer_elem_list *list = snd_kcontrol_chip(kctl);
+ struct usb_mixer_interface *mixer = list->mixer;
+
+ const unsigned int hwchan = snd_evo_kctl_hwchannels(kctl);
+ const struct snd_evo_ctrl_type *const ctype = snd_evo_kctl_type(kctl);
+ const u16 value = ctype->wValue + snd_evo_kctl_valoffs(kctl);
+ const u16 index = ctype->wIndex;
+
+ int changed = 0;
+ int i;
+
+ for (i = 0; i < hwchan; i++) {
+ s16 rbuf;
+ s16 buf = cpu_to_le16(ctl_val->value.integer.value[i]);
+ int ret;
+
+ ret = snd_evo_get_cur(mixer, value + i, index,
+ &rbuf, sizeof(rbuf));
+ if (ret < 0)
+ return ret;
+
+ if (rbuf != buf) {
+ changed = 1;
+
+ ret = snd_evo_set_cur(mixer, value + i, index,
+ &buf, sizeof(buf));
+ if (ret < 0)
+ return ret;
+ }
+ }
+
+ return changed;
+}
+
+static int snd_ctl_evo_info(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_info *uinfo)
+{
+ const struct snd_evo_ctrl_type *const ctype = snd_evo_kctl_type(kctl);
+
+ uinfo->type = ctype->type;
+ uinfo->access = SNDRV_CTL_ELEM_ACCESS_READWRITE
+ | SNDRV_CTL_ELEM_ACCESS_VOLATILE;
+ uinfo->count = snd_evo_kctl_channels(kctl);
+ uinfo->value.integer.min = ctype->min.integer;
+ uinfo->value.integer.max = ctype->max.integer;
+ return 0;
+}
+
+
+static int snd_evo_add_ctrl(struct usb_mixer_interface *mixer,
+ const struct snd_evo_ctrl *ctrl)
+{
+ const struct snd_evo_ctrl_type *ctype = &snd_evo_ctypes[ctrl->type];
+ char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
+ struct snd_kcontrol_new knew = { 0 };
+ struct snd_kcontrol *kctl;
+ struct usb_mixer_elem_info *elem;
+
+ knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ knew.name = name;
+ knew.info = snd_ctl_evo_info;
+ knew.private_value = snd_evo_kctl_priv(ctrl->type, ctrl->val_offs,
+ ctrl->hwchannels);
+
+ switch (ctype->type) {
+ case SNDRV_CTL_ELEM_TYPE_INTEGER:
+ knew.get = snd_ctl_evo_integer_get;
+ knew.put = snd_ctl_evo_integer_set;
+ break;
+ case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
+ knew.get = snd_ctl_evo_boolean_get;
+ knew.put = snd_ctl_evo_boolean_set;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snprintf(name, sizeof(name), "%s %s %s", ctrl->basename,
+ ((ctrl->flags & SND_EVO_FLAGS_DIRECTION)
+ == SND_EVO_FLAGS_CAPTURE) ? "Capture" : "Playback",
+ (ctype->type == SNDRV_CTL_ELEM_TYPE_BOOLEAN)
+ ? "Switch" : "Volume");
+
+ elem = kzalloc_obj(*elem);
+ if (!elem)
+ return -ENOMEM;
+
+ elem->head.mixer = mixer;
+ elem->channels = ctrl->channels;
+
+ kctl = snd_ctl_new1(&knew, elem);
+ if (!kctl) {
+ kfree(elem);
+ return -ENOMEM;
+ }
+
+ kctl->private_free = snd_usb_mixer_elem_free;
+
+ return snd_usb_mixer_add_control(&elem->head, kctl);
+}
+
+
+/* called from mixer_quirks.c */
+int snd_evo_controls_create(struct usb_mixer_interface *mixer)
+{
+ const struct evo_devinfo *info;
+ const struct snd_evo_ctrl *ctrl;
+
+ for (info = evo_devinfo; info->controls; info++)
+ if (info->usb_id == mixer->chip->usb_id)
+ break;
+
+ if (!info->controls)
+ return -ENODEV;
+
+ for (ctrl = info->controls; ctrl->basename; ctrl++) {
+ const int ret = snd_evo_add_ctrl(mixer, ctrl);
+
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
diff --git a/sound/usb/mixer_evo.h b/sound/usb/mixer_evo.h
new file mode 100644
index 000000000000..7fe7e197185c
--- /dev/null
+++ b/sound/usb/mixer_evo.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __USB_MIXER_EVO_H
+#define __USB_MIXER_EVO_H
+
+#include "mixer.h"
+
+#define USB_AUDIENT_VID (0x2708)
+#define USB_EVO4_PID (0x0006)
+
+int snd_evo_controls_create(struct usb_mixer_interface *mixer);
+
+#endif /* __USB_MIXER_EVO_H */
diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c
index f8e1f78039e1..98b4146216ae 100644
--- a/sound/usb/mixer_maps.c
+++ b/sound/usb/mixer_maps.c
@@ -505,6 +505,14 @@ static const struct usbmix_connector_map gigabyte_b450_connector_map[] = {
{}
};
+/* Audient EVO4: Rename FU 10 from 'EVO4 ' to 'Master' according to the
+ * ALSA naming convention
+ */
+static const struct usbmix_name_map audient_evo4_map[] = {
+ { 10, "Master Playback" },
+ {}
+};
+
/* Audient iD14 MkI and MkII: FU 12 sits on the monitor mixer branch but is
* traced through to the Speaker output terminal, so it is named "Speaker
* Playback Volume". On MkII it controls only 4 of 6 playback channels. MkI
@@ -633,6 +641,11 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = {
.map = audient_id14_map,
},
{
+ /* Audient EVO4 MkI */
+ .id = USB_ID(0x2708, 0x0006),
+ .map = audient_evo4_map,
+ },
+ {
/* Audient iD14 MkII */
.id = USB_ID(0x2708, 0x0008),
.map = audient_id14_map,
diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c
index fc622eb95dc5..01210cc3fae7 100644
--- a/sound/usb/mixer_quirks.c
+++ b/sound/usb/mixer_quirks.c
@@ -38,6 +38,7 @@
#include "mixer_scarlett2.h"
#include "mixer_us16x08.h"
#include "mixer_s1810c.h"
+#include "mixer_evo.h"
#include "helper.h"
#include "fcp.h"
@@ -166,7 +167,7 @@ static int add_single_ctl_with_resume(struct usb_mixer_interface *mixer,
}
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);
+ return snd_usb_mixer_add_list(list, kctl, false, true);
}
/*
@@ -4539,6 +4540,10 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer)
err = snd_fcp_init(mixer);
break;
+ case USB_ID(USB_AUDIENT_VID, USB_EVO4_PID): /* Audient EVO 4 */
+ err = snd_evo_controls_create(mixer);
+ break;
+
case USB_ID(0x041e, 0x323b): /* Creative Sound Blaster E1 */
err = snd_soundblaster_e1_switch_create(mixer);
break;
diff --git a/sound/usb/mixer_scarlett.c b/sound/usb/mixer_scarlett.c
index 369968565c19..6808730ef047 100644
--- a/sound/usb/mixer_scarlett.c
+++ b/sound/usb/mixer_scarlett.c
@@ -292,7 +292,7 @@ static int forte_get_ctl_value(struct usb_mixer_elem_info *elem, int *value)
/* Device may not support reading input controls.
* Return cached value or default to avoid blocking module load.
*/
- if (elem->cached)
+ if (test_bit(0, elem->cached))
*value = elem->cache_val[0];
else
*value = 0; /* Default: first option */
@@ -353,7 +353,7 @@ static int forte_input_gain_put(struct snd_kcontrol *kctl,
err = forte_set_ctl_value(elem, val);
if (err < 0)
return err;
- elem->cached |= 1;
+ set_bit(0, elem->cached);
elem->cache_val[0] = val;
return 1;
}
@@ -364,7 +364,7 @@ static int forte_input_gain_resume(struct usb_mixer_elem_list *list)
{
struct usb_mixer_elem_info *elem = mixer_elem_list_to_info(list);
- if (elem->cached)
+ if (test_bit(0, elem->cached))
forte_set_ctl_value(elem, *elem->cache_val);
return 0;
}
@@ -405,7 +405,7 @@ static int forte_ctl_enum_put(struct snd_kcontrol *kctl,
err = forte_set_ctl_value(elem, val);
if (err < 0)
return err;
- elem->cached |= 1;
+ set_bit(0, elem->cached);
elem->cache_val[0] = val;
return 1;
}
@@ -416,7 +416,7 @@ static int forte_ctl_enum_resume(struct usb_mixer_elem_list *list)
{
struct usb_mixer_elem_info *elem = mixer_elem_list_to_info(list);
- if (elem->cached)
+ if (test_bit(0, elem->cached))
forte_set_ctl_value(elem, *elem->cache_val);
return 0;
}
@@ -454,7 +454,7 @@ static int forte_ctl_switch_put(struct snd_kcontrol *kctl,
err = forte_set_ctl_value(elem, val);
if (err < 0)
return err;
- elem->cached |= 1;
+ set_bit(0, elem->cached);
elem->cache_val[0] = val;
return 1;
}
@@ -465,7 +465,7 @@ static int forte_ctl_switch_resume(struct usb_mixer_elem_list *list)
{
struct usb_mixer_elem_info *elem = mixer_elem_list_to_info(list);
- if (elem->cached)
+ if (test_bit(0, elem->cached))
forte_set_ctl_value(elem, *elem->cache_val);
return 0;
}
@@ -532,7 +532,7 @@ static int scarlett_ctl_resume(struct usb_mixer_elem_list *list)
int i;
for (i = 0; i < elem->channels; i++)
- if (elem->cached & (1 << i))
+ if (test_bit(i, elem->cached))
snd_usb_set_cur_mix_value(elem, i, i,
elem->cache_val[i]);
return 0;
@@ -692,7 +692,7 @@ 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)
+ if (test_bit(0, elem->cached))
snd_usb_set_cur_mix_value(elem, 0, 0, *elem->cache_val);
return 0;
}
diff --git a/sound/usb/mixer_us16x08.c b/sound/usb/mixer_us16x08.c
index 14fb1ad764a7..18e8c2bd2678 100644
--- a/sound/usb/mixer_us16x08.c
+++ b/sound/usb/mixer_us16x08.c
@@ -189,6 +189,7 @@ static int snd_us16x08_route_get(struct snd_kcontrol *kcontrol,
struct usb_mixer_elem_info *elem = snd_kcontrol_chip(kcontrol);
int index = ucontrol->id.index;
+ guard(mutex)(&elem->head.mixer->lock);
/* route has no bias */
ucontrol->value.enumerated.item[0] = elem->cache_val[index];
@@ -204,6 +205,7 @@ static int snd_us16x08_route_put(struct snd_kcontrol *kcontrol,
char buf[sizeof(route_msg)];
int val, val_org, err;
+ guard(mutex)(&elem->head.mixer->lock);
/* get the new value (no bias for routes) */
val = ucontrol->value.enumerated.item[0];
@@ -236,7 +238,7 @@ static int snd_us16x08_route_put(struct snd_kcontrol *kcontrol,
return err;
}
- elem->cached |= 1 << index;
+ set_bit(index, elem->cached);
elem->cache_val[index] = val;
return 1;
}
@@ -258,6 +260,7 @@ static int snd_us16x08_master_get(struct snd_kcontrol *kcontrol,
struct usb_mixer_elem_info *elem = snd_kcontrol_chip(kcontrol);
int index = ucontrol->id.index;
+ guard(mutex)(&elem->head.mixer->lock);
ucontrol->value.integer.value[0] = elem->cache_val[index];
return 0;
@@ -272,6 +275,7 @@ static int snd_us16x08_master_put(struct snd_kcontrol *kcontrol,
int val, err;
int index = ucontrol->id.index;
+ guard(mutex)(&elem->head.mixer->lock);
/* new control value incl. bias*/
val = ucontrol->value.integer.value[0];
@@ -295,7 +299,7 @@ static int snd_us16x08_master_put(struct snd_kcontrol *kcontrol,
return err;
}
- elem->cached |= 1 << index;
+ set_bit(index, elem->cached);
elem->cache_val[index] = val;
return 1;
}
@@ -310,6 +314,7 @@ static int snd_us16x08_bus_put(struct snd_kcontrol *kcontrol,
val = ucontrol->value.integer.value[0];
+ guard(mutex)(&elem->head.mixer->lock);
/* prepare the message buffer from template */
switch (elem->head.id) {
case SND_US16X08_ID_BYPASS:
@@ -336,7 +341,7 @@ static int snd_us16x08_bus_put(struct snd_kcontrol *kcontrol,
return err;
}
- elem->cached |= 1;
+ set_bit(0, elem->cached);
elem->cache_val[0] = val;
return 1;
}
@@ -346,6 +351,7 @@ static int snd_us16x08_bus_get(struct snd_kcontrol *kcontrol,
{
struct usb_mixer_elem_info *elem = snd_kcontrol_chip(kcontrol);
+ guard(mutex)(&elem->head.mixer->lock);
switch (elem->head.id) {
case SND_US16X08_ID_BUSS_OUT:
ucontrol->value.integer.value[0] = elem->cache_val[0];
@@ -368,6 +374,7 @@ static int snd_us16x08_channel_get(struct snd_kcontrol *kcontrol,
struct usb_mixer_elem_info *elem = snd_kcontrol_chip(kcontrol);
int index = ucontrol->id.index;
+ guard(mutex)(&elem->head.mixer->lock);
ucontrol->value.integer.value[0] = elem->cache_val[index];
return 0;
@@ -382,6 +389,7 @@ static int snd_us16x08_channel_put(struct snd_kcontrol *kcontrol,
int val, err;
int index = ucontrol->id.index;
+ guard(mutex)(&elem->head.mixer->lock);
val = ucontrol->value.integer.value[0];
/* sanity check */
@@ -404,7 +412,7 @@ static int snd_us16x08_channel_put(struct snd_kcontrol *kcontrol,
return err;
}
- elem->cached |= 1 << index;
+ set_bit(index, elem->cached);
elem->cache_val[index] = val;
return 1;
}
@@ -428,6 +436,7 @@ static int snd_us16x08_comp_get(struct snd_kcontrol *kcontrol,
int index = ucontrol->id.index;
int val_idx = COMP_STORE_IDX(elem->head.id);
+ guard(mutex)(&elem->head.mixer->lock);
ucontrol->value.integer.value[0] = store->val[val_idx][index];
return 0;
@@ -445,6 +454,7 @@ static int snd_us16x08_comp_put(struct snd_kcontrol *kcontrol,
int threshold, ratio, attack, release, gain, switch_on;
int err;
+ guard(mutex)(&elem->head.mixer->lock);
val = ucontrol->value.integer.value[0];
/* sanity check */
@@ -508,7 +518,7 @@ static int snd_us16x08_comp_put(struct snd_kcontrol *kcontrol,
}
store->val[val_idx][index] = val;
- elem->cached |= 1 << index;
+ set_bit(index, elem->cached);
elem->cache_val[index] = val;
return 1;
}
@@ -521,6 +531,7 @@ static int snd_us16x08_eqswitch_get(struct snd_kcontrol *kcontrol,
struct snd_us16x08_eq_store *store = elem->private_data;
int index = ucontrol->id.index;
+ guard(mutex)(&elem->head.mixer->lock);
/* 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];
@@ -540,6 +551,7 @@ static int snd_us16x08_eqswitch_put(struct snd_kcontrol *kcontrol,
int val, err = 0;
int b_idx;
+ guard(mutex)(&elem->head.mixer->lock);
/* new control value incl. bias*/
val = ucontrol->value.integer.value[0] + SND_US16X08_KCBIAS(kcontrol);
@@ -567,7 +579,7 @@ static int snd_us16x08_eqswitch_put(struct snd_kcontrol *kcontrol,
return err;
}
- elem->cached |= 1 << index;
+ set_bit(index, elem->cached);
elem->cache_val[index] = val;
return 1;
}
@@ -582,6 +594,7 @@ static int snd_us16x08_eq_get(struct snd_kcontrol *kcontrol,
int b_idx = EQ_STORE_BAND_IDX(elem->head.id) - 1;
int p_idx = EQ_STORE_PARAM_IDX(elem->head.id);
+ guard(mutex)(&elem->head.mixer->lock);
val = store->val[b_idx][p_idx][index];
ucontrol->value.integer.value[0] = val;
@@ -601,6 +614,7 @@ static int snd_us16x08_eq_put(struct snd_kcontrol *kcontrol,
int b_idx = EQ_STORE_BAND_IDX(elem->head.id) - 1;
int p_idx = EQ_STORE_PARAM_IDX(elem->head.id);
+ guard(mutex)(&elem->head.mixer->lock);
val = ucontrol->value.integer.value[0];
/* sanity check */
@@ -631,7 +645,7 @@ static int snd_us16x08_eq_put(struct snd_kcontrol *kcontrol,
store->val[b_idx][p_idx][index] = val;
/* store new value in EQ band cache */
- elem->cached |= 1 << index;
+ set_bit(index, elem->cached);
elem->cache_val[index] = val;
return 1;
}
@@ -729,6 +743,7 @@ static int snd_us16x08_meter_get(struct snd_kcontrol *kcontrol,
struct snd_us16x08_meter_store *store = elem->private_data;
u8 meter_urb[64] = {0};
+ guard(mutex)(&elem->head.mixer->lock);
switch (kcontrol->private_value) {
case 0: {
char tmp[sizeof(mix_init_msg1)];
@@ -788,6 +803,7 @@ static int snd_us16x08_meter_put(struct snd_kcontrol *kcontrol,
struct snd_us16x08_meter_store *store = elem->private_data;
int val;
+ guard(mutex)(&elem->head.mixer->lock);
val = ucontrol->value.integer.value[0];
/* sanity check */
@@ -1118,7 +1134,8 @@ static int add_new_ctl(struct usb_mixer_interface *mixer,
strscpy(kctl->id.name, name, sizeof(kctl->id.name));
- err = snd_usb_mixer_add_control(&elem->head, kctl);
+ /* look_for_empty=false, as we want the fixed id.index as channel */
+ err = snd_usb_mixer_add_list(&elem->head, kctl, true, false);
if (err < 0)
return err;
@@ -1352,7 +1369,7 @@ int snd_us16x08_controls_create(struct usb_mixer_interface *mixer)
}
for (i = 0; i < 8; i++)
elem->cache_val[i] = i < 2 ? i : i + 2;
- elem->cached = 0xff;
+ bitmap_set(elem->cached, 0, 8);
/* create compressor mixer elements */
comp_store = snd_us16x08_create_comp_store();
@@ -1374,7 +1391,7 @@ int snd_us16x08_controls_create(struct usb_mixer_interface *mixer)
if (err < 0)
return err;
elem->cache_val[0] = master_controls[i].default_val;
- elem->cached = 1;
+ set_bit(0, elem->cached);
}
/* add channel controls */
@@ -1394,7 +1411,7 @@ int snd_us16x08_controls_create(struct usb_mixer_interface *mixer)
elem->cache_val[j] =
channel_controls[i].default_val;
}
- elem->cached = 0xffff;
+ bitmap_set(elem->cached, 0, SND_US16X08_MAX_CHANNELS);
}
/* create eq store */
diff --git a/sound/usb/mixer_us16x08.h b/sound/usb/mixer_us16x08.h
index 7b8583dd3b07..e98fe55265d1 100644
--- a/sound/usb/mixer_us16x08.h
+++ b/sound/usb/mixer_us16x08.h
@@ -60,7 +60,7 @@
#define SND_US16X08_ID_EQHIGHMIDLEVEL 0x03
#define SND_US16X08_ID_EQHIGHLEVEL 0x04
-/* EQ frequence IDs */
+/* EQ frequency IDs */
#define SND_US16X08_ID_EQLOWFREQ 0x11
#define SND_US16X08_ID_EQLOWMIDFREQ 0x12
#define SND_US16X08_ID_EQHIGHMIDFREQ 0x13
diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h
index dd02c6294edf..2da8cbe3e6de 100644
--- a/sound/usb/quirks-table.h
+++ b/sound/usb/quirks-table.h
@@ -1227,15 +1227,15 @@ YAMAHA_DEVICE(0x7010, "UB99"),
}
},
{
- /* only 44.1 kHz works at the moment */
+ /* S24_3LE; altsettings 1-4 match the device clock. */
USB_DEVICE(0x0582, 0x0120),
QUIRK_DRIVER_INFO {
/* .vendor_name = "Roland", */
- /* .product_name = "OCTO-CAPTURE", */
+ /* .product_name = "OCTA-CAPTURE", */
QUIRK_DATA_COMPOSITE {
{
QUIRK_DATA_AUDIOFORMAT(0) {
- .formats = SNDRV_PCM_FMTBIT_S32_LE,
+ .formats = SNDRV_PCM_FMTBIT_S24_3LE,
.channels = 10,
.iface = 0,
.altsetting = 1,
@@ -1250,8 +1250,56 @@ YAMAHA_DEVICE(0x7010, "UB99"),
}
},
{
+ QUIRK_DATA_AUDIOFORMAT(0) {
+ .formats = SNDRV_PCM_FMTBIT_S24_3LE,
+ .channels = 10,
+ .iface = 0,
+ .altsetting = 2,
+ .altset_idx = 2,
+ .endpoint = 0x05,
+ .ep_attr = 0x05,
+ .rates = SNDRV_PCM_RATE_48000,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ .nr_rates = 1,
+ .rate_table = (unsigned int[]) { 48000 }
+ }
+ },
+ {
+ QUIRK_DATA_AUDIOFORMAT(0) {
+ .formats = SNDRV_PCM_FMTBIT_S24_3LE,
+ .channels = 10,
+ .iface = 0,
+ .altsetting = 3,
+ .altset_idx = 3,
+ .endpoint = 0x05,
+ .ep_attr = 0x05,
+ .rates = SNDRV_PCM_RATE_96000,
+ .rate_min = 96000,
+ .rate_max = 96000,
+ .nr_rates = 1,
+ .rate_table = (unsigned int[]) { 96000 }
+ }
+ },
+ {
+ QUIRK_DATA_AUDIOFORMAT(0) {
+ .formats = SNDRV_PCM_FMTBIT_S24_3LE,
+ .channels = 4,
+ .iface = 0,
+ .altsetting = 4,
+ .altset_idx = 4,
+ .endpoint = 0x05,
+ .ep_attr = 0x05,
+ .rates = SNDRV_PCM_RATE_192000,
+ .rate_min = 192000,
+ .rate_max = 192000,
+ .nr_rates = 1,
+ .rate_table = (unsigned int[]) { 192000 }
+ }
+ },
+ {
QUIRK_DATA_AUDIOFORMAT(1) {
- .formats = SNDRV_PCM_FMTBIT_S32_LE,
+ .formats = SNDRV_PCM_FMTBIT_S24_3LE,
.channels = 12,
.iface = 1,
.altsetting = 1,
@@ -1266,9 +1314,57 @@ YAMAHA_DEVICE(0x7010, "UB99"),
}
},
{
+ QUIRK_DATA_AUDIOFORMAT(1) {
+ .formats = SNDRV_PCM_FMTBIT_S24_3LE,
+ .channels = 12,
+ .iface = 1,
+ .altsetting = 2,
+ .altset_idx = 2,
+ .endpoint = 0x85,
+ .ep_attr = 0x25,
+ .rates = SNDRV_PCM_RATE_48000,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ .nr_rates = 1,
+ .rate_table = (unsigned int[]) { 48000 }
+ }
+ },
+ {
+ QUIRK_DATA_AUDIOFORMAT(1) {
+ .formats = SNDRV_PCM_FMTBIT_S24_3LE,
+ .channels = 12,
+ .iface = 1,
+ .altsetting = 3,
+ .altset_idx = 3,
+ .endpoint = 0x85,
+ .ep_attr = 0x25,
+ .rates = SNDRV_PCM_RATE_96000,
+ .rate_min = 96000,
+ .rate_max = 96000,
+ .nr_rates = 1,
+ .rate_table = (unsigned int[]) { 96000 }
+ }
+ },
+ {
+ QUIRK_DATA_AUDIOFORMAT(1) {
+ .formats = SNDRV_PCM_FMTBIT_S24_3LE,
+ .channels = 4,
+ .iface = 1,
+ .altsetting = 4,
+ .altset_idx = 4,
+ .endpoint = 0x85,
+ .ep_attr = 0x25,
+ .rates = SNDRV_PCM_RATE_192000,
+ .rate_min = 192000,
+ .rate_max = 192000,
+ .nr_rates = 1,
+ .rate_table = (unsigned int[]) { 192000 }
+ }
+ },
+ {
QUIRK_DATA_MIDI_FIXED_ENDPOINT(2) {
- .out_cables = 0x0001,
- .in_cables = 0x0001
+ .out_cables = 0x0005,
+ .in_cables = 0x0003
}
},
{ QUIRK_DATA_IGNORE(3) },
@@ -1278,7 +1374,7 @@ YAMAHA_DEVICE(0x7010, "UB99"),
}
},
{
- /* only 44.1 kHz works at the moment */
+ /* S32_LE; altsettings 1-4 match the device clock. */
USB_DEVICE(0x0582, 0x012f),
QUIRK_DRIVER_INFO {
/* .vendor_name = "Roland", */
@@ -1301,6 +1397,54 @@ YAMAHA_DEVICE(0x7010, "UB99"),
}
},
{
+ QUIRK_DATA_AUDIOFORMAT(0) {
+ .formats = SNDRV_PCM_FMTBIT_S32_LE,
+ .channels = 4,
+ .iface = 0,
+ .altsetting = 2,
+ .altset_idx = 2,
+ .endpoint = 0x05,
+ .ep_attr = 0x05,
+ .rates = SNDRV_PCM_RATE_48000,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ .nr_rates = 1,
+ .rate_table = (unsigned int[]) { 48000 }
+ }
+ },
+ {
+ QUIRK_DATA_AUDIOFORMAT(0) {
+ .formats = SNDRV_PCM_FMTBIT_S32_LE,
+ .channels = 4,
+ .iface = 0,
+ .altsetting = 3,
+ .altset_idx = 3,
+ .endpoint = 0x05,
+ .ep_attr = 0x05,
+ .rates = SNDRV_PCM_RATE_96000,
+ .rate_min = 96000,
+ .rate_max = 96000,
+ .nr_rates = 1,
+ .rate_table = (unsigned int[]) { 96000 }
+ }
+ },
+ {
+ QUIRK_DATA_AUDIOFORMAT(0) {
+ .formats = SNDRV_PCM_FMTBIT_S32_LE,
+ .channels = 2,
+ .iface = 0,
+ .altsetting = 4,
+ .altset_idx = 4,
+ .endpoint = 0x05,
+ .ep_attr = 0x05,
+ .rates = SNDRV_PCM_RATE_192000,
+ .rate_min = 192000,
+ .rate_max = 192000,
+ .nr_rates = 1,
+ .rate_table = (unsigned int[]) { 192000 }
+ }
+ },
+ {
QUIRK_DATA_AUDIOFORMAT(1) {
.formats = SNDRV_PCM_FMTBIT_S32_LE,
.channels = 6,
@@ -1317,6 +1461,54 @@ YAMAHA_DEVICE(0x7010, "UB99"),
}
},
{
+ QUIRK_DATA_AUDIOFORMAT(1) {
+ .formats = SNDRV_PCM_FMTBIT_S32_LE,
+ .channels = 6,
+ .iface = 1,
+ .altsetting = 2,
+ .altset_idx = 2,
+ .endpoint = 0x85,
+ .ep_attr = 0x25,
+ .rates = SNDRV_PCM_RATE_48000,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ .nr_rates = 1,
+ .rate_table = (unsigned int[]) { 48000 }
+ }
+ },
+ {
+ QUIRK_DATA_AUDIOFORMAT(1) {
+ .formats = SNDRV_PCM_FMTBIT_S32_LE,
+ .channels = 6,
+ .iface = 1,
+ .altsetting = 3,
+ .altset_idx = 3,
+ .endpoint = 0x85,
+ .ep_attr = 0x25,
+ .rates = SNDRV_PCM_RATE_96000,
+ .rate_min = 96000,
+ .rate_max = 96000,
+ .nr_rates = 1,
+ .rate_table = (unsigned int[]) { 96000 }
+ }
+ },
+ {
+ QUIRK_DATA_AUDIOFORMAT(1) {
+ .formats = SNDRV_PCM_FMTBIT_S32_LE,
+ .channels = 2,
+ .iface = 1,
+ .altsetting = 4,
+ .altset_idx = 4,
+ .endpoint = 0x85,
+ .ep_attr = 0x25,
+ .rates = SNDRV_PCM_RATE_192000,
+ .rate_min = 192000,
+ .rate_max = 192000,
+ .nr_rates = 1,
+ .rate_table = (unsigned int[]) { 192000 }
+ }
+ },
+ {
QUIRK_DATA_MIDI_FIXED_ENDPOINT(2) {
.out_cables = 0x0001,
.in_cables = 0x0001
@@ -3541,6 +3733,53 @@ YAMAHA_DEVICE(0x7010, "UB99"),
},
{
/*
+ * Pioneer DJ DDJ-SZ
+ * 10 channels playback & 10 channels capture @ 44.1kHz S24LE
+ */
+ USB_DEVICE_VENDOR_SPEC(0x08e4, 0x0191),
+ QUIRK_DRIVER_INFO {
+ QUIRK_DATA_COMPOSITE {
+ {
+ QUIRK_DATA_AUDIOFORMAT(0) {
+ .formats = SNDRV_PCM_FMTBIT_S24_3LE,
+ .channels = 10,
+ .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 }
+ }
+ },
+ {
+ QUIRK_DATA_AUDIOFORMAT(0) {
+ .formats = SNDRV_PCM_FMTBIT_S24_3LE,
+ .channels = 10,
+ .iface = 0,
+ .altsetting = 1,
+ .altset_idx = 1,
+ .endpoint = 0x82,
+ .ep_idx = 1,
+ .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 }
+ }
+ },
+ QUIRK_COMPOSITE_END
+ }
+ }
+},
+{
+ /*
* Pioneer DJ DJM-750MK2
* 10 channels playback & 12 channels capture @ 48kHz S24LE
*/
diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c
index a54fabce7920..4fadb1fdcdea 100644
--- a/sound/usb/quirks.c
+++ b/sound/usb/quirks.c
@@ -3,6 +3,7 @@
*/
#include <linux/cleanup.h>
+#include <linux/delay.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/slab.h>
@@ -871,6 +872,49 @@ static int snd_usb_cm1a_boot_quirk(struct usb_device *dev)
}
/*
+ * The Xiaomi audio connector ignores UAC2 volume changes after reconnecting
+ * until the device and configuration descriptors have been read again.
+ */
+#define XIAOMI_AUDIO_MAX_CONFIG_SIZE 1024
+
+static void snd_usb_xiaomi_boot_quirk(struct usb_device *dev)
+{
+ u8 *buf __free(kfree) = kmalloc(XIAOMI_AUDIO_MAX_CONFIG_SIZE, GFP_KERNEL);
+ struct usb_config_descriptor *config;
+ unsigned int length;
+ int ret;
+
+ if (!buf)
+ return;
+
+ ret = usb_get_descriptor(dev, USB_DT_DEVICE, 0, buf,
+ USB_DT_DEVICE_SIZE);
+ if (ret != USB_DT_DEVICE_SIZE) {
+ dev_warn(&dev->dev, "device descriptor re-read failed: %d\n", ret);
+ return;
+ }
+
+ ret = usb_get_descriptor(dev, USB_DT_CONFIG, 0, buf,
+ USB_DT_CONFIG_SIZE);
+ if (ret != USB_DT_CONFIG_SIZE) {
+ dev_warn(&dev->dev, "configuration header re-read failed: %d\n", ret);
+ return;
+ }
+
+ config = (struct usb_config_descriptor *)buf;
+ length = le16_to_cpu(config->wTotalLength);
+ if (length < USB_DT_CONFIG_SIZE ||
+ length > XIAOMI_AUDIO_MAX_CONFIG_SIZE) {
+ dev_warn(&dev->dev, "unexpected configuration length: %u\n", length);
+ return;
+ }
+
+ ret = usb_get_descriptor(dev, USB_DT_CONFIG, 0, buf, length);
+ if (ret != (int)length)
+ dev_warn(&dev->dev, "configuration descriptor re-read failed: %d\n", ret);
+}
+
+/*
* 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.
@@ -1606,22 +1650,60 @@ static int s1810c_skip_setting_quirk(struct snd_usb_audio *chip,
return 0;
}
+static int hp_elite_x3_lap_dock_skip_setting_quirk(struct snd_usb_audio *chip,
+ int iface, int altno)
+{
+ /*
+ * 1:1 - Capture, S16_LE, 8000/32000/44100/48000Hz
+ * 2:1 - Playback, S16_LE, 48000Hz
+ * 2:2 - Playback, S24_3LE, 48000Hz
+ *
+ * 2:2 is broken because:
+ * - it doesn't accept SET_CUR(SAMPLE_RATE). QUIRK_FLAG_FIXED_RATE works
+ * around it, however...
+ * - it constantly produces severe harmonic distortion once the capture
+ * stream is also opened. The interface 2 must be closed and reopened
+ * to make it recover. IOW, simply closing the capture stream makes no
+ * difference.
+ *
+ * Considering that S24_3LE offers no additional benefit on small
+ * speakers compared to S16_LE, and 2:1 is always usable as an
+ * alternative, skip 2:2 to get rid of the trouble.
+ *
+ * Setting chip->setup to any non-default value disables the fixup and
+ * reenables 2:2 (in this case QUIRK_FLAG_FIXED_RATE is required).
+ */
+ if (!chip->setup && iface == 2 && altno == 2) {
+ usb_audio_info(chip,
+ "%d:%d: skipping broken altsetting on HP Elite x3 Lap Dock\n",
+ iface, altno);
+ return 1;
+ }
+
+ return 0;
+}
+
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);
+ switch (chip->usb_id) {
+ /* HP Elite x3 Lap Dock: skip broken altsets */
+ case USB_ID(0x03f0, 0x0c56):
+ return hp_elite_x3_lap_dock_skip_setting_quirk(chip, iface, altno);
/* quattro usb: skip altsets incompatible with device_setup */
- if (chip->usb_id == USB_ID(0x0763, 0x2001))
+ case USB_ID(0x0763, 0x2001):
return quattro_skip_setting_quirk(chip, iface, altno);
+ /* audiophile usb: skip altsets incompatible with device_setup */
+ case USB_ID(0x0763, 0x2003):
+ return audiophile_skip_setting_quirk(chip, iface, altno);
/* fasttrackpro usb: skip altsets incompatible with device_setup */
- if (chip->usb_id == USB_ID(0x0763, 0x2012))
+ case USB_ID(0x0763, 0x2012):
return fasttrackpro_skip_setting_quirk(chip, iface, altno);
/* presonus studio 1810c: skip altsets incompatible with device_setup */
- if (chip->usb_id == USB_ID(0x194f, 0x010c))
+ case USB_ID(0x194f, 0x010c):
return s1810c_skip_setting_quirk(chip, iface, altno);
+ }
return 0;
}
@@ -1702,6 +1784,9 @@ int snd_usb_apply_boot_quirk_once(struct usb_device *dev,
switch (id) {
case USB_ID(0x07fd, 0x0008): /* MOTU M Series, 1st hardware version */
return snd_usb_motu_m_series_boot_quirk(dev);
+ case USB_ID(0x2717, 0xd005): /* Xiaomi audio connector */
+ snd_usb_xiaomi_boot_quirk(dev);
+ return 0;
}
/* Behringer devices may need explicit device descriptor read at boot */
@@ -1791,6 +1876,62 @@ static void set_format_emu_quirk(struct snd_usb_substream *subs,
subs->pkt_offset_adj = (emu_samplerate_id >= EMU_QUIRK_SR_176400HZ) ? 4 : 0;
}
+/*
+ * The DDJ-SZ needs a vendor "arm" sequence before its capture path
+ * produces real audio; without it capture runs with no USB or ALSA error
+ * but delivers a hard zero on every channel. The sequence is replicated
+ * byte-for-byte from a USB capture of the Windows driver: each write is
+ * followed by a status read whose content is a fixed value regardless of
+ * what was written, but the read is replicated too, since it is unclear
+ * whether the device requires it to process the preceding write.
+ *
+ * This runs from snd_usb_set_format_quirk(), i.e. on every format setup
+ * rather than once per device. Re-arming is harmless in practice and
+ * keeps the device armed if it is reset behind our back.
+ */
+static void ddj_sz_arm_quirk(struct usb_device *dev)
+{
+ static const struct {
+ u16 value;
+ u16 index;
+ u8 read_len;
+ } cmds[] = {
+ { 0x0100, 0x8002, 6 },
+ { 0x0200, 0x8002, 6 },
+ { 0x0303, 0x8002, 6 },
+ { 0x0403, 0x8002, 6 },
+ { 0x050a, 0x8002, 6 },
+ { 0x0000, 0x8003, 2 },
+ };
+ u8 buf[6];
+ unsigned int i;
+ int err;
+
+ for (i = 0; i < ARRAY_SIZE(cmds); i++) {
+ err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), 3,
+ USB_DIR_OUT | USB_TYPE_VENDOR |
+ USB_RECIP_DEVICE,
+ cmds[i].value, cmds[i].index, NULL, 0);
+ if (err < 0)
+ goto err_out;
+
+ err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), 0,
+ USB_DIR_IN | USB_TYPE_VENDOR |
+ USB_RECIP_DEVICE,
+ 0x0000, cmds[i].index, buf,
+ cmds[i].read_len);
+ if (err < 0)
+ goto err_out;
+ }
+
+ return;
+
+err_out:
+ dev_warn(&dev->dev,
+ "DDJ-SZ: arm sequence step %u failed (%d), capture may be silent\n",
+ i, err);
+}
+
static int pioneer_djm_set_format_quirk(struct snd_usb_substream *subs,
u16 windex)
{
@@ -1880,6 +2021,75 @@ static int rme_digiface_set_format_quirk(struct snd_usb_substream *subs)
return 0;
}
+#define ROLAND_CAPTURE_RATE_REQUEST 3
+#define ROLAND_CAPTURE_RATE_READ_VALUE 0x0001
+#define ROLAND_CAPTURE_RATE_WRITE_VALUE 0x0008
+#define ROLAND_CAPTURE_RATE_WRITE_PREFIX 0x40
+#define ROLAND_CAPTURE_RATE_RETRIES 40
+#define ROLAND_CAPTURE_RATE_POLL_MS 25
+
+/*
+ * OCTA-CAPTURE and QUAD-CAPTURE use the same vendor request for their
+ * hardware clock. A read returns the 24-bit little-endian rate followed by
+ * a transition-status byte. A write carries 0x40 followed by the rate.
+ */
+static int roland_capture_read_rate(struct usb_device *dev, u32 *rate)
+{
+ u8 data[4];
+ int err;
+
+ err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0),
+ ROLAND_CAPTURE_RATE_REQUEST,
+ USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ ROLAND_CAPTURE_RATE_READ_VALUE, 0,
+ data, sizeof(data));
+ if (err != sizeof(data))
+ return err < 0 ? err : -EIO;
+
+ *rate = combine_triple(data);
+ return 0;
+}
+
+static void roland_capture_set_rate(struct snd_usb_substream *subs)
+{
+ struct snd_usb_audio *chip = subs->stream->chip;
+ struct usb_device *dev = chip->dev;
+ u32 rate = subs->data_endpoint->cur_rate;
+ u32 current_rate;
+ u8 data[4];
+ int err;
+ int i;
+
+ /* Serialize playback and capture endpoint starts during a clock change. */
+ guard(mutex)(&chip->mutex);
+ err = roland_capture_read_rate(dev, &current_rate);
+ if (!err && current_rate == rate)
+ return;
+
+ data[0] = ROLAND_CAPTURE_RATE_WRITE_PREFIX;
+ data[1] = rate;
+ data[2] = rate >> 8;
+ data[3] = rate >> 16;
+ err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0),
+ ROLAND_CAPTURE_RATE_REQUEST,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ ROLAND_CAPTURE_RATE_WRITE_VALUE, 0,
+ data, sizeof(data));
+ if (err != sizeof(data)) {
+ usb_audio_warn(chip, "cannot set Roland sample rate to %u Hz: %d\n",
+ rate, err < 0 ? err : -EIO);
+ return;
+ }
+
+ for (i = 0; i < ROLAND_CAPTURE_RATE_RETRIES; i++) {
+ err = roland_capture_read_rate(dev, &current_rate);
+ if (!err && current_rate == rate)
+ return;
+ msleep(ROLAND_CAPTURE_RATE_POLL_MS);
+ }
+ usb_audio_warn(chip, "Roland sample rate did not reach %u Hz\n", rate);
+}
+
void snd_usb_set_format_quirk(struct snd_usb_substream *subs,
const struct audioformat *fmt)
{
@@ -1903,6 +2113,10 @@ void snd_usb_set_format_quirk(struct snd_usb_substream *subs,
case USB_ID(0x08e4, 0x0163): /* Pioneer DJM-850 */
pioneer_djm_set_format_quirk(subs, 0x0086);
break;
+ case USB_ID(0x08e4, 0x0191): /* Pioneer DDJ-SZ */
+ ddj_sz_arm_quirk(subs->dev);
+ pioneer_djm_set_format_quirk(subs, 0x0082);
+ break;
case USB_ID(0x0dba, 0x5000):
mbox3_set_format_quirk(subs, fmt); /* Digidesign Mbox 3 */
break;
@@ -1910,6 +2124,10 @@ void snd_usb_set_format_quirk(struct snd_usb_substream *subs,
case USB_ID(0x2a39, 0x3fa0): /* RME Digiface USB (alternate) */
rme_digiface_set_format_quirk(subs);
break;
+ case USB_ID(0x0582, 0x0120): /* Roland OCTA-CAPTURE */
+ case USB_ID(0x0582, 0x012f): /* Roland QUAD-CAPTURE */
+ roland_capture_set_rate(subs);
+ break;
}
}
@@ -1980,6 +2198,16 @@ void snd_usb_endpoint_start_quirk(struct snd_usb_endpoint *ep)
ep->chip->usb_id == USB_ID(0x1852, 0x5034)) && /* T+A Dac8 */
ep->syncmaxsize == 4)
ep->tenor_fb_quirk = 1;
+
+ /*
+ * Mayflower ARC AMP DAC (MAG Technology USB front-end): the
+ * feedback can get stuck at nominal+1 sample per frame, causing
+ * pops every 1-2 sec until stream restart. Snap implausible
+ * feedback to nominal.
+ */
+ if (ep->chip->usb_id == USB_ID(0x0493, 0x4170) &&
+ ep->type == SND_USB_ENDPOINT_TYPE_SYNC)
+ ep->snap_fb_quirk = 1;
}
/* quirk applied after snd_usb_ctl_msg(); not applied during boot quirks */
@@ -2114,6 +2342,21 @@ void snd_usb_audioformat_attributes_quirk(struct snd_usb_audio *chip,
int stream)
{
switch (chip->usb_id) {
+ case USB_ID(0x0499, 0x5500): /* Yamaha 01V96i */
+ /*
+ * The 01V96i declares its audio streaming interfaces as
+ * vendor-specific (class 0xff) and provides no class-specific
+ * endpoint descriptor. parse_uac_endpoint_attributes() therefore
+ * returns no attributes, and the driver never issues the SET_CUR
+ * sampling-frequency request the hardware needs to engage its USB
+ * stream: playback and capture PCMs report RUNNING, but the desk
+ * passes no audio.
+ *
+ * Force the attribute on so the stream is initialised the same way
+ * the Windows driver does it.
+ */
+ fp->attributes |= UAC_EP_CS_ATTR_SAMPLE_RATE;
+ break;
case USB_ID(0x0a92, 0x0053): /* AudioTrak Optoplay */
/* Optoplay sets the sample rate attribute although
* it seems not supporting it in fact.
@@ -2148,6 +2391,14 @@ void snd_usb_audioformat_attributes_quirk(struct snd_usb_audio *chip,
*/
fp->attributes &= ~UAC_EP_CS_ATTR_FILL_MAX;
break;
+ case USB_ID(0x0a12, 0x1244): /* Shanling UP4 */
+ /*
+ * MaxPacketsOnly is set, but the device expects nominal-size
+ * packets. Filling 48 frames per packet at 44.1 kHz results
+ * in silence. No effect at 48 kHz, where both sizes match.
+ */
+ fp->attributes &= ~UAC_EP_CS_ATTR_FILL_MAX;
+ break;
case USB_ID(0x1224, 0x2a25): /* Jieli Technology USB PHY 2.0 */
/* mic works only when ep packet size is set to wMaxPacketSize */
fp->attributes |= UAC_EP_CS_ATTR_FILL_MAX;
@@ -2238,6 +2489,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = {
QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY),
DEVICE_FLG(0x0124, 0x0c21, /* Generic USB Headphone */
QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY),
+ DEVICE_FLG(0x03f0, 0x0c56, /* HP Elite x3 Lap Dock */
+ QUIRK_FLAG_FIXED_RATE),
DEVICE_FLG(0x03f0, 0x654a, /* HP 320 FHD Webcam */
QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16),
DEVICE_FLG(0x041e, 0x3000, /* Creative SB Extigy */
@@ -2251,6 +2504,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = {
QUIRK_FLAG_DISABLE_AUTOSUSPEND),
DEVICE_FLG(0x045e, 0x070f, /* MS LifeChat LX-3000 Headset */
QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE),
+ DEVICE_FLG(0x046d, 0x0802, /* Logitech Webcam C200 */
+ QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIC_RES_384),
DEVICE_FLG(0x046d, 0x0807, /* Logitech Webcam C500 */
QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIC_RES_384),
DEVICE_FLG(0x046d, 0x0808, /* Logitech Webcam C600 */
@@ -2271,6 +2526,10 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = {
QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_CTL_MSG_DELAY_1M),
DEVICE_FLG(0x046d, 0x08ca, /* Logitech Quickcam Fusion */
QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIC_RES_384),
+ DEVICE_FLG(0x046d, 0x08e5, /* Logitech HD Pro Webcam c920 */
+ QUIRK_FLAG_GET_SAMPLE_RATE),
+ DEVICE_FLG(0x046d, 0x094c, /* Logitech Brio 100 */
+ QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIC_RES_384),
DEVICE_FLG(0x046d, 0x0991, /* Logitech QuickCam Pro */
QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_IGNORE_CTL_ERROR |
QUIRK_FLAG_MIC_RES_384),
@@ -2323,6 +2582,9 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = {
DEVICE_FLG(0x0644, 0x8044, /* Esoteric D-05X */
QUIRK_FLAG_ITF_USB_DSD_DAC | QUIRK_FLAG_CTL_MSG_DELAY |
QUIRK_FLAG_IFACE_DELAY | QUIRK_FLAG_FORCE_IFACE_RESET),
+ DEVICE_FLG(0x0644, 0x8047, /* TASCAM US-16x08 */
+ QUIRK_FLAG_CTL_MSG_DELAY | QUIRK_FLAG_IFACE_DELAY |
+ QUIRK_FLAG_FORCE_IFACE_RESET),
DEVICE_FLG(0x0644, 0x804a, /* TEAC UD-301 */
QUIRK_FLAG_ITF_USB_DSD_DAC | QUIRK_FLAG_CTL_MSG_DELAY |
QUIRK_FLAG_IFACE_DELAY),
@@ -2404,6 +2666,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = {
QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE),
DEVICE_FLG(0x1101, 0x0003, /* Audioengine D1 */
QUIRK_FLAG_GET_SAMPLE_RATE),
+ DEVICE_FLG(0x1237, 0x17b8, /* rongyuan GS20 */
+ QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY),
DEVICE_FLG(0x12d1, 0x3a07, /* HUAWEI USB-C HEADSET */
QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE | QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE |
QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY),
@@ -2556,8 +2820,12 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = {
QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE),
DEVICE_FLG(0x3443, 0x930d, /* NexiGo N930W 60fps Webcam */
QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16),
+ DEVICE_FLG(0x35d8, 0x011d, /* MOONDROP DAWN PRO2 */
+ QUIRK_FLAG_DSD_RAW),
DEVICE_FLG(0x36f9, 0xc009, /* XIBERIA K03S */
QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY),
+ DEVICE_FLG(0x3703, 0x2000, /* NUX NAI-24 */
+ QUIRK_FLAG_SWAP_RATES),
DEVICE_FLG(0x3c20, 0x3d21, /* AB13X USB Audio */
QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY),
DEVICE_FLG(0x413c, 0xa506, /* Dell AE515 sound bar */
@@ -2685,6 +2953,7 @@ static const char *const snd_usb_audio_quirk_flag_names[] = {
QUIRK_STRING_ENTRY(MIXER_GET_CUR_OK),
QUIRK_STRING_ENTRY(PLAYBACK_URB_FIXUP),
QUIRK_STRING_ENTRY(ALWAYS_SET_RATE),
+ QUIRK_STRING_ENTRY(SWAP_RATES),
NULL
};
diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h
index 24436473931d..c6d59218d35f 100644
--- a/sound/usb/usbaudio.h
+++ b/sound/usb/usbaudio.h
@@ -258,6 +258,10 @@ extern bool snd_usb_skip_validation;
* Issue SET_CUR for the sample rate even when the clock already reports the
* requested rate. A device advertising a single rate is otherwise never sent
* the request at all, and some require it before streaming will start.
+ * QUIRK_FLAG_SWAP_RATES
+ * The device applies a requested sample rate to the other base-rate family
+ * (44100 <-> 48000, 88200 <-> 96000, 176400 <-> 192000). Send the partner
+ * rate in SET_CUR so that the device runs at the requested rate.
*/
enum {
@@ -294,6 +298,7 @@ enum {
QUIRK_TYPE_MIXER_GET_CUR_OK = 30,
QUIRK_TYPE_PLAYBACK_URB_FIXUP = 31,
QUIRK_TYPE_ALWAYS_SET_RATE = 32,
+ QUIRK_TYPE_SWAP_RATES = 33,
/* Please also edit snd_usb_audio_quirk_flag_names and alsa-configuration.rst */
};
@@ -332,5 +337,6 @@ enum {
#define QUIRK_FLAG_MIXER_GET_CUR_OK QUIRK_FLAG(MIXER_GET_CUR_OK)
#define QUIRK_FLAG_PLAYBACK_URB_FIXUP QUIRK_FLAG(PLAYBACK_URB_FIXUP)
#define QUIRK_FLAG_ALWAYS_SET_RATE QUIRK_FLAG(ALWAYS_SET_RATE)
+#define QUIRK_FLAG_SWAP_RATES QUIRK_FLAG(SWAP_RATES)
#endif /* __USBAUDIO_H */
diff --git a/sound/virtio/virtio_pcm.h b/sound/virtio/virtio_pcm.h
index 368dafa5df3f..8837dfc12828 100644
--- a/sound/virtio/virtio_pcm.h
+++ b/sound/virtio/virtio_pcm.h
@@ -118,6 +118,8 @@ int virtsnd_pcm_msg_alloc(struct virtio_pcm_substream *vss,
void virtsnd_pcm_msg_free(struct virtio_pcm_substream *vss);
+void virtsnd_pcm_msg_reset(struct virtio_pcm_substream *vss);
+
int virtsnd_pcm_msg_send(struct virtio_pcm_substream *vss, unsigned long offset,
unsigned long bytes);
diff --git a/sound/virtio/virtio_pcm_msg.c b/sound/virtio/virtio_pcm_msg.c
index a5c4e7027717..72103f606716 100644
--- a/sound/virtio/virtio_pcm_msg.c
+++ b/sound/virtio/virtio_pcm_msg.c
@@ -183,6 +183,25 @@ void virtsnd_pcm_msg_free(struct virtio_pcm_substream *vss)
}
/**
+ * virtsnd_pcm_msg_reset() - Reset the fill level of all I/O messages.
+ * @vss: VirtIO PCM substream.
+ *
+ * Messages that were only partially filled by the application when the
+ * substream was stopped are never sent to the device, so their length must be
+ * cleared before the substream is started again.
+ *
+ * Context: Any context. Takes and releases the VirtIO substream spinlock.
+ */
+void virtsnd_pcm_msg_reset(struct virtio_pcm_substream *vss)
+{
+ unsigned int i;
+
+ guard(spinlock_irqsave)(&vss->lock);
+ for (i = 0; vss->msgs && i < vss->nmsgs; ++i)
+ vss->msgs[i]->length = 0;
+}
+
+/**
* virtsnd_pcm_msg_send() - Send asynchronous I/O messages.
* @vss: VirtIO PCM substream.
* @offset: starting position that has been updated
diff --git a/sound/virtio/virtio_pcm_ops.c b/sound/virtio/virtio_pcm_ops.c
index 1105e7ff3523..1091100a4c98 100644
--- a/sound/virtio/virtio_pcm_ops.c
+++ b/sound/virtio/virtio_pcm_ops.c
@@ -299,6 +299,7 @@ static int virtsnd_pcm_prepare(struct snd_pcm_substream *substream)
vss->xfer_xrun = false;
vss->suspended = false;
vss->msg_count = 0;
+ virtsnd_pcm_msg_reset(vss);
memset(&vss->pcm_indirect, 0, sizeof(vss->pcm_indirect));
vss->pcm_indirect.sw_buffer_size =
diff --git a/sound/x86/intel_hdmi_audio.c b/sound/x86/intel_hdmi_audio.c
index 91dfcf47e979..542fc35ec9b7 100644
--- a/sound/x86/intel_hdmi_audio.c
+++ b/sound/x86/intel_hdmi_audio.c
@@ -475,7 +475,8 @@ static void had_build_channel_allocation_map(struct snd_intelhad *intelhaddata)
kfree(intelhaddata->chmap->chmap);
intelhaddata->chmap->chmap = NULL;
- chmap = kzalloc_obj(*chmap);
+ /* info->chmap must be zero-terminated; allocate a spare terminator entry */
+ chmap = kzalloc_objs(*chmap, 2);
if (!chmap)
return;
@@ -575,6 +576,12 @@ static int had_register_chmap_ctls(struct snd_intelhad *intelhaddata,
intelhaddata->chmap->kctl->info = had_chmap_ctl_info;
intelhaddata->chmap->kctl->get = had_chmap_ctl_get;
intelhaddata->chmap->chmap = NULL;
+
+ /* drop the inherited TLV read: it walks chmap without our mutex */
+ intelhaddata->chmap->kctl->tlv.c = NULL;
+ intelhaddata->chmap->kctl->vd[0].access &=
+ ~(SNDRV_CTL_ELEM_ACCESS_TLV_READ | SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK);
+
return 0;
}
@@ -1634,9 +1641,9 @@ static void hdmi_lpe_audio_free(struct snd_card *card)
* 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)
+static int hdmi_lpe_audio_probe(struct platform_device *pdev)
{
- struct snd_card *card;
+ struct snd_card *card __free(snd_card_free) = NULL;
struct snd_intelhad_card *card_ctx;
struct snd_intelhad *ctx;
struct snd_pcm *pcm;
@@ -1797,14 +1804,10 @@ static int __hdmi_lpe_audio_probe(struct platform_device *pdev)
schedule_work(&ctx->hdmi_audio_wq);
}
+ card = NULL; /* probe succeeded, don't release as error */
return 0;
}
-static int hdmi_lpe_audio_probe(struct platform_device *pdev)
-{
- return snd_card_free_on_error(&pdev->dev, __hdmi_lpe_audio_probe(pdev));
-}
-
static const struct dev_pm_ops hdmi_lpe_audio_pm = {
SYSTEM_SLEEP_PM_OPS(hdmi_lpe_audio_suspend, hdmi_lpe_audio_resume)
};
diff --git a/tools/testing/selftests/alsa/.gitignore b/tools/testing/selftests/alsa/.gitignore
index 3dd8e1176b89..7b0e1e9ebf1b 100644
--- a/tools/testing/selftests/alsa/.gitignore
+++ b/tools/testing/selftests/alsa/.gitignore
@@ -1,3 +1,4 @@
+aloop-test
global-timer
mixer-test
pcm-test
diff --git a/tools/testing/selftests/alsa/Makefile b/tools/testing/selftests/alsa/Makefile
index 8dab90ad22bb..afd64a679dc1 100644
--- a/tools/testing/selftests/alsa/Makefile
+++ b/tools/testing/selftests/alsa/Makefile
@@ -16,7 +16,7 @@ LDLIBS+=-lpthread
OVERRIDE_TARGETS = 1
-TEST_GEN_PROGS := mixer-test pcm-test test-pcmtest-driver utimer-test
+TEST_GEN_PROGS := aloop-test mixer-test pcm-test test-pcmtest-driver utimer-test
TEST_GEN_PROGS_EXTENDED := libatest.so global-timer
diff --git a/tools/testing/selftests/alsa/aloop-test.c b/tools/testing/selftests/alsa/aloop-test.c
new file mode 100644
index 000000000000..a58b9fdbdc17
--- /dev/null
+++ b/tools/testing/selftests/alsa/aloop-test.c
@@ -0,0 +1,345 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Tests for the hw constraints between the two ends of an snd-aloop cable,
+ * with and without the "PCM Notify" control, and for the "PCM Slave"
+ * controls that report the playback side's parameters.
+ *
+ * Needs snd-aloop loaded with the default card id "Loopback". The tests use
+ * the cable between hw:Loopback,0,0 (playback) and hw:Loopback,1,0 (capture).
+ */
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <alsa/asoundlib.h>
+#include "kselftest_harness.h"
+
+#define FRAMES 1024
+#define MAX_CHANNELS 4
+
+struct stream_params {
+ snd_pcm_access_t access;
+ snd_pcm_format_t format;
+ unsigned int channels;
+ unsigned int rate;
+};
+
+/* The playback params differ from the capture params in every field. */
+static const struct stream_params capture_params = {
+ SND_PCM_ACCESS_RW_INTERLEAVED, SND_PCM_FORMAT_S16_LE, 2, 44100
+};
+
+static const struct stream_params playback_params = {
+ SND_PCM_ACCESS_RW_NONINTERLEAVED, SND_PCM_FORMAT_S32_LE, 4, 96000
+};
+
+struct probe_result {
+ unsigned int rate_min, rate_max;
+ unsigned int channels_min, channels_max;
+ bool format_ok;
+};
+
+/* Value events seen on the cable's controls */
+enum {
+ EV_ACTIVE = 1 << 0,
+ EV_FORMAT = 1 << 1,
+ EV_RATE = 1 << 2,
+ EV_CHANNELS = 1 << 3,
+ EV_ACCESS = 1 << 4,
+};
+
+FIXTURE(aloop) {
+ char play_name[32];
+ char capt_name[32];
+ snd_ctl_t *ctl;
+ bool saved_notify;
+ bool notify_saved;
+};
+
+/* The cable's controls are on the capture side device. */
+static void cable_ctl_id(snd_ctl_elem_value_t *value, const char *name)
+{
+ snd_ctl_elem_value_set_interface(value, SND_CTL_ELEM_IFACE_PCM);
+ snd_ctl_elem_value_set_name(value, name);
+ snd_ctl_elem_value_set_device(value, 1);
+ snd_ctl_elem_value_set_subdevice(value, 0);
+}
+
+static long cable_ctl_get(snd_ctl_t *ctl, const char *name)
+{
+ snd_ctl_elem_value_t *value;
+
+ snd_ctl_elem_value_alloca(&value);
+ cable_ctl_id(value, name);
+ if (snd_ctl_elem_read(ctl, value) < 0)
+ return -1;
+ if (!strcmp(name, "PCM Slave Access Mode"))
+ return snd_ctl_elem_value_get_enumerated(value, 0);
+ return snd_ctl_elem_value_get_integer(value, 0);
+}
+
+static int set_notify(snd_ctl_t *ctl, bool on)
+{
+ snd_ctl_elem_value_t *value;
+
+ snd_ctl_elem_value_alloca(&value);
+ cable_ctl_id(value, "PCM Notify");
+ snd_ctl_elem_value_set_boolean(value, 0, on);
+ return snd_ctl_elem_write(ctl, value);
+}
+
+/* Read all pending control events and return the EV_* bits for the cable's controls. */
+static unsigned int read_events(snd_ctl_t *ctl)
+{
+ static const struct {
+ const char *name;
+ unsigned int bit;
+ } names[] = {
+ { "PCM Slave Active", EV_ACTIVE },
+ { "PCM Slave Format", EV_FORMAT },
+ { "PCM Slave Rate", EV_RATE },
+ { "PCM Slave Channels", EV_CHANNELS },
+ { "PCM Slave Access Mode", EV_ACCESS },
+ };
+ snd_ctl_event_t *event;
+ unsigned int seen = 0;
+ int i;
+
+ snd_ctl_event_alloca(&event);
+ while (snd_ctl_read(ctl, event) > 0) {
+ if (snd_ctl_event_get_type(event) != SND_CTL_EVENT_ELEM ||
+ !(snd_ctl_event_elem_get_mask(event) & SND_CTL_EVENT_MASK_VALUE) ||
+ snd_ctl_event_elem_get_device(event) != 1 ||
+ snd_ctl_event_elem_get_subdevice(event) != 0)
+ continue;
+ for (i = 0; i < ARRAY_SIZE(names); i++)
+ if (!strcmp(snd_ctl_event_elem_get_name(event), names[i].name))
+ seen |= names[i].bit;
+ }
+ return seen;
+}
+
+/* Open and configure a stream. snd_pcm_hw_params() also prepares it. */
+static int open_pcm(snd_pcm_t **pcm, const char *name, snd_pcm_stream_t stream,
+ const struct stream_params *p)
+{
+ unsigned int buffer_time = 100000;
+ snd_pcm_hw_params_t *hw;
+ int err;
+
+ snd_pcm_hw_params_alloca(&hw);
+ err = snd_pcm_open(pcm, name, stream, 0);
+ if (err < 0)
+ return err;
+ err = snd_pcm_hw_params_any(*pcm, hw);
+ if (err >= 0)
+ err = snd_pcm_hw_params_set_access(*pcm, hw, p->access);
+ if (err >= 0)
+ err = snd_pcm_hw_params_set_format(*pcm, hw, p->format);
+ if (err >= 0)
+ err = snd_pcm_hw_params_set_channels(*pcm, hw, p->channels);
+ if (err >= 0)
+ err = snd_pcm_hw_params_set_rate(*pcm, hw, p->rate, 0);
+ if (err >= 0)
+ err = snd_pcm_hw_params_set_buffer_time_near(*pcm, hw, &buffer_time, NULL);
+ if (err >= 0)
+ err = snd_pcm_hw_params(*pcm, hw);
+ if (err < 0) {
+ snd_pcm_close(*pcm);
+ *pcm = NULL;
+ }
+ return err;
+}
+
+/* What a client probing the device sees, and whether it may use the given format */
+static int probe_pcm(const char *name, snd_pcm_stream_t stream, snd_pcm_format_t format,
+ struct probe_result *res)
+{
+ snd_pcm_hw_params_t *hw;
+ snd_pcm_t *pcm;
+ int err;
+
+ snd_pcm_hw_params_alloca(&hw);
+ err = snd_pcm_open(&pcm, name, stream, 0);
+ if (err < 0)
+ return err;
+ err = snd_pcm_hw_params_any(pcm, hw);
+ if (err >= 0)
+ err = snd_pcm_hw_params_get_rate_min(hw, &res->rate_min, NULL);
+ if (err >= 0)
+ err = snd_pcm_hw_params_get_rate_max(hw, &res->rate_max, NULL);
+ if (err >= 0)
+ err = snd_pcm_hw_params_get_channels_min(hw, &res->channels_min);
+ if (err >= 0)
+ err = snd_pcm_hw_params_get_channels_max(hw, &res->channels_max);
+ if (err >= 0)
+ res->format_ok = !snd_pcm_hw_params_test_format(pcm, hw, format);
+ snd_pcm_close(pcm);
+ return err;
+}
+
+static int start_playback(snd_pcm_t *pcm, const struct stream_params *p)
+{
+ static char silence[FRAMES * MAX_CHANNELS * 4];
+ void *bufs[MAX_CHANNELS];
+ snd_pcm_sframes_t written;
+ unsigned int i;
+
+ if (p->access == SND_PCM_ACCESS_RW_NONINTERLEAVED) {
+ for (i = 0; i < p->channels; i++)
+ bufs[i] = silence + i * FRAMES * 4;
+ written = snd_pcm_writen(pcm, bufs, FRAMES);
+ } else {
+ written = snd_pcm_writei(pcm, silence, FRAMES);
+ }
+ if (written < 0)
+ return written;
+ if (snd_pcm_state(pcm) == SND_PCM_STATE_PREPARED)
+ return snd_pcm_start(pcm);
+ return 0;
+}
+
+FIXTURE_SETUP(aloop) {
+ char ctl_name[32];
+ snd_pcm_t *pcm;
+ int card, err;
+
+ card = snd_card_get_index("Loopback");
+ if (card < 0)
+ SKIP(return, "No Loopback card, snd-aloop is probably not loaded");
+
+ sprintf(ctl_name, "hw:%d", card);
+ sprintf(self->play_name, "hw:%d,0,0", card);
+ sprintf(self->capt_name, "hw:%d,1,0", card);
+
+ err = snd_pcm_open(&pcm, self->capt_name, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK);
+ if (err == -EBUSY)
+ SKIP(return, "%s is in use", self->capt_name);
+ ASSERT_EQ(err, 0);
+ snd_pcm_close(pcm);
+ err = snd_pcm_open(&pcm, self->play_name, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
+ if (err == -EBUSY)
+ SKIP(return, "%s is in use", self->play_name);
+ ASSERT_EQ(err, 0);
+ snd_pcm_close(pcm);
+
+ ASSERT_EQ(snd_ctl_open(&self->ctl, ctl_name, SND_CTL_NONBLOCK), 0);
+ ASSERT_EQ(snd_ctl_subscribe_events(self->ctl, 1), 0);
+ self->saved_notify = cable_ctl_get(self->ctl, "PCM Notify");
+ self->notify_saved = true;
+}
+
+FIXTURE_TEARDOWN(aloop) {
+ if (self->notify_saved)
+ set_notify(self->ctl, self->saved_notify);
+ if (self->ctl)
+ snd_ctl_close(self->ctl);
+}
+
+/* Without notify, a playback opened while a capture is set up is pinned to its parameters. */
+TEST_F(aloop, playback_constrained_without_notify) {
+ struct probe_result res;
+ snd_pcm_t *capt;
+
+ ASSERT_EQ(set_notify(self->ctl, false), 0);
+ ASSERT_EQ(open_pcm(&capt, self->capt_name, SND_PCM_STREAM_CAPTURE, &capture_params), 0);
+
+ ASSERT_EQ(probe_pcm(self->play_name, SND_PCM_STREAM_PLAYBACK, playback_params.format,
+ &res), 0);
+ EXPECT_EQ(res.rate_min, capture_params.rate);
+ EXPECT_EQ(res.rate_max, capture_params.rate);
+ EXPECT_EQ(res.channels_min, capture_params.channels);
+ EXPECT_EQ(res.channels_max, capture_params.channels);
+ EXPECT_FALSE(res.format_ok);
+
+ snd_pcm_close(capt);
+}
+
+/* With notify, the playback side is free to pick other parameters. */
+TEST_F(aloop, playback_unconstrained_with_notify) {
+ struct probe_result res;
+ snd_pcm_t *capt;
+
+ ASSERT_EQ(set_notify(self->ctl, true), 0);
+ ASSERT_EQ(open_pcm(&capt, self->capt_name, SND_PCM_STREAM_CAPTURE, &capture_params), 0);
+
+ ASSERT_EQ(probe_pcm(self->play_name, SND_PCM_STREAM_PLAYBACK, playback_params.format,
+ &res), 0);
+ EXPECT_LE(res.rate_min, capture_params.rate);
+ EXPECT_GE(res.rate_max, playback_params.rate);
+ EXPECT_LE(res.channels_min, capture_params.channels);
+ EXPECT_GE(res.channels_max, playback_params.channels);
+ EXPECT_TRUE(res.format_ok);
+
+ snd_pcm_close(capt);
+}
+
+/*
+ * With notify, starting a playback with different parameters stops the
+ * running capture, and the "PCM Slave" controls report the new parameters
+ * with a value event for each one that changed.
+ */
+TEST_F(aloop, params_change_stops_capture_with_notify) {
+ snd_pcm_t *capt, *play;
+ unsigned int events;
+
+ /*
+ * The controls keep the last playback's parameters, and only notify on
+ * a change. Start a playback with the capture's parameters while no
+ * capture is open, so that every control changes below.
+ */
+ ASSERT_EQ(open_pcm(&play, self->play_name, SND_PCM_STREAM_PLAYBACK, &capture_params), 0);
+ ASSERT_EQ(start_playback(play, &capture_params), 0);
+ snd_pcm_close(play);
+
+ ASSERT_EQ(set_notify(self->ctl, true), 0);
+ ASSERT_EQ(open_pcm(&capt, self->capt_name, SND_PCM_STREAM_CAPTURE, &capture_params), 0);
+ ASSERT_EQ(snd_pcm_start(capt), 0);
+ ASSERT_EQ(snd_pcm_state(capt), SND_PCM_STATE_RUNNING);
+ EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Active"), 0);
+ read_events(self->ctl);
+
+ ASSERT_EQ(open_pcm(&play, self->play_name, SND_PCM_STREAM_PLAYBACK, &playback_params), 0)
+ TH_LOG("Playback refused other parameters while the capture is running");
+ ASSERT_EQ(start_playback(play, &playback_params), 0);
+
+ /* loopback_check_format() stops the capture from the playback's start trigger. */
+ EXPECT_NE(snd_pcm_state(capt), SND_PCM_STATE_RUNNING);
+
+ EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Active"), 1);
+ EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Format"), playback_params.format);
+ EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Rate"), playback_params.rate);
+ EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Channels"), playback_params.channels);
+ EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Access Mode"), 1);
+
+ events = read_events(self->ctl);
+ EXPECT_TRUE(events & EV_ACTIVE);
+ EXPECT_TRUE(events & EV_FORMAT);
+ EXPECT_TRUE(events & EV_RATE);
+ EXPECT_TRUE(events & EV_CHANNELS);
+ EXPECT_TRUE(events & EV_ACCESS);
+
+ snd_pcm_close(play);
+ snd_pcm_close(capt);
+}
+
+/* With notify, a capture opened second is still pinned to the playback's parameters. */
+TEST_F(aloop, capture_constrained_with_notify) {
+ struct probe_result res;
+ snd_pcm_t *play;
+
+ ASSERT_EQ(set_notify(self->ctl, true), 0);
+ ASSERT_EQ(open_pcm(&play, self->play_name, SND_PCM_STREAM_PLAYBACK, &playback_params), 0);
+
+ ASSERT_EQ(probe_pcm(self->capt_name, SND_PCM_STREAM_CAPTURE, capture_params.format,
+ &res), 0);
+ EXPECT_EQ(res.rate_min, playback_params.rate);
+ EXPECT_EQ(res.rate_max, playback_params.rate);
+ EXPECT_EQ(res.channels_min, playback_params.channels);
+ EXPECT_EQ(res.channels_max, playback_params.channels);
+ EXPECT_FALSE(res.format_ok);
+
+ snd_pcm_close(play);
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/alsa/mixer-test.c b/tools/testing/selftests/alsa/mixer-test.c
index 0857d64c322a..966ef83354b9 100644
--- a/tools/testing/selftests/alsa/mixer-test.c
+++ b/tools/testing/selftests/alsa/mixer-test.c
@@ -28,7 +28,13 @@
#include "kselftest.h"
#include "alsa-local.h"
-#define TESTS_PER_CONTROL 7
+#define TESTS_PER_CONTROL 8
+
+/* Suffixes of the SNDRV_CTL_NAME_IEC958() names, not exported to userspace */
+#define IEC958_DEFAULT "Default"
+#define IEC958_CON_MASK "Con Mask"
+#define IEC958_PRO_MASK "Pro Mask"
+#define IEC958_MASK "Mask"
struct card_data {
snd_ctl_t *handle;
@@ -46,9 +52,14 @@ struct ctl_data {
snd_ctl_elem_id_t *id;
snd_ctl_elem_info_t *info;
snd_ctl_elem_value_t *def_val;
+ snd_ctl_elem_value_t *snapshot;
+ bool snapshot_valid;
+ bool moved;
int elem;
int event_missing;
int event_spurious;
+ int side_effects;
+ unsigned int ev_mask;
struct card_data *card;
struct ctl_data *next;
};
@@ -153,6 +164,10 @@ static void find_controls(void)
if (err < 0)
ksft_exit_fail_msg("Out of memory\n");
+ err = snd_ctl_elem_value_malloc(&ctl_data->snapshot);
+ if (err < 0)
+ ksft_exit_fail_msg("Out of memory\n");
+
snd_ctl_elem_list_get_id(card_data->ctls, ctl,
ctl_data->id);
snd_ctl_elem_info_set_id(ctl_data->info, ctl_data->id);
@@ -201,6 +216,20 @@ static void find_controls(void)
snd_config_delete(config);
}
+/* The control on the same card that an event's numid refers to */
+static struct ctl_data *find_ctl_by_numid(struct card_data *card,
+ unsigned int numid)
+{
+ struct ctl_data *ctl;
+
+ for (ctl = ctl_list; ctl != NULL; ctl = ctl->next)
+ if (ctl->card == card &&
+ snd_ctl_elem_info_get_numid(ctl->info) == numid)
+ return ctl;
+
+ return NULL;
+}
+
/*
* Block for up to timeout ms for an event, returns a negative value
* on error, 0 for no event and 1 for an event.
@@ -259,8 +288,21 @@ static int wait_for_event(struct ctl_data *ctl, int timeout)
mask = snd_ctl_event_elem_get_mask(event);
ev_id = snd_ctl_event_elem_get_numid(event);
if (ev_id != snd_ctl_elem_info_get_numid(ctl->info)) {
+ struct ctl_data *other = find_ctl_by_numid(ctl->card,
+ ev_id);
+
+ /*
+ * Remember that the driver announced this one.
+ * test_ctl_write_side_effects() uses that to tell a
+ * deliberate link from a silent register collision.
+ */
+ if (other)
+ other->ev_mask |= mask;
+
ksft_print_msg("Event for unexpected ctl %s\n",
snd_ctl_event_elem_get_name(event));
+ /* The loop condition must not see the other control's mask */
+ mask = 0;
continue;
}
@@ -842,6 +884,251 @@ static bool test_ctl_write_valid_enumerated(struct ctl_data *ctl)
return !fail;
}
+/*
+ * Find the read only mask control for an IEC958 value control. The two
+ * share device, subdevice and index but not always the interface.
+ */
+static struct ctl_data *find_iec958_mask_ctl(struct ctl_data *ctl,
+ const char *suffix)
+{
+ char name[64];
+ int stem;
+ struct ctl_data *mask;
+
+ stem = strlen(ctl->name) - strlen(IEC958_DEFAULT);
+ if (snprintf(name, sizeof(name), "%.*s%s", stem, ctl->name, suffix) >=
+ (int)sizeof(name))
+ return NULL;
+
+ for (mask = ctl_list; mask != NULL; mask = mask->next) {
+ if (mask->card != ctl->card)
+ continue;
+ if (snd_ctl_elem_info_get_type(mask->info) !=
+ SND_CTL_ELEM_TYPE_IEC958)
+ continue;
+ if (snd_ctl_elem_info_is_inactive(mask->info))
+ continue;
+ if (!snd_ctl_elem_info_is_readable(mask->info))
+ continue;
+ if (snd_ctl_elem_id_get_device(mask->id) !=
+ snd_ctl_elem_id_get_device(ctl->id))
+ continue;
+ if (snd_ctl_elem_id_get_subdevice(mask->id) !=
+ snd_ctl_elem_id_get_subdevice(ctl->id))
+ continue;
+ if (snd_ctl_elem_id_get_index(mask->id) !=
+ snd_ctl_elem_id_get_index(ctl->id))
+ continue;
+ if (strcmp(mask->name, name) == 0)
+ return mask;
+ }
+
+ return NULL;
+}
+
+/*
+ * Read the bits the device says it implements. Bit 0 of the first status
+ * byte picks which mask applies, some devices publish only a plain Mask.
+ */
+static bool read_iec958_mask(struct ctl_data *ctl,
+ const snd_aes_iec958_t *cur,
+ snd_aes_iec958_t *mask)
+{
+ int err;
+ struct ctl_data *mask_ctl;
+ snd_ctl_elem_value_t *val;
+
+ if (!strend(ctl->name, IEC958_DEFAULT))
+ return false;
+
+ if (cur->status[0] & IEC958_AES0_PROFESSIONAL)
+ mask_ctl = find_iec958_mask_ctl(ctl, IEC958_PRO_MASK);
+ else
+ mask_ctl = find_iec958_mask_ctl(ctl, IEC958_CON_MASK);
+ if (!mask_ctl)
+ mask_ctl = find_iec958_mask_ctl(ctl, IEC958_MASK);
+ if (!mask_ctl)
+ return false;
+
+ snd_ctl_elem_value_alloca(&val);
+ snd_ctl_elem_value_set_id(val, mask_ctl->id);
+
+ err = snd_ctl_elem_read(mask_ctl->card->handle, val);
+ if (err < 0) {
+ ksft_print_msg("snd_ctl_elem_read() failed for %s: %s\n",
+ mask_ctl->name, snd_strerror(err));
+ return false;
+ }
+
+ snd_ctl_elem_value_get_iec958(val, mask);
+
+ return true;
+}
+
+/*
+ * Throw away the events from a write we are not checking, one left behind
+ * would make a missing notification look like a notification we got.
+ */
+static int drop_events(struct ctl_data *ctl)
+{
+ int err;
+
+ do {
+ err = wait_for_event(ctl, 0);
+ } while (err > 0);
+
+ return err;
+}
+
+/*
+ * Toggle every bit the device advertises, one at a time. Each one starts
+ * from the value we read since a driver can look at the rest of the block
+ * when it stores a bit, and bit 0 of the first status byte selects the
+ * layout the others are read in so leave that one alone.
+ */
+static bool test_ctl_write_valid_iec958_mask(struct ctl_data *ctl,
+ snd_ctl_elem_value_t *orig_val,
+ const snd_aes_iec958_t *mask)
+{
+ int err, j;
+ unsigned int byte;
+ unsigned char bit;
+ bool fail = false, found = false;
+ snd_aes_iec958_t iec958;
+ snd_ctl_elem_value_t *val;
+ snd_ctl_elem_value_alloca(&val);
+
+ snd_ctl_elem_value_copy(val, orig_val);
+
+ for (byte = 0; byte < sizeof(mask->status); byte++) {
+ for (j = 0; j < 8; j++) {
+ bit = 1 << j;
+
+ if (byte == 0 && bit == IEC958_AES0_PROFESSIONAL)
+ continue;
+ if (!(mask->status[byte] & bit))
+ continue;
+ found = true;
+
+ snd_ctl_elem_value_get_iec958(orig_val, &iec958);
+ iec958.status[byte] ^= bit;
+ snd_ctl_elem_value_set_iec958(val, &iec958);
+
+ err = write_and_verify(ctl, val, NULL);
+ if (err != 0) {
+ ksft_print_msg("%s failed to set advertised status[%u] 0x%02x\n",
+ ctl->name, byte, bit);
+ fail = true;
+ }
+ }
+ }
+
+ if (!found)
+ ksft_print_msg("%s implements no settable status bits\n",
+ ctl->name);
+
+ return !fail;
+}
+
+/*
+ * With nothing advertised all we can do is try non audio, and the device
+ * need not implement even that. Write it once and ignore the result, then
+ * compare against what came back so the value cannot fail, which leaves the
+ * notification counted by event_missing.
+ */
+static bool test_ctl_write_valid_iec958_guess(struct ctl_data *ctl,
+ snd_ctl_elem_value_t *orig_val)
+{
+ int err;
+ snd_aes_iec958_t iec958;
+ snd_ctl_elem_value_t *val, *read_val, *w_val;
+ snd_ctl_elem_value_alloca(&val);
+ snd_ctl_elem_value_alloca(&read_val);
+ snd_ctl_elem_value_alloca(&w_val);
+
+ snd_ctl_elem_value_get_iec958(orig_val, &iec958);
+ iec958.status[0] ^= IEC958_AES0_NONAUDIO;
+ snd_ctl_elem_value_copy(val, orig_val);
+ snd_ctl_elem_value_set_iec958(val, &iec958);
+
+ /* Writing can modify the value so keep a copy to write from */
+ snd_ctl_elem_value_copy(w_val, val);
+ err = snd_ctl_elem_write(ctl->card->handle, w_val);
+ if (err < 0) {
+ ksft_print_msg("snd_ctl_elem_write() failed: %s\n",
+ snd_strerror(err));
+ return false;
+ }
+
+ snd_ctl_elem_value_set_id(read_val, ctl->id);
+ err = snd_ctl_elem_read(ctl->card->handle, read_val);
+ if (err < 0) {
+ ksft_print_msg("snd_ctl_elem_read() failed: %s\n",
+ snd_strerror(err));
+ return false;
+ }
+
+ /* Put it back where we found it, then drop the events from both */
+ snd_ctl_elem_value_copy(w_val, orig_val);
+ err = snd_ctl_elem_write(ctl->card->handle, w_val);
+ if (err < 0) {
+ ksft_print_msg("snd_ctl_elem_write() failed: %s\n",
+ snd_strerror(err));
+ return false;
+ }
+
+ err = drop_events(ctl);
+ if (err < 0) {
+ ksft_print_msg("drop_events() failed for %s: %d\n",
+ ctl->name, err);
+ return false;
+ }
+
+ if (snd_ctl_elem_value_compare(val, read_val)) {
+ ksft_print_msg("%s does not implement status[0] 0x%02x\n",
+ ctl->name, IEC958_AES0_NONAUDIO);
+
+ /* Expect what came back, the notification is still checked */
+ return write_and_verify(ctl, val, read_val) == 0;
+ }
+
+ return write_and_verify(ctl, val, NULL) == 0;
+}
+
+/*
+ * Write the channel status bits and check that userspace is told about it.
+ * Where a mask was found every bit in it has to stick, otherwise we fall
+ * back to one guessed bit the device is free to ignore.
+ */
+static bool test_ctl_write_valid_iec958(struct ctl_data *ctl)
+{
+ int err;
+ snd_aes_iec958_t iec958, mask;
+ snd_ctl_elem_value_t *orig_val;
+ snd_ctl_elem_value_alloca(&orig_val);
+
+ /*
+ * The bytes past the ones a driver implements are compared too,
+ * so start from a read rather than building a value here.
+ */
+ snd_ctl_elem_value_set_id(orig_val, ctl->id);
+ err = snd_ctl_elem_read(ctl->card->handle, orig_val);
+ if (err < 0) {
+ ksft_print_msg("snd_ctl_elem_read() failed: %s\n",
+ snd_strerror(err));
+ return false;
+ }
+
+ snd_ctl_elem_value_get_iec958(orig_val, &iec958);
+
+ if (!read_iec958_mask(ctl, &iec958, &mask)) {
+ ksft_print_msg("%s falling back to non audio\n", ctl->name);
+ return test_ctl_write_valid_iec958_guess(ctl, orig_val);
+ }
+
+ return test_ctl_write_valid_iec958_mask(ctl, orig_val, &mask);
+}
+
static void test_ctl_write_valid(struct ctl_data *ctl)
{
bool pass;
@@ -878,6 +1165,10 @@ static void test_ctl_write_valid(struct ctl_data *ctl)
pass = test_ctl_write_valid_enumerated(ctl);
break;
+ case SND_CTL_ELEM_TYPE_IEC958:
+ pass = test_ctl_write_valid_iec958(ctl);
+ break;
+
default:
/* No tests for this yet */
ksft_test_result_skip("write_valid.%s.%d\n",
@@ -892,6 +1183,202 @@ static void test_ctl_write_valid(struct ctl_data *ctl)
ctl->card->card_name, ctl->elem);
}
+/*
+ * Build the smallest or the largest value the control offers. The smallest
+ * clears the control's register field and the largest sets its top bit, which
+ * is the bit a mask one bit too wide puts in its neighbour.
+ */
+static bool set_limit_value(struct ctl_data *ctl, snd_ctl_elem_value_t *val,
+ bool max)
+{
+ int i, count = snd_ctl_elem_info_get_count(ctl->info);
+
+ snd_ctl_elem_value_set_id(val, ctl->id);
+
+ switch (snd_ctl_elem_info_get_type(ctl->info)) {
+ case SND_CTL_ELEM_TYPE_BOOLEAN:
+ for (i = 0; i < count; i++)
+ snd_ctl_elem_value_set_boolean(val, i, max);
+ return true;
+
+ case SND_CTL_ELEM_TYPE_INTEGER:
+ for (i = 0; i < count; i++)
+ snd_ctl_elem_value_set_integer(val, i, max ?
+ snd_ctl_elem_info_get_max(ctl->info) :
+ snd_ctl_elem_info_get_min(ctl->info));
+ return true;
+
+ case SND_CTL_ELEM_TYPE_INTEGER64:
+ for (i = 0; i < count; i++)
+ snd_ctl_elem_value_set_integer64(val, i, max ?
+ snd_ctl_elem_info_get_max64(ctl->info) :
+ snd_ctl_elem_info_get_min64(ctl->info));
+ return true;
+
+ case SND_CTL_ELEM_TYPE_ENUMERATED:
+ for (i = 0; i < count; i++)
+ snd_ctl_elem_value_set_enumerated(val, i, max ?
+ snd_ctl_elem_info_get_items(ctl->info) - 1 : 0);
+ return true;
+
+ default:
+ /* Nothing sensible to write for the rest */
+ return false;
+ }
+}
+
+/* Note every control on the card that no longer reads as it did */
+static void find_moved_ctls(struct ctl_data *ctl, snd_ctl_elem_value_t *val)
+{
+ struct ctl_data *other;
+ int err;
+
+ for (other = ctl_list; other != NULL; other = other->next) {
+ if (!other->snapshot_valid)
+ continue;
+
+ /*
+ * The buffer is shared and compare() looks at all of it, so
+ * clear what the last control left in the slots this one
+ * does not use.
+ */
+ snd_ctl_elem_value_clear(val);
+ snd_ctl_elem_value_set_id(val, other->id);
+ err = snd_ctl_elem_read(ctl->card->handle, val);
+ if (err < 0) {
+ ksft_print_msg("snd_ctl_elem_read() failed for %s: %s\n",
+ other->name, snd_strerror(err));
+ continue;
+ }
+
+ if (snd_ctl_elem_value_compare(other->snapshot, val))
+ other->moved = true;
+ }
+}
+
+/*
+ * Write one control and look for others on the same card that moved with it.
+ * A driver that links two controls on purpose tells userspace about both, so
+ * only an unannounced change is counted. That is what a control whose
+ * register mask covers bits belonging to its neighbour looks like from here.
+ */
+static void test_ctl_write_side_effects(struct ctl_data *ctl)
+{
+ struct ctl_data *other;
+ snd_ctl_elem_value_t *min_val, *max_val, *read_val;
+ int err;
+
+ snd_ctl_elem_value_alloca(&min_val);
+ snd_ctl_elem_value_alloca(&max_val);
+ snd_ctl_elem_value_alloca(&read_val);
+
+ /* Without a readable default there is nothing to put back */
+ if (snd_ctl_elem_info_is_inactive(ctl->info) ||
+ !snd_ctl_elem_info_is_writable(ctl->info) ||
+ !snd_ctl_elem_info_is_readable(ctl->info) ||
+ !set_limit_value(ctl, min_val, false) ||
+ !set_limit_value(ctl, max_val, true)) {
+ ksft_test_result_skip("write_side_effects.%s.%d\n",
+ ctl->card->card_name, ctl->elem);
+ return;
+ }
+
+ /* Drain first, a stale event would look like the driver announced it */
+ drop_events(ctl);
+
+ /*
+ * Record what the rest of the card reads as. A volatile control can
+ * move on its own so there is nothing to compare it against.
+ */
+ for (other = ctl_list; other != NULL; other = other->next) {
+ other->snapshot_valid = false;
+ other->moved = false;
+ other->ev_mask = 0;
+
+ if (other == ctl || other->card != ctl->card)
+ continue;
+ if (!snd_ctl_elem_info_is_readable(other->info) ||
+ snd_ctl_elem_info_is_volatile(other->info))
+ continue;
+
+ snd_ctl_elem_value_clear(other->snapshot);
+ snd_ctl_elem_value_set_id(other->snapshot, other->id);
+ err = snd_ctl_elem_read(ctl->card->handle, other->snapshot);
+ if (err < 0) {
+ ksft_print_msg("snd_ctl_elem_read() failed for %s: %s\n",
+ other->name, snd_strerror(err));
+ continue;
+ }
+
+ other->snapshot_valid = true;
+ }
+
+ /*
+ * Compare against the snapshot after each write, before anything is
+ * put back. Restoring the control we wrote goes through the same
+ * mask, so doing it first would hide the change we are looking for.
+ */
+ err = snd_ctl_elem_write(ctl->card->handle, min_val);
+ if (err >= 0) {
+ drop_events(ctl);
+ find_moved_ctls(ctl, read_val);
+ err = snd_ctl_elem_write(ctl->card->handle, max_val);
+ }
+ if (err < 0) {
+ ksft_print_msg("snd_ctl_elem_write() failed for %s: %s\n",
+ ctl->name, snd_strerror(err));
+ } else {
+ drop_events(ctl);
+ find_moved_ctls(ctl, read_val);
+ }
+
+ for (other = ctl_list; other != NULL; other = other->next) {
+ if (!other->moved)
+ continue;
+
+ if (other->ev_mask & SND_CTL_EVENT_MASK_VALUE) {
+ ksft_print_msg("Writing %s changed %s, the driver said so\n",
+ ctl->name, other->name);
+ } else {
+ ksft_print_msg("Writing %s silently changed %s\n",
+ ctl->name, other->name);
+ ctl->side_effects++;
+ }
+ }
+
+ /*
+ * The control we wrote goes back first so its mask stops moving the
+ * rest. A plain write keeps this out of the event counters, they
+ * belong to the tests that check them.
+ */
+ snd_ctl_elem_write(ctl->card->handle, ctl->def_val);
+
+ for (other = ctl_list; other != NULL; other = other->next) {
+ if (!other->snapshot_valid ||
+ !snd_ctl_elem_info_is_writable(other->info))
+ continue;
+
+ snd_ctl_elem_value_clear(read_val);
+ snd_ctl_elem_value_set_id(read_val, other->id);
+ if (snd_ctl_elem_read(ctl->card->handle, read_val) < 0)
+ continue;
+
+ if (snd_ctl_elem_value_compare(other->snapshot, read_val))
+ snd_ctl_elem_write(ctl->card->handle, other->snapshot);
+ }
+
+ /* Our own restores queue events, the next test must not see them */
+ drop_events(ctl);
+
+ if (err < 0)
+ ksft_test_result_skip("write_side_effects.%s.%d\n",
+ ctl->card->card_name, ctl->elem);
+ else
+ ksft_test_result(!ctl->side_effects,
+ "write_side_effects.%s.%d\n",
+ ctl->card->card_name, ctl->elem);
+}
+
static bool test_ctl_write_invalid_value(struct ctl_data *ctl,
snd_ctl_elem_value_t *val)
{
@@ -1135,6 +1622,7 @@ int main(void)
test_ctl_name(ctl);
test_ctl_write_default(ctl);
test_ctl_write_valid(ctl);
+ test_ctl_write_side_effects(ctl);
test_ctl_write_invalid(ctl);
test_ctl_event_missing(ctl);
test_ctl_event_spurious(ctl);
diff --git a/tools/testing/selftests/alsa/utimer-test.c b/tools/testing/selftests/alsa/utimer-test.c
index 1a9ff010cb11..07aeaa777649 100644
--- a/tools/testing/selftests/alsa/utimer-test.c
+++ b/tools/testing/selftests/alsa/utimer-test.c
@@ -51,6 +51,9 @@ FIXTURE_SETUP(timer_f) {
self->utimer_info->resolution = (NANO / FRAME_RATE * PERIOD_SIZE);
timer_dev_fd = open("/dev/snd/timer", O_RDONLY);
+ if (timer_dev_fd < 0 &&
+ (errno == ENOENT || errno == ENODEV || errno == ENXIO))
+ SKIP(return, "ALSA timer device unavailable");
ASSERT_GE(timer_dev_fd, 0);
if (ioctl(timer_dev_fd, SNDRV_TIMER_IOCTL_CREATE, self->utimer_info) < 0) {
@@ -156,6 +159,9 @@ TEST(wrong_timers_test) {
};
timer_dev_fd = open("/dev/snd/timer", O_RDONLY);
+ if (timer_dev_fd < 0 &&
+ (errno == ENOENT || errno == ENODEV || errno == ENXIO))
+ SKIP(return, "ALSA timer device unavailable");
ASSERT_GE(timer_dev_fd, 0);
utimer_fd = ioctl(timer_dev_fd, SNDRV_TIMER_IOCTL_CREATE, &wrong_timer);