summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/sound/codecs/cs35l56.rst292
-rw-r--r--Documentation/sound/codecs/index.rst9
-rw-r--r--Documentation/sound/designs/midi-2.0.rst18
-rw-r--r--Documentation/sound/index.rst1
-rw-r--r--MAINTAINERS12
-rw-r--r--drivers/acpi/scan.c1
-rw-r--r--drivers/platform/x86/serial-multi-instantiate.c12
-rw-r--r--include/sound/hdaudio_ext.h45
-rw-r--r--include/sound/pcm.h7
-rw-r--r--include/sound/rawmidi.h11
-rw-r--r--include/sound/simple_card_utils.h7
-rw-r--r--include/sound/ump.h1
-rw-r--r--include/uapi/sound/asequencer.h12
-rw-r--r--include/uapi/sound/asound.h8
-rw-r--r--include/uapi/sound/fcp.h120
-rw-r--r--include/uapi/sound/tlv.h2
-rw-r--r--sound/core/memory.c41
-rw-r--r--sound/core/rawmidi.c3
-rw-r--r--sound/core/seq/Kconfig4
-rw-r--r--sound/core/seq/oss/seq_oss_device.h5
-rw-r--r--sound/core/seq/oss/seq_oss_init.c8
-rw-r--r--sound/core/seq/oss/seq_oss_synth.c84
-rw-r--r--sound/core/seq/seq_clientmgr.c19
-rw-r--r--sound/core/seq/seq_system.c34
-rw-r--r--sound/core/seq/seq_system.h35
-rw-r--r--sound/core/seq/seq_ump_client.c42
-rw-r--r--sound/core/ump.c112
-rw-r--r--sound/firewire/fireface/ff-protocol-former.c4
-rw-r--r--sound/hda/hdac_component.c7
-rw-r--r--sound/hda/hdac_stream.c63
-rw-r--r--sound/isa/sb/sb16_csp.c5
-rw-r--r--sound/pci/ac97/ac97_codec.c2
-rw-r--r--sound/pci/ac97/ac97_proc.c8
-rw-r--r--sound/pci/ad1889.c8
-rw-r--r--sound/pci/cmipci.c2
-rw-r--r--sound/pci/ctxfi/ctdaio.c48
-rw-r--r--sound/pci/emu10k1/emuproc.c15
-rw-r--r--sound/pci/ens1370.c6
-rw-r--r--sound/pci/hda/Kconfig14
-rw-r--r--sound/pci/hda/Makefile2
-rw-r--r--sound/pci/hda/hda_hwdep.c2
-rw-r--r--sound/pci/hda/hda_intel.c4
-rw-r--r--sound/pci/hda/hda_sysfs.c2
-rw-r--r--sound/pci/hda/ideapad_hotkey_led_helper.c36
-rw-r--r--sound/pci/hda/patch_conexant.c13
-rw-r--r--sound/pci/hda/patch_realtek.c48
-rw-r--r--sound/pci/hda/tas2781-spi.h158
-rw-r--r--sound/pci/hda/tas2781_hda_spi.c1270
-rw-r--r--sound/pci/hda/tas2781_spi_fwlib.c2006
-rw-r--r--sound/pci/lola/lola_clock.c2
-rw-r--r--sound/pci/nm256/nm256.c8
-rw-r--r--sound/pci/rme32.c13
-rw-r--r--sound/pci/rme96.c13
-rw-r--r--sound/pci/rme9652/hdsp.c10
-rw-r--r--sound/pci/rme9652/hdspm.c15
-rw-r--r--sound/pci/rme9652/rme9652.c5
-rw-r--r--sound/pci/sonicvibes.c8
-rw-r--r--sound/pci/trident/trident_main.c4
-rw-r--r--sound/soc/codecs/Kconfig1
-rw-r--r--sound/soc/codecs/cs42l43.c1
-rw-r--r--sound/soc/codecs/es8316.c10
-rw-r--r--sound/soc/codecs/es8326.c23
-rw-r--r--sound/soc/codecs/nau8824.c8
-rw-r--r--sound/soc/codecs/tas2781-i2c.c2
-rw-r--r--sound/soc/generic/simple-card-utils.c10
-rw-r--r--sound/soc/intel/avs/apl.c1
-rw-r--r--sound/soc/intel/avs/cnl.c1
-rw-r--r--sound/soc/intel/avs/registers.h45
-rw-r--r--sound/soc/intel/avs/skl.c1
-rw-r--r--sound/soc/intel/boards/sof_sdw.c23
-rw-r--r--sound/soc/qcom/lpass-platform.c6
-rw-r--r--sound/soc/renesas/rcar/adg.c28
-rw-r--r--sound/soc/renesas/rcar/core.c4
-rw-r--r--sound/soc/renesas/rcar/rsnd.h2
-rw-r--r--sound/soc/samsung/Kconfig6
-rw-r--r--sound/usb/Makefile1
-rw-r--r--sound/usb/fcp.c1134
-rw-r--r--sound/usb/fcp.h7
-rw-r--r--sound/usb/mixer_quirks.c7
-rw-r--r--sound/usb/mixer_scarlett2.c8
-rw-r--r--sound/usb/quirks.c2
81 files changed, 5713 insertions, 364 deletions
diff --git a/Documentation/sound/codecs/cs35l56.rst b/Documentation/sound/codecs/cs35l56.rst
new file mode 100644
index 000000000000..98c6f6c74394
--- /dev/null
+++ b/Documentation/sound/codecs/cs35l56.rst
@@ -0,0 +1,292 @@
+.. SPDX-License-Identifier: GPL-2.0-only
+
+=====================================================================
+Audio drivers for Cirrus Logic CS35L54/56/57 Boosted Smart Amplifiers
+=====================================================================
+:Copyright: 2025 Cirrus Logic, Inc. and
+ Cirrus Logic International Semiconductor Ltd.
+
+Contact: patches@opensource.cirrus.com
+
+Summary
+=======
+
+The high-level summary of this document is:
+
+**If you have a laptop that uses CS35L54/56/57 amplifiers but audio is not
+working, DO NOT ATTEMPT TO USE FIRMWARE AND SETTINGS FROM ANOTHER LAPTOP,
+EVEN IF THAT LAPTOP SEEMS SIMILAR.**
+
+The CS35L54/56/57 amplifiers must be correctly configured for the power
+supply voltage, speaker impedance, maximum speaker voltage/current, and
+other external hardware connections.
+
+The amplifiers feature advanced boost technology that increases the voltage
+used to drive the speakers, while proprietary speaker protection algorithms
+allow these boosted amplifiers to push the limits of the speakers without
+causing damage. These **must** be configured correctly.
+
+Supported Cirrus Logic amplifiers
+---------------------------------
+
+The cs35l56 drivers support:
+
+* CS35L54
+* CS35L56
+* CS35L57
+
+There are two drivers in the kernel
+
+*For systems using SoundWire*: sound/soc/codecs/cs35l56.c and associated files
+
+*For systems using HDA*: sound/pci/hda/cs35l56_hda.c
+
+Firmware
+========
+
+The amplifier is controlled and managed by firmware running on the internal
+DSP. Firmware files are essential to enable the full capabilities of the
+amplifier.
+
+Firmware is distributed in the linux-firmware repository:
+https://gitlab.com/kernel-firmware/linux-firmware.git
+
+On most SoundWire systems the amplifier has a default minimum capability to
+produce audio. However this will be
+
+* at low volume, to protect the speakers, since the speaker specifications
+ and power supply voltages are unknown.
+* a mono mix of left and right channels.
+
+On some SoundWire systems that have both CS42L43 and CS35L56/57 the CS35L56/57
+receive their audio from the CS42L43 instead of directly from the host
+SoundWire interface. These systems can be identified by the CS42L43 showing
+in dmesg as a SoundWire device, but the CS35L56/57 as SPI. On these systems
+the firmware is *mandatory* to enable receiving the audio from the CS42L43.
+
+On HDA systems the firmware is *mandatory* to enable HDA bridge mode. There
+will not be any audio from the amplifiers without firmware.
+
+Cirrus Logic firmware files
+---------------------------
+
+Each amplifier requires two firmware files. One file has a .wmfw suffix, the
+other has a .bin suffix.
+
+The firmware is customized by the OEM to match the hardware of each laptop,
+and the firmware is specific to that laptop. Because of this, there are many
+firmware files in linux-firmware for these amplifiers. Firmware files are
+**not interchangeable between laptops**.
+
+Cirrus Logic submits files for known laptops to the upstream linux-firmware
+repository. Providing Cirrus Logic is aware of a particular laptop and has
+permission from the manufacturer to publish the firmware, it will be pushed
+to linux-firmware. You may need to upgrade to a newer release of
+linux-firmware to obtain the firmware for your laptop.
+
+**Important:** the Makefile for linux-firmware creates symlinks that are listed
+in the WHENCE file. These symlinks are required for the CS35L56 driver to be
+able to load the firmware.
+
+How do I know which firmware file I should have?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+All firmware file names are qualified with a unique "system ID". On normal
+x86 PCs with PCI audio this is the Vendor Subsystem ID (SSID) of the host
+PCI audio interface.
+
+The SSID can be viewed using the lspci tool::
+
+ lspci -v -nn | grep -A2 -i audio
+ 0000:00:1f.3 Audio device [0403]: Intel Corporation Meteor Lake-P HD Audio Controller [8086:7e28]
+ Subsystem: Dell Meteor Lake-P HD Audio Controller [1028:0c63]
+
+In this example the SSID is 10280c63.
+
+The format of the firmware file names is:
+
+ cs35lxx-b0-dsp1-misc-SSID[-spkidX]-ampN
+
+Where:
+
+ * cs35lxx-b0 is the amplifier model and silicon revision. This information
+ is logged by the driver during initialization.
+ * SSID is the 8-digit hexadecimal SSID value.
+ * ampN is the amplifier number (for example amp1). This is the same as
+ the prefix on the ALSA control names except that it is always lower-case
+ in the file name.
+ * spkidX is an optional part, used for laptops that have firmware
+ configurations for different makes and models of internal speakers.
+
+Sound Open Firmware and ALSA topology files
+-------------------------------------------
+
+All SoundWire systems will require a Sound Open Firmware (SOF) for the
+host CPU audio DSP, together with an ALSA topology file (.tplg).
+
+The SOF firmware will usually be provided by the manufacturer of the host
+CPU (i.e. Intel or AMD). The .tplg file is normally part of the SOF firmware
+release.
+
+SOF binary builds are available from: https://github.com/thesofproject/sof-bin/releases
+
+The main SOF source is here: https://github.com/thesofproject
+
+ALSA-ucm configurations
+-----------------------
+Typically an appropriate ALSA-ucm configuration file is needed for
+use-case managers and audio servers such as PipeWire.
+
+Configuration files are available from the alsa-ucm-conf repository:
+https://git.alsa-project.org/?p=alsa-ucm-conf.git
+
+Kernel log messages
+===================
+
+SoundWire
+---------
+A successful initialization will look like this (this will be repeated for
+each amplifier)::
+
+ [ 7.568374] cs35l56 sdw:0:0:01fa:3556:01:0: supply VDD_P not found, using dummy regulator
+ [ 7.605208] cs35l56 sdw:0:0:01fa:3556:01:0: supply VDD_IO not found, using dummy regulator
+ [ 7.605313] cs35l56 sdw:0:0:01fa:3556:01:0: supply VDD_A not found, using dummy regulator
+ [ 7.939279] cs35l56 sdw:0:0:01fa:3556:01:0: Cirrus Logic CS35L56 Rev B0 OTP3 fw:3.4.4 (patched=0)
+ [ 7.947844] cs35l56 sdw:0:0:01fa:3556:01:0: Slave 4 state check1: UNATTACHED, status was 1
+ [ 8.740280] cs35l56 sdw:0:0:01fa:3556:01:0: supply VDD_B not found, using dummy regulator
+ [ 8.740552] cs35l56 sdw:0:0:01fa:3556:01:0: supply VDD_AMP not found, using dummy regulator
+ [ 9.242164] cs35l56 sdw:0:0:01fa:3556:01:0: DSP1: cirrus/cs35l56-b0-dsp1-misc-xxxxxxxx.wmfw: format 3 timestamp 0x66b2b872
+ [ 9.242173] cs35l56 sdw:0:0:01fa:3556:01:0: DSP1: cirrus/cs35l56-b0-dsp1-misc-xxxxxxxx.wmfw: Tue 05 Dec 2023 21:37:21 GMT Standard Time
+ [ 9.991709] cs35l56 sdw:0:0:01fa:3556:01:0: DSP1: Firmware: 1a00d6 vendor: 0x2 v3.11.23, 41 algorithms
+ [10.039098] cs35l56 sdw:0:0:01fa:3556:01:0: DSP1: cirrus/cs35l56-b0-dsp1-misc-xxxxxxxx-amp1.bin: v3.11.23
+ [10.879235] cs35l56 sdw:0:0:01fa:3556:01:0: Slave 4 state check1: UNATTACHED, status was 1
+ [11.401536] cs35l56 sdw:0:0:01fa:3556:01:0: Calibration applied
+
+HDA
+---
+A successful initialization will look like this (this will be repeated for
+each amplifier)::
+
+ [ 6.306475] cs35l56-hda i2c-CSC3556:00-cs35l56-hda.0: Cirrus Logic CS35L56 Rev B0 OTP3 fw:3.4.4 (patched=0)
+ [ 6.613892] cs35l56-hda i2c-CSC3556:00-cs35l56-hda.0: DSP system name: 'xxxxxxxx', amp name: 'AMP1'
+ [ 8.266660] snd_hda_codec_cs8409 ehdaudio0D0: bound i2c-CSC3556:00-cs35l56-hda.0 (ops cs35l56_hda_comp_ops [snd_hda_scodec_cs35l56])
+ [ 8.287525] cs35l56-hda i2c-CSC3556:00-cs35l56-hda.0: DSP1: cirrus/cs35l56-b0-dsp1-misc-xxxxxxxx.wmfw: format 3 timestamp 0x66b2b872
+ [ 8.287528] cs35l56-hda i2c-CSC3556:00-cs35l56-hda.0: DSP1: cirrus/cs35l56-b0-dsp1-misc-xxxxxxxx.wmfw: Tue 05 Dec 2023 21:37:21 GMT Standard Time
+ [ 9.984335] cs35l56-hda i2c-CSC3556:00-cs35l56-hda.0: DSP1: Firmware: 1a00d6 vendor: 0x2 v3.11.23, 41 algorithms
+ [10.085797] cs35l56-hda i2c-CSC3556:00-cs35l56-hda.0: DSP1: cirrus/cs35l56-b0-dsp1-misc-xxxxxxxx-amp1.bin: v3.11.23
+ [10.655237] cs35l56-hda i2c-CSC3556:00-cs35l56-hda.0: Calibration applied
+
+Important messages
+~~~~~~~~~~~~~~~~~~
+Cirrus Logic CS35L56 Rev B0 OTP3 fw:3.4.4 (patched=0)
+ Shows that the driver has been able to read device ID registers from the
+ amplifier.
+
+ * The actual amplifier type and silicon revision (CS35L56 B0 in this
+ example) is shown, as read from the amplifier identification registers.
+ * (patched=0) is normal, and indicates that the amplifier has been hard
+ reset and is running default ROM firmware.
+ * (patched=1) means that something has previously downloaded firmware
+ to the amplifier and the driver does not have control of the RESET
+ signal to be able to replace this preloaded firmware. This is normal
+ for systems where the BIOS downloads firmware to the amplifiers
+ before OS boot.
+ This status can also be seen if the cs35l56 kernel module is unloaded
+ and reloaded on a system where the driver does not have control of
+ RESET. SoundWire systems typically do not give the driver control of
+ RESET and only a BIOS (re)boot can reset the amplifiers.
+
+DSP1: cirrus/cs35l56-b0-dsp1-misc-xxxxxxxx.wmfw
+ Shows that a .wmfw firmware file was found and downloaded.
+
+DSP1: cirrus/cs35l56-b0-dsp1-misc-xxxxxxxx-amp1.bin
+ Shows that a .bin firmware file was found and downloaded.
+
+Calibration applied
+ Factory calibration data in EFI was written to the amplifier.
+
+Error messages
+==============
+This section explains some of the error messages that the driver can log.
+
+Algorithm coefficient version %d.%d.%d but expected %d.%d.%d
+ The version of the .bin file content does not match the loaded firmware.
+ Caused by mismatched .wmfw and .bin file, or .bin file was found but
+ .wmfw was not.
+
+No %s for algorithm %x
+ The version of the .bin file content does not match the loaded firmware.
+ Caused by mismatched .wmfw and .bin file, or .bin file was found but
+ .wmfw was not.
+
+.bin file required but not found
+ HDA driver did not find a .bin file that matches this hardware.
+
+Calibration disabled due to missing firmware controls
+ Driver was not able to write EFI calibration data to firmware registers.
+ This typically means that either:
+
+ * The driver did not find a suitable wmfw for this hardware, or
+ * The amplifier has already been patched with firmware by something
+ previously, and the driver does not have control of a hard RESET line
+ to be able to reset the amplifier and download the firmware files it
+ found. This situation is indicated by the device identification
+ string in the kernel log shows "(patched=1)"
+
+Failed to write calibration
+ Same meaning and cause as "Calibration disabled due to missing firmware
+ controls"
+
+Failed to read calibration data from EFI
+ Factory calibration data in EFI is missing, empty or corrupt.
+ This is most likely to be cause by accidentally deleting the file from
+ the EFI filesystem.
+
+No calibration for silicon ID
+ The factory calibration data in EFI does not match this hardware.
+ The most likely cause is that an amplifier has been replaced on the
+ motherboard without going through manufacturer calibration process to
+ generate calibration data for the new amplifier.
+
+Did not find any buses for CSCxxxx
+ Only on HDA systems. The HDA codec driver found an ACPI entry for
+ Cirrus Logic companion amps, but could not enumerate the ACPI entries for
+ the I2C/SPI buses. The most likely cause of this is that:
+
+ * The relevant bus driver (I2C or SPI) is not part of the kernel.
+ * The HDA codec driver was built-in to the kernel but the I2C/SPI
+ bus driver is a module and so the HDA codec driver cannot call the
+ bus driver functions.
+
+init_completion timed out
+ The SoundWire bus controller (host end) did not enumerate the amplifier.
+ In other words, the ACPI says there is an amplifier but for some reason
+ it was not detected on the bus.
+
+No AF01 node
+ Indicates an error in ACPI. A SoundWire system should have a Device()
+ node named "AF01" but it was not found.
+
+Failed to get spk-id-gpios
+ ACPI says that the driver should request a GPIO but the driver was not
+ able to get that GPIO. The most likely cause is that the kernel does not
+ include the correct GPIO or PINCTRL driver for this system.
+
+Failed to read spk-id
+ ACPI says that the driver should request a GPIO but the driver was not
+ able to read that GPIO.
+
+Unexpected spk-id element count
+ AF01 contains more speaker ID GPIO entries than the driver supports
+
+Overtemp error
+ Amplifier overheat protection was triggered and the amplifier shut down
+ to protect itself.
+
+Amp short error
+ Amplifier detected a short-circuit on the speaker output pins and shut
+ down for protection. This would normally indicate a damaged speaker.
+
+Hibernate wake failed
+ The driver tried to wake the amplifier from its power-saving state but
+ did not see the expected responses from the amplifier. This can be caused
+ by using firmware that does not match the hardware.
diff --git a/Documentation/sound/codecs/index.rst b/Documentation/sound/codecs/index.rst
new file mode 100644
index 000000000000..2cb95d87bbef
--- /dev/null
+++ b/Documentation/sound/codecs/index.rst
@@ -0,0 +1,9 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+Codec-Specific Information
+==========================
+
+.. toctree::
+ :maxdepth: 2
+
+ cs35l56
diff --git a/Documentation/sound/designs/midi-2.0.rst b/Documentation/sound/designs/midi-2.0.rst
index 086487ca7ab1..71a343c93fe7 100644
--- a/Documentation/sound/designs/midi-2.0.rst
+++ b/Documentation/sound/designs/midi-2.0.rst
@@ -293,6 +293,17 @@ Rawmidi API Extensions
status 0x05). When UMP core receives such a message, it updates the
UMP EP info and the corresponding sequencer clients as well.
+* The legacy rawmidi device number is found in the new `tied_device`
+ field of the rawmidi info.
+ On the other hand, the UMP rawmidi device number is found in
+ `tied_device` field of the legacy rawmidi info, too.
+
+* Each substream of the legacy rawmidi may be enabled / disabled
+ dynamically depending on the UMP FB state.
+ When the selected substream is inactive, it's indicated by the bit
+ 0x10 (`SNDRV_RAWMIDI_INFO_STREAM_INACTIVE`) in the `flags` field of
+ the legacy rawmidi info.
+
Control API Extensions
======================
@@ -377,6 +388,13 @@ Sequencer API Extensions
announcement to the ALSA sequencer system port, similarly like the
normal port change notification.
+* There are two extended event types for notifying the UMP Endpoint and
+ Function Block changes via the system announcement port:
+ type 68 (`SNDRV_SEQ_EVENT_UMP_EP_CHANGE`) and type 69
+ (`SNDRV_SEQ_EVENT_UMP_BLOCK_CHANGE`). They take the new type,
+ `snd_seq_ev_ump_notify` in the payload, indicating the client number
+ and the FB number that are changed.
+
MIDI2 USB Gadget Function Driver
================================
diff --git a/Documentation/sound/index.rst b/Documentation/sound/index.rst
index c437f2a4bc85..51cd736f65b5 100644
--- a/Documentation/sound/index.rst
+++ b/Documentation/sound/index.rst
@@ -13,6 +13,7 @@ Sound Subsystem Documentation
alsa-configuration
hd-audio/index
cards/index
+ codecs/index
utimers
.. only:: subproject and html
diff --git a/MAINTAINERS b/MAINTAINERS
index f2c085207c83..545ebac63f51 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5469,6 +5469,7 @@ L: linux-sound@vger.kernel.org
L: patches@opensource.cirrus.com
S: Maintained
F: Documentation/devicetree/bindings/sound/cirrus,cs*
+F: Documentation/sound/codecs/cs*
F: drivers/mfd/cs42l43*
F: drivers/pinctrl/cirrus/pinctrl-cs42l43*
F: drivers/spi/spi-cs42l43*
@@ -8951,14 +8952,16 @@ L: linux-input@vger.kernel.org
S: Maintained
F: drivers/input/joystick/fsia6b.c
-FOCUSRITE SCARLETT2 MIXER DRIVER (Scarlett Gen 2+ and Clarett)
+FOCUSRITE CONTROL PROTOCOL/SCARLETT2 MIXER DRIVERS (Scarlett Gen 2+, Clarett, and Vocaster)
M: Geoffrey D. Bennett <g@b4.vu>
L: linux-sound@vger.kernel.org
S: Maintained
-W: https://github.com/geoffreybennett/scarlett-gen2
-B: https://github.com/geoffreybennett/scarlett-gen2/issues
-T: git https://github.com/geoffreybennett/scarlett-gen2.git
+W: https://github.com/geoffreybennett/linux-fcp
+B: https://github.com/geoffreybennett/linux-fcp/issues
+T: git https://github.com/geoffreybennett/linux-fcp.git
+F: include/uapi/sound/fcp.h
F: include/uapi/sound/scarlett2.h
+F: sound/usb/fcp.c
F: sound/usb/mixer_scarlett2.c
FORCEDETH GIGABIT ETHERNET DRIVER
@@ -21993,6 +21996,7 @@ W: https://github.com/thesofproject/linux/
F: sound/soc/sof/
SOUND - GENERIC SOUND CARD (Simple-Audio-Card, Audio-Graph-Card)
+M: Mark Brown <broonie@kernel.org>
M: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
S: Supported
L: linux-sound@vger.kernel.org
diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c
index 74dcccdc6482..55a9a3d5afa8 100644
--- a/drivers/acpi/scan.c
+++ b/drivers/acpi/scan.c
@@ -1769,6 +1769,7 @@ static bool acpi_device_enumeration_by_parent(struct acpi_device *device)
{"CSC3557", },
{"INT33FE", },
{"INT3515", },
+ {"TXNW2781", },
/* Non-conforming _HID for Cirrus Logic already released */
{"CLSA0100", },
{"CLSA0101", },
diff --git a/drivers/platform/x86/serial-multi-instantiate.c b/drivers/platform/x86/serial-multi-instantiate.c
index ed6b28505cd6..db030b0f176a 100644
--- a/drivers/platform/x86/serial-multi-instantiate.c
+++ b/drivers/platform/x86/serial-multi-instantiate.c
@@ -384,6 +384,17 @@ static const struct smi_node cs35l57_hda = {
.bus_type = SMI_AUTO_DETECT,
};
+static const struct smi_node tas2781_hda = {
+ .instances = {
+ { "tas2781-hda", IRQ_RESOURCE_AUTO, 0 },
+ { "tas2781-hda", IRQ_RESOURCE_AUTO, 0 },
+ { "tas2781-hda", IRQ_RESOURCE_AUTO, 0 },
+ { "tas2781-hda", IRQ_RESOURCE_AUTO, 0 },
+ {}
+ },
+ .bus_type = SMI_AUTO_DETECT,
+};
+
/*
* Note new device-ids must also be added to ignore_serial_bus_ids in
* drivers/acpi/scan.c: acpi_device_enumeration_by_parent().
@@ -396,6 +407,7 @@ static const struct acpi_device_id smi_acpi_ids[] = {
{ "CSC3556", (unsigned long)&cs35l56_hda },
{ "CSC3557", (unsigned long)&cs35l57_hda },
{ "INT3515", (unsigned long)&int3515_data },
+ { "TXNW2781", (unsigned long)&tas2781_hda },
/* Non-conforming _HID for Cirrus Logic already released */
{ "CLSA0100", (unsigned long)&cs35l41_hda },
{ "CLSA0101", (unsigned long)&cs35l41_hda },
diff --git a/include/sound/hdaudio_ext.h b/include/sound/hdaudio_ext.h
index 957295364a5e..4c7a40e149a5 100644
--- a/include/sound/hdaudio_ext.h
+++ b/include/sound/hdaudio_ext.h
@@ -2,8 +2,6 @@
#ifndef __SOUND_HDAUDIO_EXT_H
#define __SOUND_HDAUDIO_EXT_H
-#include <linux/io-64-nonatomic-lo-hi.h>
-#include <linux/iopoll.h>
#include <sound/hdaudio.h>
int snd_hdac_ext_bus_init(struct hdac_bus *bus, struct device *dev,
@@ -119,49 +117,6 @@ int snd_hdac_ext_bus_link_put(struct hdac_bus *bus, struct hdac_ext_link *hlink)
void snd_hdac_ext_bus_link_power(struct hdac_device *codec, bool enable);
-#define snd_hdac_adsp_writeb(chip, reg, value) \
- snd_hdac_reg_writeb(chip, (chip)->dsp_ba + (reg), value)
-#define snd_hdac_adsp_readb(chip, reg) \
- snd_hdac_reg_readb(chip, (chip)->dsp_ba + (reg))
-#define snd_hdac_adsp_writew(chip, reg, value) \
- snd_hdac_reg_writew(chip, (chip)->dsp_ba + (reg), value)
-#define snd_hdac_adsp_readw(chip, reg) \
- snd_hdac_reg_readw(chip, (chip)->dsp_ba + (reg))
-#define snd_hdac_adsp_writel(chip, reg, value) \
- snd_hdac_reg_writel(chip, (chip)->dsp_ba + (reg), value)
-#define snd_hdac_adsp_readl(chip, reg) \
- snd_hdac_reg_readl(chip, (chip)->dsp_ba + (reg))
-#define snd_hdac_adsp_writeq(chip, reg, value) \
- snd_hdac_reg_writeq(chip, (chip)->dsp_ba + (reg), value)
-#define snd_hdac_adsp_readq(chip, reg) \
- snd_hdac_reg_readq(chip, (chip)->dsp_ba + (reg))
-
-#define snd_hdac_adsp_updateb(chip, reg, mask, val) \
- snd_hdac_adsp_writeb(chip, reg, \
- (snd_hdac_adsp_readb(chip, reg) & ~(mask)) | (val))
-#define snd_hdac_adsp_updatew(chip, reg, mask, val) \
- snd_hdac_adsp_writew(chip, reg, \
- (snd_hdac_adsp_readw(chip, reg) & ~(mask)) | (val))
-#define snd_hdac_adsp_updatel(chip, reg, mask, val) \
- snd_hdac_adsp_writel(chip, reg, \
- (snd_hdac_adsp_readl(chip, reg) & ~(mask)) | (val))
-#define snd_hdac_adsp_updateq(chip, reg, mask, val) \
- snd_hdac_adsp_writeq(chip, reg, \
- (snd_hdac_adsp_readq(chip, reg) & ~(mask)) | (val))
-
-#define snd_hdac_adsp_readb_poll(chip, reg, val, cond, delay_us, timeout_us) \
- readb_poll_timeout((chip)->dsp_ba + (reg), val, cond, \
- delay_us, timeout_us)
-#define snd_hdac_adsp_readw_poll(chip, reg, val, cond, delay_us, timeout_us) \
- readw_poll_timeout((chip)->dsp_ba + (reg), val, cond, \
- delay_us, timeout_us)
-#define snd_hdac_adsp_readl_poll(chip, reg, val, cond, delay_us, timeout_us) \
- readl_poll_timeout((chip)->dsp_ba + (reg), val, cond, \
- delay_us, timeout_us)
-#define snd_hdac_adsp_readq_poll(chip, reg, val, cond, delay_us, timeout_us) \
- readq_poll_timeout((chip)->dsp_ba + (reg), val, cond, \
- delay_us, timeout_us)
-
struct hdac_ext_device;
/* ops common to all codec drivers */
diff --git a/include/sound/pcm.h b/include/sound/pcm.h
index 67c99ffbf51b..8becb4504887 100644
--- a/include/sound/pcm.h
+++ b/include/sound/pcm.h
@@ -1532,9 +1532,10 @@ static inline u64 pcm_format_to_bits(snd_pcm_format_t pcm_format)
dev_dbg((pcm)->card->dev, fmt, ##args)
/* helpers for copying between iov_iter and iomem */
-int copy_to_iter_fromio(struct iov_iter *itert, const void __iomem *src,
- size_t count);
-int copy_from_iter_toio(void __iomem *dst, struct iov_iter *iter, size_t count);
+size_t copy_to_iter_fromio(const void __iomem *src, size_t bytes,
+ struct iov_iter *iter) __must_check;
+size_t copy_from_iter_toio(void __iomem *dst, size_t bytes,
+ struct iov_iter *iter) __must_check;
struct snd_pcm_status64 {
snd_pcm_state_t state; /* stream state */
diff --git a/include/sound/rawmidi.h b/include/sound/rawmidi.h
index f31cabf0158c..6916f7133597 100644
--- a/include/sound/rawmidi.h
+++ b/include/sound/rawmidi.h
@@ -89,6 +89,7 @@ struct snd_rawmidi_substream {
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) */
+ bool inactive; /* inactive substream (for UMP legacy) */
size_t bytes;
spinlock_t lock;
struct snd_rawmidi *rmidi;
@@ -118,6 +119,7 @@ struct snd_rawmidi {
struct list_head list;
unsigned int device; /* device number */
unsigned int info_flags; /* SNDRV_RAWMIDI_INFO_XXXX */
+ unsigned int tied_device;
char id[64];
char name[80];
@@ -189,4 +191,13 @@ long snd_rawmidi_kernel_read(struct snd_rawmidi_substream *substream,
long snd_rawmidi_kernel_write(struct snd_rawmidi_substream *substream,
const unsigned char *buf, long count);
+/* set up the tied devices */
+static inline void snd_rawmidi_tie_devices(struct snd_rawmidi *r1,
+ struct snd_rawmidi *r2)
+{
+ /* tied_device field keeps the device+1 (so that 0 being unknown) */
+ r1->tied_device = r2->device + 1;
+ r2->tied_device = r1->device + 1;
+}
+
#endif /* __SOUND_RAWMIDI_H */
diff --git a/include/sound/simple_card_utils.h b/include/sound/simple_card_utils.h
index f6bfd485c31a..892f70532363 100644
--- a/include/sound/simple_card_utils.h
+++ b/include/sound/simple_card_utils.h
@@ -89,6 +89,13 @@ struct simple_util_priv {
#define simple_props_to_dai_codec(props, i) ((props)->codec_dai + i)
#define simple_props_to_codec_conf(props, i) ((props)->codec_conf + i)
+/* has the same effect as simple_priv_to_props(). Preferred over
+ * simple_priv_to_props() when dealing with PCM runtime data as
+ * the ID stored in rtd->id may not be a valid array index.
+ */
+#define runtime_simple_priv_to_props(priv, rtd) \
+ ((priv)->dai_props + ((rtd)->dai_link - (priv)->dai_link))
+
#define for_each_prop_dlc_cpus(props, i, cpu) \
for ((i) = 0; \
((i) < (props)->num.cpus) && \
diff --git a/include/sound/ump.h b/include/sound/ump.h
index 532c2c3ea28e..73f97f88e2ed 100644
--- a/include/sound/ump.h
+++ b/include/sound/ump.h
@@ -83,6 +83,7 @@ struct snd_ump_ops {
struct snd_seq_ump_ops {
void (*input_receive)(struct snd_ump_endpoint *ump,
const u32 *data, int words);
+ int (*notify_ep_change)(struct snd_ump_endpoint *ump);
int (*notify_fb_change)(struct snd_ump_endpoint *ump,
struct snd_ump_block *fb);
int (*switch_protocol)(struct snd_ump_endpoint *ump);
diff --git a/include/uapi/sound/asequencer.h b/include/uapi/sound/asequencer.h
index bc30c1f2a109..a5c41f771e05 100644
--- a/include/uapi/sound/asequencer.h
+++ b/include/uapi/sound/asequencer.h
@@ -10,7 +10,7 @@
#include <sound/asound.h>
/** version of the sequencer */
-#define SNDRV_SEQ_VERSION SNDRV_PROTOCOL_VERSION(1, 0, 4)
+#define SNDRV_SEQ_VERSION SNDRV_PROTOCOL_VERSION(1, 0, 5)
/**
* definition of sequencer event types
@@ -92,6 +92,9 @@
#define SNDRV_SEQ_EVENT_PORT_SUBSCRIBED 66 /* ports connected */
#define SNDRV_SEQ_EVENT_PORT_UNSUBSCRIBED 67 /* ports disconnected */
+#define SNDRV_SEQ_EVENT_UMP_EP_CHANGE 68 /* UMP EP info has changed */
+#define SNDRV_SEQ_EVENT_UMP_BLOCK_CHANGE 69 /* UMP block info has changed */
+
/* 70-89: synthesizer events - obsoleted */
/** user-defined events with fixed length
@@ -253,6 +256,12 @@ struct snd_seq_ev_quote {
struct snd_seq_event *event; /* quoted event */
} __packed;
+ /* UMP info change notify */
+struct snd_seq_ev_ump_notify {
+ unsigned char client; /**< Client number */
+ unsigned char block; /**< Block number (optional) */
+};
+
union snd_seq_event_data { /* event data... */
struct snd_seq_ev_note note;
struct snd_seq_ev_ctrl control;
@@ -265,6 +274,7 @@ union snd_seq_event_data { /* event data... */
struct snd_seq_connect connect;
struct snd_seq_result result;
struct snd_seq_ev_quote quote;
+ struct snd_seq_ev_ump_notify ump_notify;
};
/* sequencer event */
diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h
index 4cd513215bcd..5a049eeaecce 100644
--- a/include/uapi/sound/asound.h
+++ b/include/uapi/sound/asound.h
@@ -716,7 +716,7 @@ enum {
* Raw MIDI section - /dev/snd/midi??
*/
-#define SNDRV_RAWMIDI_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 4)
+#define SNDRV_RAWMIDI_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 5)
enum {
SNDRV_RAWMIDI_STREAM_OUTPUT = 0,
@@ -728,6 +728,9 @@ enum {
#define SNDRV_RAWMIDI_INFO_INPUT 0x00000002
#define SNDRV_RAWMIDI_INFO_DUPLEX 0x00000004
#define SNDRV_RAWMIDI_INFO_UMP 0x00000008
+#define SNDRV_RAWMIDI_INFO_STREAM_INACTIVE 0x00000010
+
+#define SNDRV_RAWMIDI_DEVICE_UNKNOWN 0
struct snd_rawmidi_info {
unsigned int device; /* RO/WR (control): device number */
@@ -740,7 +743,8 @@ struct snd_rawmidi_info {
unsigned char subname[32]; /* name of active or selected subdevice */
unsigned int subdevices_count;
unsigned int subdevices_avail;
- unsigned char reserved[64]; /* reserved for future use */
+ int tied_device; /* R: tied rawmidi device (UMP/legacy) */
+ unsigned char reserved[60]; /* reserved for future use */
};
#define SNDRV_RAWMIDI_MODE_FRAMING_MASK (7<<0)
diff --git a/include/uapi/sound/fcp.h b/include/uapi/sound/fcp.h
new file mode 100644
index 000000000000..aea428956d8f
--- /dev/null
+++ b/include/uapi/sound/fcp.h
@@ -0,0 +1,120 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * Focusrite Control Protocol Driver for ALSA
+ *
+ * Copyright (c) 2024-2025 by Geoffrey D. Bennett <g at b4.vu>
+ */
+/*
+ * DOC: FCP (Focusrite Control Protocol) User-Space API
+ *
+ * This header defines the interface between the FCP kernel driver and
+ * user-space programs to enable the use of the proprietary features
+ * available in Focusrite USB audio interfaces. This includes Scarlett
+ * 2nd Gen, 3rd Gen, 4th Gen, Clarett USB, Clarett+, and Vocaster
+ * series devices.
+ *
+ * The interface is provided via ALSA's hwdep interface. Opening the
+ * hwdep device requires CAP_SYS_RAWIO privileges as this interface
+ * provides near-direct access.
+ *
+ * For details on the FCP protocol, refer to the kernel scarlett2
+ * driver in sound/usb/mixer_scarlett2.c and the fcp-support project
+ * at https://github.com/geoffreybennett/fcp-support
+ *
+ * For examples of using these IOCTLs, see the fcp-server source in
+ * the fcp-support project.
+ *
+ * IOCTL Interface
+ * --------------
+ * FCP_IOCTL_PVERSION:
+ * Returns the protocol version supported by the driver.
+ *
+ * FCP_IOCTL_INIT:
+ * Initialises the protocol and synchronises sequence numbers
+ * between the driver and device. Must be called at least once
+ * before sending commands. Can be safely called again at any time.
+ *
+ * FCP_IOCTL_CMD:
+ * Sends an FCP command to the device and returns the response.
+ * Requires prior initialisation via FCP_IOCTL_INIT.
+ *
+ * FCP_IOCTL_SET_METER_MAP:
+ * Configures the Level Meter control's mapping between device
+ * meters and control channels. Requires FCP_IOCTL_INIT to have been
+ * called first. The map size and number of slots cannot be changed
+ * after initial configuration, although the map itself can be
+ * updated. Once configured, the Level Meter remains functional even
+ * after the hwdep device is closed.
+ *
+ * FCP_IOCTL_SET_METER_LABELS:
+ * Set the labels for the Level Meter control. Requires
+ * FCP_IOCTL_SET_METER_MAP to have been called first. labels[]
+ * should contain a sequence of null-terminated labels corresponding
+ * to the control's channels.
+ */
+#ifndef __UAPI_SOUND_FCP_H
+#define __UAPI_SOUND_FCP_H
+
+#include <linux/types.h>
+#include <linux/ioctl.h>
+
+#define FCP_HWDEP_MAJOR 2
+#define FCP_HWDEP_MINOR 0
+#define FCP_HWDEP_SUBMINOR 0
+
+#define FCP_HWDEP_VERSION \
+ ((FCP_HWDEP_MAJOR << 16) | \
+ (FCP_HWDEP_MINOR << 8) | \
+ FCP_HWDEP_SUBMINOR)
+
+#define FCP_HWDEP_VERSION_MAJOR(v) (((v) >> 16) & 0xFF)
+#define FCP_HWDEP_VERSION_MINOR(v) (((v) >> 8) & 0xFF)
+#define FCP_HWDEP_VERSION_SUBMINOR(v) ((v) & 0xFF)
+
+/* Get protocol version */
+#define FCP_IOCTL_PVERSION _IOR('S', 0x60, int)
+
+/* Start the protocol */
+
+/* Step 0 and step 2 responses are variable length and placed in
+ * resp[] one after the other.
+ */
+struct fcp_init {
+ __u16 step0_resp_size;
+ __u16 step2_resp_size;
+ __u32 init1_opcode;
+ __u32 init2_opcode;
+ __u8 resp[];
+} __attribute__((packed));
+
+#define FCP_IOCTL_INIT _IOWR('S', 0x64, struct fcp_init)
+
+/* Perform a command */
+
+/* The request data is placed in data[] and the response data will
+ * overwrite it.
+ */
+struct fcp_cmd {
+ __u32 opcode;
+ __u16 req_size;
+ __u16 resp_size;
+ __u8 data[];
+} __attribute__((packed));
+#define FCP_IOCTL_CMD _IOWR('S', 0x65, struct fcp_cmd)
+
+/* Set the meter map */
+struct fcp_meter_map {
+ __u16 map_size;
+ __u16 meter_slots;
+ __s16 map[];
+} __attribute__((packed));
+#define FCP_IOCTL_SET_METER_MAP _IOW('S', 0x66, struct fcp_meter_map)
+
+/* Set the meter labels */
+struct fcp_meter_labels {
+ __u16 labels_size;
+ char labels[];
+} __attribute__((packed));
+#define FCP_IOCTL_SET_METER_LABELS _IOW('S', 0x67, struct fcp_meter_labels)
+
+#endif /* __UAPI_SOUND_FCP_H */
diff --git a/include/uapi/sound/tlv.h b/include/uapi/sound/tlv.h
index b99a2414b53d..5bb55c80e095 100644
--- a/include/uapi/sound/tlv.h
+++ b/include/uapi/sound/tlv.h
@@ -18,6 +18,8 @@
#define SNDRV_CTL_TLVT_CHMAP_VAR 0x102 /* channels freely swappable */
#define SNDRV_CTL_TLVT_CHMAP_PAIRED 0x103 /* pair-wise swappable */
+#define SNDRV_CTL_TLVT_FCP_CHANNEL_LABELS 0x110 /* channel labels */
+
/*
* TLV structure is right behind the struct snd_ctl_tlv:
* unsigned int type - see SNDRV_CTL_TLVT_*
diff --git a/sound/core/memory.c b/sound/core/memory.c
index 2d2d0094c897..d683442b4c97 100644
--- a/sound/core/memory.c
+++ b/sound/core/memory.c
@@ -27,38 +27,43 @@ int copy_to_user_fromio(void __user *dst, const volatile void __iomem *src, size
if (import_ubuf(ITER_DEST, dst, count, &iter))
return -EFAULT;
- return copy_to_iter_fromio(&iter, (const void __iomem *)src, count);
+ if (copy_to_iter_fromio((const void __iomem *)src, count, &iter) != count)
+ return -EFAULT;
+ return 0;
}
EXPORT_SYMBOL(copy_to_user_fromio);
/**
* copy_to_iter_fromio - copy data from mmio-space to iov_iter
- * @dst: the destination iov_iter
* @src: the source pointer on mmio
* @count: the data size to copy in bytes
+ * @dst: the destination iov_iter
*
* Copies the data from mmio-space to iov_iter.
*
- * Return: Zero if successful, or non-zero on failure.
+ * Return: number of bytes to be copied
*/
-int copy_to_iter_fromio(struct iov_iter *dst, const void __iomem *src,
- size_t count)
+size_t copy_to_iter_fromio(const void __iomem *src, size_t count,
+ struct iov_iter *dst)
{
#if defined(__i386__) || defined(CONFIG_SPARC32)
- return copy_to_iter((const void __force *)src, count, dst) == count ? 0 : -EFAULT;
+ return copy_to_iter((const void __force *)src, count, dst);
#else
char buf[256];
+ size_t res = 0;
+
while (count) {
size_t c = count;
if (c > sizeof(buf))
c = sizeof(buf);
memcpy_fromio(buf, (void __iomem *)src, c);
if (copy_to_iter(buf, c, dst) != c)
- return -EFAULT;
+ return res;
count -= c;
src += c;
+ res += c;
}
- return 0;
+ return res;
#endif
}
EXPORT_SYMBOL(copy_to_iter_fromio);
@@ -79,37 +84,43 @@ int copy_from_user_toio(volatile void __iomem *dst, const void __user *src, size
if (import_ubuf(ITER_SOURCE, (void __user *)src, count, &iter))
return -EFAULT;
- return copy_from_iter_toio((void __iomem *)dst, &iter, count);
+ if (copy_from_iter_toio((void __iomem *)dst, count, &iter) != count)
+ return -EFAULT;
+ return 0;
}
EXPORT_SYMBOL(copy_from_user_toio);
/**
* copy_from_iter_toio - copy data from iov_iter to mmio-space
* @dst: the destination pointer on mmio-space
- * @src: the source iov_iter
* @count: the data size to copy in bytes
+ * @src: the source iov_iter
*
* Copies the data from iov_iter to mmio-space.
*
- * Return: Zero if successful, or non-zero on failure.
+ * Return: number of bytes to be copied
*/
-int copy_from_iter_toio(void __iomem *dst, struct iov_iter *src, size_t count)
+size_t copy_from_iter_toio(void __iomem *dst, size_t count,
+ struct iov_iter *src)
{
#if defined(__i386__) || defined(CONFIG_SPARC32)
- return copy_from_iter((void __force *)dst, count, src) == count ? 0 : -EFAULT;
+ return copy_from_iter((void __force *)dst, count, src);
#else
char buf[256];
+ size_t res = 0;
+
while (count) {
size_t c = count;
if (c > sizeof(buf))
c = sizeof(buf);
if (copy_from_iter(buf, c, src) != c)
- return -EFAULT;
+ return res;
memcpy_toio(dst, buf, c);
count -= c;
dst += c;
+ res += c;
}
- return 0;
+ return res;
#endif
}
EXPORT_SYMBOL(copy_from_iter_toio);
diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c
index 348ce1b7725e..70a958ac1112 100644
--- a/sound/core/rawmidi.c
+++ b/sound/core/rawmidi.c
@@ -629,12 +629,15 @@ static int snd_rawmidi_info(struct snd_rawmidi_substream *substream,
info->subdevice = substream->number;
info->stream = substream->stream;
info->flags = rmidi->info_flags;
+ if (substream->inactive)
+ info->flags |= SNDRV_RAWMIDI_INFO_STREAM_INACTIVE;
strcpy(info->id, rmidi->id);
strcpy(info->name, rmidi->name);
strcpy(info->subname, substream->name);
info->subdevices_count = substream->pstr->substream_count;
info->subdevices_avail = (substream->pstr->substream_count -
substream->pstr->substream_opened);
+ info->tied_device = rmidi->tied_device;
return 0;
}
diff --git a/sound/core/seq/Kconfig b/sound/core/seq/Kconfig
index 0374bbf51cd4..e4f58cb985d4 100644
--- a/sound/core/seq/Kconfig
+++ b/sound/core/seq/Kconfig
@@ -62,7 +62,7 @@ config SND_SEQ_VIRMIDI
config SND_SEQ_UMP
bool "Support for UMP events"
- default y if SND_SEQ_UMP_CLIENT
+ default SND_UMP
help
Say Y here to enable the support for handling UMP (Universal MIDI
Packet) events via ALSA sequencer infrastructure, which is an
@@ -71,6 +71,6 @@ config SND_SEQ_UMP
among legacy and UMP clients.
config SND_SEQ_UMP_CLIENT
- def_tristate SND_UMP
+ def_tristate SND_UMP && SND_SEQ_UMP
endif # SND_SEQUENCER
diff --git a/sound/core/seq/oss/seq_oss_device.h b/sound/core/seq/oss/seq_oss_device.h
index 98dd20b42976..6163a00bc8de 100644
--- a/sound/core/seq/oss/seq_oss_device.h
+++ b/sound/core/seq/oss/seq_oss_device.h
@@ -55,7 +55,6 @@ struct seq_oss_chinfo {
struct seq_oss_synthinfo {
struct snd_seq_oss_arg arg;
struct seq_oss_chinfo *ch;
- struct seq_oss_synth_sysex *sysex;
int nr_voices;
int opened;
int is_midi;
@@ -157,8 +156,4 @@ snd_seq_oss_fill_addr(struct seq_oss_devinfo *dp, struct snd_seq_event *ev,
ev->dest.port = dest_port;
}
-
-/* misc. functions for proc interface */
-char *enabled_str(bool b);
-
#endif /* __SEQ_OSS_DEVICE_H */
diff --git a/sound/core/seq/oss/seq_oss_init.c b/sound/core/seq/oss/seq_oss_init.c
index 676246fa02f1..e6d7d83ed0e7 100644
--- a/sound/core/seq/oss/seq_oss_init.c
+++ b/sound/core/seq/oss/seq_oss_init.c
@@ -111,7 +111,7 @@ snd_seq_oss_create_client(void)
/*
- * receive annoucement from system port, and check the midi device
+ * receive announcement from system port, and check the midi device
*/
static int
receive_announce(struct snd_seq_event *ev, int direct, void *private, int atomic, int hop)
@@ -449,12 +449,6 @@ snd_seq_oss_reset(struct seq_oss_devinfo *dp)
/*
* misc. functions for proc interface
*/
-char *
-enabled_str(bool b)
-{
- return b ? "enabled" : "disabled";
-}
-
static const char *
filemode_str(int val)
{
diff --git a/sound/core/seq/oss/seq_oss_synth.c b/sound/core/seq/oss/seq_oss_synth.c
index 51ee4c00a843..9de47e098b29 100644
--- a/sound/core/seq/oss/seq_oss_synth.c
+++ b/sound/core/seq/oss/seq_oss_synth.c
@@ -26,13 +26,6 @@
* definition of synth info records
*/
-/* sysex buffer */
-struct seq_oss_synth_sysex {
- int len;
- int skip;
- unsigned char buf[MAX_SYSEX_BUFLEN];
-};
-
/* synth info */
struct seq_oss_synth {
int seq_device;
@@ -66,7 +59,6 @@ static struct seq_oss_synth midi_synth_dev = {
};
static DEFINE_SPINLOCK(register_lock);
-static DEFINE_MUTEX(sysex_mutex);
/*
* prototypes
@@ -319,8 +311,6 @@ snd_seq_oss_synth_cleanup(struct seq_oss_devinfo *dp)
}
snd_use_lock_free(&rec->use_lock);
}
- kfree(info->sysex);
- info->sysex = NULL;
kfree(info->ch);
info->ch = NULL;
}
@@ -396,8 +386,6 @@ snd_seq_oss_synth_reset(struct seq_oss_devinfo *dp, int dev)
info = get_synthinfo_nospec(dp, dev);
if (!info || !info->opened)
return;
- if (info->sysex)
- info->sysex->len = 0; /* reset sysex */
reset_channels(info);
if (info->is_midi) {
if (midi_synth_dev.opened <= 0)
@@ -409,8 +397,6 @@ snd_seq_oss_synth_reset(struct seq_oss_devinfo *dp, int dev)
dp->file_mode) < 0) {
midi_synth_dev.opened--;
info->opened = 0;
- kfree(info->sysex);
- info->sysex = NULL;
kfree(info->ch);
info->ch = NULL;
}
@@ -483,64 +469,26 @@ snd_seq_oss_synth_info(struct seq_oss_devinfo *dp, int dev)
/*
* receive OSS 6 byte sysex packet:
- * the full sysex message will be sent if it reaches to the end of data
- * (0xff).
+ * the event is filled and prepared for sending immediately
+ * (i.e. sysex messages are fragmented)
*/
int
snd_seq_oss_synth_sysex(struct seq_oss_devinfo *dp, int dev, unsigned char *buf, struct snd_seq_event *ev)
{
- int i, send;
- unsigned char *dest;
- struct seq_oss_synth_sysex *sysex;
- struct seq_oss_synthinfo *info;
+ unsigned char *p;
+ int len = 6;
- info = snd_seq_oss_synth_info(dp, dev);
- if (!info)
- return -ENXIO;
+ p = memchr(buf, 0xff, 6);
+ if (p)
+ len = p - buf + 1;
- guard(mutex)(&sysex_mutex);
- sysex = info->sysex;
- if (sysex == NULL) {
- sysex = kzalloc(sizeof(*sysex), GFP_KERNEL);
- if (sysex == NULL)
- return -ENOMEM;
- info->sysex = sysex;
- }
-
- send = 0;
- dest = sysex->buf + sysex->len;
- /* copy 6 byte packet to the buffer */
- for (i = 0; i < 6; i++) {
- if (buf[i] == 0xff) {
- send = 1;
- break;
- }
- dest[i] = buf[i];
- sysex->len++;
- if (sysex->len >= MAX_SYSEX_BUFLEN) {
- sysex->len = 0;
- sysex->skip = 1;
- break;
- }
- }
-
- if (sysex->len && send) {
- if (sysex->skip) {
- sysex->skip = 0;
- sysex->len = 0;
- return -EINVAL; /* skip */
- }
- /* copy the data to event record and send it */
- ev->flags = SNDRV_SEQ_EVENT_LENGTH_VARIABLE;
- if (snd_seq_oss_synth_addr(dp, dev, ev))
- return -EINVAL;
- ev->data.ext.len = sysex->len;
- ev->data.ext.ptr = sysex->buf;
- sysex->len = 0;
- return 0;
- }
-
- return -EINVAL; /* skip */
+ /* copy the data to event record and send it */
+ if (snd_seq_oss_synth_addr(dp, dev, ev))
+ return -EINVAL;
+ ev->flags = SNDRV_SEQ_EVENT_LENGTH_VARIABLE;
+ ev->data.ext.len = len;
+ ev->data.ext.ptr = buf;
+ return 0;
}
/*
@@ -660,8 +608,8 @@ snd_seq_oss_synth_info_read(struct snd_info_buffer *buf)
rec->synth_type, rec->synth_subtype,
rec->nr_voices);
snd_iprintf(buf, " capabilities : ioctl %s / load_patch %s\n",
- enabled_str((long)rec->oper.ioctl),
- enabled_str((long)rec->oper.load_patch));
+ str_enabled_disabled((long)rec->oper.ioctl),
+ str_enabled_disabled((long)rec->oper.load_patch));
snd_use_lock_free(&rec->use_lock);
}
}
diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c
index 77b6ac9b5c11..073b56dc2225 100644
--- a/sound/core/seq/seq_clientmgr.c
+++ b/sound/core/seq/seq_clientmgr.c
@@ -1296,6 +1296,10 @@ static int snd_seq_ioctl_set_client_info(struct snd_seq_client *client,
client->midi_version = client_info->midi_version;
memcpy(client->event_filter, client_info->event_filter, 32);
client->group_filter = client_info->group_filter;
+
+ /* notify the change */
+ snd_seq_system_client_ev_client_change(client->number);
+
return 0;
}
@@ -1419,6 +1423,9 @@ static int snd_seq_ioctl_set_port_info(struct snd_seq_client *client, void *arg)
if (port) {
snd_seq_set_port_info(port, info);
snd_seq_port_unlock(port);
+ /* notify the change */
+ snd_seq_system_client_ev_port_change(info->addr.client,
+ info->addr.port);
}
return 0;
}
@@ -1475,7 +1482,7 @@ int snd_seq_client_notify_subscription(int client, int port,
event.data.connect.dest = info->dest;
event.data.connect.sender = info->sender;
- return snd_seq_system_notify(client, port, &event); /* non-atomic */
+ return snd_seq_system_notify(client, port, &event, false); /* non-atomic */
}
@@ -2229,6 +2236,16 @@ static int snd_seq_ioctl_client_ump_info(struct snd_seq_client *caller,
error:
mutex_unlock(&cptr->ioctl_mutex);
snd_seq_client_unlock(cptr);
+ if (!err && cmd == SNDRV_SEQ_IOCTL_SET_CLIENT_UMP_INFO) {
+ if (type == SNDRV_SEQ_CLIENT_UMP_INFO_ENDPOINT)
+ snd_seq_system_ump_notify(client, 0,
+ SNDRV_SEQ_EVENT_UMP_EP_CHANGE,
+ false);
+ else
+ snd_seq_system_ump_notify(client, type - 1,
+ SNDRV_SEQ_EVENT_UMP_BLOCK_CHANGE,
+ false);
+ }
return err;
}
#endif
diff --git a/sound/core/seq/seq_system.c b/sound/core/seq/seq_system.c
index 80267290190d..853920f79016 100644
--- a/sound/core/seq/seq_system.c
+++ b/sound/core/seq/seq_system.c
@@ -49,12 +49,14 @@ static int sysclient = -1;
/* port id numbers for this client */
static int announce_port = -1;
+/* number of subscriptions to announce port */
+static int announce_subscribed;
/* fill standard header data, source port & channel are filled in */
static int setheader(struct snd_seq_event * ev, int client, int port)
{
- if (announce_port < 0)
+ if (announce_port < 0 || !announce_subscribed)
return -ENODEV;
memset(ev, 0, sizeof(struct snd_seq_event));
@@ -76,26 +78,27 @@ static int setheader(struct snd_seq_event * ev, int client, int port)
/* entry points for broadcasting system events */
-void snd_seq_system_broadcast(int client, int port, int type)
+void snd_seq_system_broadcast(int client, int port, int type, bool atomic)
{
struct snd_seq_event ev;
if (setheader(&ev, client, port) < 0)
return;
ev.type = type;
- snd_seq_kernel_client_dispatch(sysclient, &ev, 0, 0);
+ snd_seq_kernel_client_dispatch(sysclient, &ev, atomic, 0);
}
EXPORT_SYMBOL_GPL(snd_seq_system_broadcast);
/* entry points for broadcasting system events */
-int snd_seq_system_notify(int client, int port, struct snd_seq_event *ev)
+int snd_seq_system_notify(int client, int port, struct snd_seq_event *ev,
+ bool atomic)
{
ev->flags = SNDRV_SEQ_EVENT_LENGTH_FIXED;
ev->source.client = sysclient;
ev->source.port = announce_port;
ev->dest.client = client;
ev->dest.port = port;
- return snd_seq_kernel_client_dispatch(sysclient, ev, 0, 0);
+ return snd_seq_kernel_client_dispatch(sysclient, ev, atomic, 0);
}
/* call-back handler for timer events */
@@ -104,6 +107,22 @@ static int event_input_timer(struct snd_seq_event * ev, int direct, void *privat
return snd_seq_control_queue(ev, atomic, hop);
}
+static int sys_announce_subscribe(void *private_data,
+ struct snd_seq_port_subscribe *info)
+{
+ announce_subscribed++;
+ return 0;
+}
+
+static int sys_announce_unsubscribe(void *private_data,
+ struct snd_seq_port_subscribe *info)
+{
+ if (snd_BUG_ON(!announce_subscribed))
+ return 0;
+ announce_subscribed--;
+ return 0;
+}
+
/* register our internal client */
int __init snd_seq_system_client_init(void)
{
@@ -143,7 +162,10 @@ int __init snd_seq_system_client_init(void)
/* register announcement port */
strcpy(port->name, "Announce");
port->capability = SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_SUBS_READ; /* for broadcast only */
- port->kernel = NULL;
+ pcallbacks.event_input = NULL;
+ pcallbacks.subscribe = sys_announce_subscribe;
+ pcallbacks.unsubscribe = sys_announce_unsubscribe;
+ port->kernel = &pcallbacks;
port->type = 0;
port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT;
port->addr.client = sysclient;
diff --git a/sound/core/seq/seq_system.h b/sound/core/seq/seq_system.h
index 4fe88ad40346..62e513f74871 100644
--- a/sound/core/seq/seq_system.h
+++ b/sound/core/seq/seq_system.h
@@ -10,16 +10,31 @@
/* entry points for broadcasting system events */
-void snd_seq_system_broadcast(int client, int port, int type);
-
-#define snd_seq_system_client_ev_client_start(client) snd_seq_system_broadcast(client, 0, SNDRV_SEQ_EVENT_CLIENT_START)
-#define snd_seq_system_client_ev_client_exit(client) snd_seq_system_broadcast(client, 0, SNDRV_SEQ_EVENT_CLIENT_EXIT)
-#define snd_seq_system_client_ev_client_change(client) snd_seq_system_broadcast(client, 0, SNDRV_SEQ_EVENT_CLIENT_CHANGE)
-#define snd_seq_system_client_ev_port_start(client, port) snd_seq_system_broadcast(client, port, SNDRV_SEQ_EVENT_PORT_START)
-#define snd_seq_system_client_ev_port_exit(client, port) snd_seq_system_broadcast(client, port, SNDRV_SEQ_EVENT_PORT_EXIT)
-#define snd_seq_system_client_ev_port_change(client, port) snd_seq_system_broadcast(client, port, SNDRV_SEQ_EVENT_PORT_CHANGE)
-
-int snd_seq_system_notify(int client, int port, struct snd_seq_event *ev);
+void snd_seq_system_broadcast(int client, int port, int type, bool atomic);
+
+/* normal system notification event broadcast */
+#define notify_event(client, port, type) \
+ snd_seq_system_broadcast(client, port, type, false)
+
+/* notify UMP EP/FB change event */
+static inline void snd_seq_system_ump_notify(int client, int block, int type,
+ bool atomic)
+{
+ /* reuse the existing snd_seq_system_broadcast():
+ * struct snd_seq_ev_ump_notify is compatible with struct snd_seq_addr
+ */
+ snd_seq_system_broadcast(client, block, type, atomic);
+}
+
+#define snd_seq_system_client_ev_client_start(client) notify_event(client, 0, SNDRV_SEQ_EVENT_CLIENT_START)
+#define snd_seq_system_client_ev_client_exit(client) notify_event(client, 0, SNDRV_SEQ_EVENT_CLIENT_EXIT)
+#define snd_seq_system_client_ev_client_change(client) notify_event(client, 0, SNDRV_SEQ_EVENT_CLIENT_CHANGE)
+#define snd_seq_system_client_ev_port_start(client, port) notify_event(client, port, SNDRV_SEQ_EVENT_PORT_START)
+#define snd_seq_system_client_ev_port_exit(client, port) notify_event(client, port, SNDRV_SEQ_EVENT_PORT_EXIT)
+#define snd_seq_system_client_ev_port_change(client, port) notify_event(client, port, SNDRV_SEQ_EVENT_PORT_CHANGE)
+
+int snd_seq_system_notify(int client, int port, struct snd_seq_event *ev,
+ bool atomic);
/* register our internal client */
int snd_seq_system_client_init(void);
diff --git a/sound/core/seq/seq_ump_client.c b/sound/core/seq/seq_ump_client.c
index e956f17f3792..1255351b59ce 100644
--- a/sound/core/seq/seq_ump_client.c
+++ b/sound/core/seq/seq_ump_client.c
@@ -272,8 +272,6 @@ static void update_port_infos(struct seq_ump_client *client)
new);
if (err < 0)
continue;
- /* notify to system port */
- snd_seq_system_client_ev_port_change(client->seq_client, i);
}
}
@@ -390,6 +388,33 @@ static void handle_group_notify(struct work_struct *work)
setup_client_group_filter(client);
}
+/* UMP EP change notification */
+static int seq_ump_notify_ep_change(struct snd_ump_endpoint *ump)
+{
+ struct seq_ump_client *client = ump->seq_client;
+ struct snd_seq_client *cptr;
+ int client_id;
+
+ if (!client)
+ return -ENODEV;
+ client_id = client->seq_client;
+ cptr = snd_seq_kernel_client_get(client_id);
+ if (!cptr)
+ return -ENODEV;
+
+ snd_seq_system_ump_notify(client_id, 0, SNDRV_SEQ_EVENT_UMP_EP_CHANGE,
+ true);
+
+ /* update sequencer client name if needed */
+ if (*ump->core.name && strcmp(ump->core.name, cptr->name)) {
+ strscpy(cptr->name, ump->core.name, sizeof(cptr->name));
+ snd_seq_system_client_ev_client_change(client_id);
+ }
+
+ snd_seq_kernel_client_put(cptr);
+ return 0;
+}
+
/* UMP FB change notification */
static int seq_ump_notify_fb_change(struct snd_ump_endpoint *ump,
struct snd_ump_block *fb)
@@ -399,20 +424,29 @@ static int seq_ump_notify_fb_change(struct snd_ump_endpoint *ump,
if (!client)
return -ENODEV;
schedule_work(&client->group_notify_work);
+ snd_seq_system_ump_notify(client->seq_client, fb->info.block_id,
+ SNDRV_SEQ_EVENT_UMP_BLOCK_CHANGE,
+ true);
return 0;
}
/* UMP protocol change notification; just update the midi_version field */
static int seq_ump_switch_protocol(struct snd_ump_endpoint *ump)
{
- if (!ump->seq_client)
+ struct seq_ump_client *client = ump->seq_client;
+
+ if (!client)
return -ENODEV;
- setup_client_midi_version(ump->seq_client);
+ setup_client_midi_version(client);
+ snd_seq_system_ump_notify(client->seq_client, 0,
+ SNDRV_SEQ_EVENT_UMP_EP_CHANGE,
+ true);
return 0;
}
static const struct snd_seq_ump_ops seq_ump_ops = {
.input_receive = seq_ump_input_receive,
+ .notify_ep_change = seq_ump_notify_ep_change,
.notify_fb_change = seq_ump_notify_fb_change,
.switch_protocol = seq_ump_switch_protocol,
};
diff --git a/sound/core/ump.c b/sound/core/ump.c
index 9198bff4768c..8d8681a42ca5 100644
--- a/sound/core/ump.c
+++ b/sound/core/ump.c
@@ -37,6 +37,7 @@ static int process_legacy_output(struct snd_ump_endpoint *ump,
u32 *buffer, int count);
static void process_legacy_input(struct snd_ump_endpoint *ump, const u32 *src,
int words);
+static void ump_legacy_set_rawmidi_name(struct snd_ump_endpoint *ump);
static void update_legacy_names(struct snd_ump_endpoint *ump);
#else
static inline int process_legacy_output(struct snd_ump_endpoint *ump,
@@ -48,11 +49,42 @@ static inline void process_legacy_input(struct snd_ump_endpoint *ump,
const u32 *src, int words)
{
}
+static inline void ump_legacy_set_rawmidi_name(struct snd_ump_endpoint *ump)
+{
+}
static inline void update_legacy_names(struct snd_ump_endpoint *ump)
{
}
#endif
+/* copy a string safely with stripping non-printable letters */
+static void safe_copy_string(void *dst, size_t max_dst_size,
+ const void *src, size_t max_src_size)
+{
+ const unsigned char *s = src;
+ unsigned char *d = dst;
+
+ if (!max_dst_size--)
+ return;
+ for (s = src; max_dst_size && *s && max_src_size--; s++) {
+ if (!isascii(*s) || !isprint(*s))
+ continue;
+ *d++ = *s;
+ max_dst_size--;
+ }
+ *d = 0;
+}
+
+/* append a string safely with stripping non-printable letters */
+static void safe_append_string(void *dst, size_t max_dst_size,
+ const void *src, size_t max_src_size)
+{
+ unsigned char *d = dst;
+ size_t len = strlen(d);
+
+ safe_copy_string(d + len, max_dst_size - len, src, max_src_size);
+}
+
static const struct snd_rawmidi_global_ops snd_ump_rawmidi_ops = {
.dev_register = snd_ump_dev_register,
.dev_unregister = snd_ump_dev_unregister,
@@ -565,16 +597,10 @@ void snd_ump_update_group_attrs(struct snd_ump_endpoint *ump)
}
if (!*fb->info.name)
continue;
- if (!*group->name) {
- /* store the first matching name */
- strscpy(group->name, fb->info.name,
- sizeof(group->name));
- } else {
- /* when overlapping, concat names */
+ if (*group->name)
strlcat(group->name, ", ", sizeof(group->name));
- strlcat(group->name, fb->info.name,
- sizeof(group->name));
- }
+ safe_append_string(group->name, sizeof(group->name),
+ fb->info.name, sizeof(fb->info.name));
}
}
}
@@ -669,6 +695,15 @@ static void choose_default_protocol(struct snd_ump_endpoint *ump)
ump->info.protocol |= SNDRV_UMP_EP_INFO_PROTO_MIDI1;
}
+/* notify the EP info/name change to sequencer */
+static void seq_notify_ep_change(struct snd_ump_endpoint *ump)
+{
+#if IS_ENABLED(CONFIG_SND_SEQUENCER)
+ if (ump->parsed && ump->seq_ops && ump->seq_ops->notify_ep_change)
+ ump->seq_ops->notify_ep_change(ump);
+#endif
+}
+
/* handle EP info stream message; update the UMP attributes */
static int ump_handle_ep_info_msg(struct snd_ump_endpoint *ump,
const union snd_ump_stream_msg *buf)
@@ -693,6 +728,7 @@ static int ump_handle_ep_info_msg(struct snd_ump_endpoint *ump,
ump->info.protocol &= ump->info.protocol_caps;
choose_default_protocol(ump);
+ seq_notify_ep_change(ump);
return 1; /* finished */
}
@@ -715,24 +751,46 @@ static int ump_handle_device_info_msg(struct snd_ump_endpoint *ump,
ump->info.family_id,
ump->info.model_id,
ump->info.sw_revision);
+ seq_notify_ep_change(ump);
return 1; /* finished */
}
+/* set up the core rawmidi name from UMP EP name string */
+static void ump_set_rawmidi_name(struct snd_ump_endpoint *ump)
+{
+ safe_copy_string(ump->core.name, sizeof(ump->core.name),
+ ump->info.name, sizeof(ump->info.name));
+}
+
/* handle EP name stream message; update the UMP name string */
static int ump_handle_ep_name_msg(struct snd_ump_endpoint *ump,
const union snd_ump_stream_msg *buf)
{
- return ump_append_string(ump, ump->info.name, sizeof(ump->info.name),
- buf->raw, 2);
+ int ret;
+
+ ret = ump_append_string(ump, ump->info.name, sizeof(ump->info.name),
+ buf->raw, 2);
+ if (ret && ump->parsed) {
+ ump_set_rawmidi_name(ump);
+ ump_legacy_set_rawmidi_name(ump);
+ seq_notify_ep_change(ump);
+ }
+
+ return ret;
}
/* handle EP product id stream message; update the UMP product_id string */
static int ump_handle_product_id_msg(struct snd_ump_endpoint *ump,
const union snd_ump_stream_msg *buf)
{
- return ump_append_string(ump, ump->info.product_id,
- sizeof(ump->info.product_id),
- buf->raw, 2);
+ int ret;
+
+ ret = ump_append_string(ump, ump->info.product_id,
+ sizeof(ump->info.product_id),
+ buf->raw, 2);
+ if (ret)
+ seq_notify_ep_change(ump);
+ return ret;
}
/* notify the protocol change to sequencer */
@@ -1045,6 +1103,8 @@ int snd_ump_parse_endpoint(struct snd_ump_endpoint *ump)
if (err < 0)
ump_dbg(ump, "Unable to get UMP EP name string\n");
+ ump_set_rawmidi_name(ump);
+
/* Request Endpoint Product ID */
err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_PRODUCT_ID,
UMP_STREAM_MSG_STATUS_PRODUCT_ID);
@@ -1250,8 +1310,8 @@ static int fill_legacy_mapping(struct snd_ump_endpoint *ump)
return num;
}
-static void fill_substream_names(struct snd_ump_endpoint *ump,
- struct snd_rawmidi *rmidi, int dir)
+static void update_legacy_substreams(struct snd_ump_endpoint *ump,
+ struct snd_rawmidi *rmidi, int dir)
{
struct snd_rawmidi_substream *s;
const char *name;
@@ -1261,10 +1321,11 @@ static void fill_substream_names(struct snd_ump_endpoint *ump,
idx = ump->legacy_mapping[s->number];
name = ump->groups[idx].name;
if (!*name)
- name = ump->info.name;
+ name = ump->core.name;
scnprintf(s->name, sizeof(s->name), "Group %d (%.16s)%s",
idx + 1, name,
ump->groups[idx].active ? "" : " [Inactive]");
+ s->inactive = !ump->groups[idx].active;
}
}
@@ -1272,8 +1333,16 @@ static void update_legacy_names(struct snd_ump_endpoint *ump)
{
struct snd_rawmidi *rmidi = ump->legacy_rmidi;
- fill_substream_names(ump, rmidi, SNDRV_RAWMIDI_STREAM_INPUT);
- fill_substream_names(ump, rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT);
+ update_legacy_substreams(ump, rmidi, SNDRV_RAWMIDI_STREAM_INPUT);
+ update_legacy_substreams(ump, rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT);
+}
+
+static void ump_legacy_set_rawmidi_name(struct snd_ump_endpoint *ump)
+{
+ struct snd_rawmidi *rmidi = ump->legacy_rmidi;
+
+ snprintf(rmidi->name, sizeof(rmidi->name), "%.68s (MIDI 1.0)",
+ ump->core.name);
}
int snd_ump_attach_legacy_rawmidi(struct snd_ump_endpoint *ump,
@@ -1306,14 +1375,15 @@ int snd_ump_attach_legacy_rawmidi(struct snd_ump_endpoint *ump,
if (output)
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
&snd_ump_legacy_output_ops);
- snprintf(rmidi->name, sizeof(rmidi->name), "%.68s (MIDI 1.0)",
- ump->info.name);
rmidi->info_flags = ump->core.info_flags & ~SNDRV_RAWMIDI_INFO_UMP;
rmidi->ops = &snd_ump_legacy_ops;
rmidi->private_data = ump;
ump->legacy_rmidi = rmidi;
+ ump_legacy_set_rawmidi_name(ump);
update_legacy_names(ump);
+ snd_rawmidi_tie_devices(rmidi, &ump->core);
+
ump_dbg(ump, "Created a legacy rawmidi #%d (%s)\n", device, id);
return 0;
}
diff --git a/sound/firewire/fireface/ff-protocol-former.c b/sound/firewire/fireface/ff-protocol-former.c
index efd59e9d9935..0907d0a2296f 100644
--- a/sound/firewire/fireface/ff-protocol-former.c
+++ b/sound/firewire/fireface/ff-protocol-former.c
@@ -135,13 +135,13 @@ static void dump_clock_config(struct snd_ff *ff, struct snd_info_buffer *buffer)
snd_iprintf(buffer, "Output S/PDIF format: %s (Emphasis: %s)\n",
(data & 0x00000020) ? "Professional" : "Consumer",
- (data & 0x00000040) ? "on" : "off");
+ str_on_off(data & 0x00000040));
snd_iprintf(buffer, "Optical output interface format: %s\n",
(data & 0x00000100) ? "S/PDIF" : "ADAT");
snd_iprintf(buffer, "Word output single speed: %s\n",
- (data & 0x00002000) ? "on" : "off");
+ str_on_off(data & 0x00002000));
snd_iprintf(buffer, "S/PDIF input interface: %s\n",
(data & 0x00000200) ? "Optical" : "Coaxial");
diff --git a/sound/hda/hdac_component.c b/sound/hda/hdac_component.c
index bb37e7e0bd79..9c82a2864a2f 100644
--- a/sound/hda/hdac_component.c
+++ b/sound/hda/hdac_component.c
@@ -5,6 +5,7 @@
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/component.h>
+#include <linux/string_choices.h>
#include <sound/core.h>
#include <sound/hdaudio.h>
#include <sound/hda_component.h>
@@ -42,8 +43,7 @@ int snd_hdac_set_codec_wakeup(struct hdac_bus *bus, bool enable)
if (!acomp->ops->codec_wake_override)
return 0;
- dev_dbg(bus->dev, "%s codec wakeup\n",
- enable ? "enable" : "disable");
+ dev_dbg(bus->dev, "%s codec wakeup\n", str_enable_disable(enable));
acomp->ops->codec_wake_override(acomp->dev, enable);
@@ -67,8 +67,7 @@ void snd_hdac_display_power(struct hdac_bus *bus, unsigned int idx, bool enable)
{
struct drm_audio_component *acomp = bus->audio_component;
- dev_dbg(bus->dev, "display power %s\n",
- enable ? "enable" : "disable");
+ dev_dbg(bus->dev, "display power %s\n", str_enable_disable(enable));
mutex_lock(&bus->lock);
if (enable)
diff --git a/sound/hda/hdac_stream.c b/sound/hda/hdac_stream.c
index 2670792f43b4..4e85a838ad7e 100644
--- a/sound/hda/hdac_stream.c
+++ b/sound/hda/hdac_stream.c
@@ -492,32 +492,21 @@ static int setup_bdle(struct hdac_bus *bus,
}
/**
- * snd_hdac_stream_setup_periods - set up BDL entries
+ * snd_hdac_stream_setup_bdle - set up BDL entries
* @azx_dev: HD-audio core stream to set up
+ * @dmab: allocated DMA buffer
+ * @runtime: substream runtime, optional
*
* Set up the buffer descriptor table of the given stream based on the
* period and buffer sizes of the assigned PCM substream.
*/
-int snd_hdac_stream_setup_periods(struct hdac_stream *azx_dev)
+static int snd_hdac_stream_setup_bdle(struct hdac_stream *azx_dev, struct snd_dma_buffer *dmab,
+ struct snd_pcm_runtime *runtime)
{
struct hdac_bus *bus = azx_dev->bus;
- struct snd_pcm_substream *substream = azx_dev->substream;
- struct snd_compr_stream *cstream = azx_dev->cstream;
- struct snd_pcm_runtime *runtime = NULL;
- struct snd_dma_buffer *dmab;
- __le32 *bdl;
int i, ofs, periods, period_bytes;
int pos_adj, pos_align;
-
- if (substream) {
- runtime = substream->runtime;
- dmab = snd_pcm_get_dma_buf(substream);
- } else if (cstream) {
- dmab = snd_pcm_get_dma_buf(cstream);
- } else {
- WARN(1, "No substream or cstream assigned\n");
- return -EINVAL;
- }
+ __le32 *bdl;
/* reset BDL address */
snd_hdac_stream_writel(azx_dev, SD_BDLPL, 0);
@@ -571,6 +560,33 @@ int snd_hdac_stream_setup_periods(struct hdac_stream *azx_dev)
azx_dev->bufsize, period_bytes);
return -EINVAL;
}
+
+/**
+ * snd_hdac_stream_setup_periods - set up BDL entries
+ * @azx_dev: HD-audio core stream to set up
+ *
+ * Set up the buffer descriptor table of the given stream based on the
+ * period and buffer sizes of the assigned PCM substream.
+ */
+int snd_hdac_stream_setup_periods(struct hdac_stream *azx_dev)
+{
+ struct snd_pcm_substream *substream = azx_dev->substream;
+ struct snd_compr_stream *cstream = azx_dev->cstream;
+ struct snd_pcm_runtime *runtime = NULL;
+ struct snd_dma_buffer *dmab;
+
+ if (substream) {
+ runtime = substream->runtime;
+ dmab = snd_pcm_get_dma_buf(substream);
+ } else if (cstream) {
+ dmab = snd_pcm_get_dma_buf(cstream);
+ } else {
+ WARN(1, "No substream or cstream assigned\n");
+ return -EINVAL;
+ }
+
+ return snd_hdac_stream_setup_bdle(azx_dev, dmab, runtime);
+}
EXPORT_SYMBOL_GPL(snd_hdac_stream_setup_periods);
/**
@@ -923,7 +939,6 @@ int snd_hdac_dsp_prepare(struct hdac_stream *azx_dev, unsigned int format,
unsigned int byte_size, struct snd_dma_buffer *bufp)
{
struct hdac_bus *bus = azx_dev->bus;
- __le32 *bdl;
int err;
snd_hdac_dsp_lock(azx_dev);
@@ -943,18 +958,14 @@ int snd_hdac_dsp_prepare(struct hdac_stream *azx_dev, unsigned int format,
azx_dev->substream = NULL;
azx_dev->bufsize = byte_size;
- azx_dev->period_bytes = byte_size;
+ /* It is recommended to transfer the firmware in two or more chunks. */
+ azx_dev->period_bytes = byte_size / 2;
azx_dev->format_val = format;
+ azx_dev->no_period_wakeup = 1;
snd_hdac_stream_reset(azx_dev);
- /* reset BDL address */
- snd_hdac_stream_writel(azx_dev, SD_BDLPL, 0);
- snd_hdac_stream_writel(azx_dev, SD_BDLPU, 0);
-
- azx_dev->frags = 0;
- bdl = (__le32 *)azx_dev->bdl.area;
- err = setup_bdle(bus, bufp, azx_dev, &bdl, 0, byte_size, 0);
+ err = snd_hdac_stream_setup_bdle(azx_dev, bufp, NULL);
if (err < 0)
goto error;
diff --git a/sound/isa/sb/sb16_csp.c b/sound/isa/sb/sb16_csp.c
index 071ba85f76bb..7034072c80d4 100644
--- a/sound/isa/sb/sb16_csp.c
+++ b/sound/isa/sb/sb16_csp.c
@@ -13,6 +13,7 @@
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/module.h>
+#include <linux/string_choices.h>
#include <sound/core.h>
#include <sound/control.h>
#include <sound/info.h>
@@ -1157,8 +1158,8 @@ static void info_read(struct snd_info_entry *entry, struct snd_info_buffer *buff
((p->acc_rates & SNDRV_SB_CSP_RATE_44100) ? "44100Hz" : ""));
}
if (p->mode == SNDRV_SB_CSP_MODE_QSOUND) {
- snd_iprintf(buffer, "QSound decoder %sabled\n",
- p->q_enabled ? "en" : "dis");
+ snd_iprintf(buffer, "QSound decoder %s\n",
+ str_enabled_disabled(p->q_enabled));
} else {
snd_iprintf(buffer, "PCM format ID: 0x%x (%s/%s) [%s/%s] [%s/%s]\n",
p->acc_format,
diff --git a/sound/pci/ac97/ac97_codec.c b/sound/pci/ac97/ac97_codec.c
index 25f93e56cfc7..6e710dce5c60 100644
--- a/sound/pci/ac97/ac97_codec.c
+++ b/sound/pci/ac97/ac97_codec.c
@@ -1864,7 +1864,7 @@ void snd_ac97_get_name(struct snd_ac97 *ac97, unsigned int id, char *name, int m
strcat(name, " ");
strcat(name, pid->name);
if (pid->mask != 0xffffffff)
- sprintf(name + strlen(name), " rev %d", id & ~pid->mask);
+ sprintf(name + strlen(name), " rev %u", id & ~pid->mask);
if (ac97 && pid->patch) {
if ((modem && (pid->flags & AC97_MODEM_PATCH)) ||
(! modem && ! (pid->flags & AC97_MODEM_PATCH)))
diff --git a/sound/pci/ac97/ac97_proc.c b/sound/pci/ac97/ac97_proc.c
index 5426f7bc9884..518834964b7b 100644
--- a/sound/pci/ac97/ac97_proc.c
+++ b/sound/pci/ac97/ac97_proc.c
@@ -161,12 +161,12 @@ static void snd_ac97_proc_read_main(struct snd_ac97 *ac97, struct snd_info_buffe
"Mic select : %s\n"
"ADC/DAC loopback : %s\n",
val & 0x8000 ? "post" : "pre",
- val & 0x4000 ? "on" : "off",
- val & 0x2000 ? "on" : "off",
- val & 0x1000 ? "on" : "off",
+ str_on_off(val & 0x4000),
+ str_on_off(val & 0x2000),
+ str_on_off(val & 0x1000),
val & 0x0200 ? "Mic" : "MIX",
val & 0x0100 ? "Mic2" : "Mic1",
- val & 0x0080 ? "on" : "off");
+ str_on_off(val & 0x0080));
if (ac97->ext_id & AC97_EI_DRA)
snd_iprintf(buffer, "Double rate slots: %s\n",
double_rate_slots[(val >> 10) & 3]);
diff --git a/sound/pci/ad1889.c b/sound/pci/ad1889.c
index 50e30704bf6f..9ed778b6b03c 100644
--- a/sound/pci/ad1889.c
+++ b/sound/pci/ad1889.c
@@ -626,7 +626,7 @@ snd_ad1889_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffe
reg = ad1889_readw(chip, AD_DS_WSMC);
snd_iprintf(buffer, "Wave output: %s\n",
- (reg & AD_DS_WSMC_WAEN) ? "enabled" : "disabled");
+ str_enabled_disabled(reg & AD_DS_WSMC_WAEN));
snd_iprintf(buffer, "Wave Channels: %s\n",
(reg & AD_DS_WSMC_WAST) ? "stereo" : "mono");
snd_iprintf(buffer, "Wave Quality: %d-bit linear\n",
@@ -642,7 +642,7 @@ snd_ad1889_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffe
snd_iprintf(buffer, "Synthesis output: %s\n",
- reg & AD_DS_WSMC_SYEN ? "enabled" : "disabled");
+ str_enabled_disabled(reg & AD_DS_WSMC_SYEN));
/* SYRQ is at offset 4 */
tmp = (reg & AD_DS_WSMC_SYRQ) ?
@@ -654,7 +654,7 @@ snd_ad1889_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffe
reg = ad1889_readw(chip, AD_DS_RAMC);
snd_iprintf(buffer, "ADC input: %s\n",
- (reg & AD_DS_RAMC_ADEN) ? "enabled" : "disabled");
+ str_enabled_disabled(reg & AD_DS_RAMC_ADEN));
snd_iprintf(buffer, "ADC Channels: %s\n",
(reg & AD_DS_RAMC_ADST) ? "stereo" : "mono");
snd_iprintf(buffer, "ADC Quality: %d-bit linear\n",
@@ -669,7 +669,7 @@ snd_ad1889_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffe
(reg & AD_DS_RAMC_ADST) ? "stereo" : "mono");
snd_iprintf(buffer, "Resampler input: %s\n",
- reg & AD_DS_RAMC_REEN ? "enabled" : "disabled");
+ str_enabled_disabled(reg & AD_DS_RAMC_REEN));
/* RERQ is at offset 12 */
tmp = (reg & AD_DS_RAMC_RERQ) ?
diff --git a/sound/pci/cmipci.c b/sound/pci/cmipci.c
index e3cac73517d6..cb8593c376ee 100644
--- a/sound/pci/cmipci.c
+++ b/sound/pci/cmipci.c
@@ -3084,7 +3084,7 @@ static int snd_cmipci_create(struct snd_card *card, struct pci_dev *pci,
}
}
}
- sprintf(card->shortname, "C-Media CMI%d", val);
+ sprintf(card->shortname, "C-Media CMI%u", val);
if (cm->chip_version < 68)
scnprintf(modelstr, sizeof(modelstr),
" (model %d)", cm->chip_version);
diff --git a/sound/pci/ctxfi/ctdaio.c b/sound/pci/ctxfi/ctdaio.c
index 83aaf9441ef3..9993b02d2968 100644
--- a/sound/pci/ctxfi/ctdaio.c
+++ b/sound/pci/ctxfi/ctdaio.c
@@ -211,52 +211,30 @@ static int dao_set_right_input(struct dao *dao, struct rsc *input)
return 0;
}
-static int dao_clear_left_input(struct dao *dao)
+static int dao_clear_input(struct dao *dao, unsigned int start, unsigned int end)
{
- struct imapper *entry;
- struct daio *daio = &dao->daio;
- int i;
+ unsigned int i;
- if (!dao->imappers[0])
+ if (!dao->imappers[start])
return 0;
-
- entry = dao->imappers[0];
- dao->mgr->imap_delete(dao->mgr, entry);
- /* Program conjugate resources */
- for (i = 1; i < daio->rscl.msr; i++) {
- entry = dao->imappers[i];
- dao->mgr->imap_delete(dao->mgr, entry);
+ for (i = start; i < end; i++) {
+ dao->mgr->imap_delete(dao->mgr, dao->imappers[i]);
dao->imappers[i] = NULL;
}
- kfree(dao->imappers[0]);
- dao->imappers[0] = NULL;
-
return 0;
}
-static int dao_clear_right_input(struct dao *dao)
-{
- struct imapper *entry;
- struct daio *daio = &dao->daio;
- int i;
- if (!dao->imappers[daio->rscl.msr])
- return 0;
-
- entry = dao->imappers[daio->rscl.msr];
- dao->mgr->imap_delete(dao->mgr, entry);
- /* Program conjugate resources */
- for (i = 1; i < daio->rscr.msr; i++) {
- entry = dao->imappers[daio->rscl.msr + i];
- dao->mgr->imap_delete(dao->mgr, entry);
- dao->imappers[daio->rscl.msr + i] = NULL;
- }
-
- kfree(dao->imappers[daio->rscl.msr]);
- dao->imappers[daio->rscl.msr] = NULL;
+static int dao_clear_left_input(struct dao *dao)
+{
+ return dao_clear_input(dao, 0, dao->daio.rscl.msr);
+}
- return 0;
+static int dao_clear_right_input(struct dao *dao)
+{
+ return dao_clear_input(dao, dao->daio.rscl.msr,
+ dao->daio.rscl.msr + dao->daio.rscr.msr);
}
static const struct dao_rsc_ops dao_ops = {
diff --git a/sound/pci/emu10k1/emuproc.c b/sound/pci/emu10k1/emuproc.c
index 737c28d31b41..bd4734dc04cd 100644
--- a/sound/pci/emu10k1/emuproc.c
+++ b/sound/pci/emu10k1/emuproc.c
@@ -11,6 +11,7 @@
#include <linux/slab.h>
#include <linux/init.h>
+#include <linux/string_choices.h>
#include <sound/core.h>
#include <sound/emu10k1.h>
#include "p16v.h"
@@ -32,9 +33,9 @@ static void snd_emu10k1_proc_spdif_status(struct snd_emu10k1 * emu,
snd_iprintf(buffer, "\n%s\n", title);
if (status != 0xffffffff) {
- snd_iprintf(buffer, "Professional Mode : %s\n", (status & SPCS_PROFESSIONAL) ? "yes" : "no");
- snd_iprintf(buffer, "Not Audio Data : %s\n", (status & SPCS_NOTAUDIODATA) ? "yes" : "no");
- snd_iprintf(buffer, "Copyright : %s\n", (status & SPCS_COPYRIGHT) ? "yes" : "no");
+ snd_iprintf(buffer, "Professional Mode : %s\n", str_yes_no(status & SPCS_PROFESSIONAL));
+ snd_iprintf(buffer, "Not Audio Data : %s\n", str_yes_no(status & SPCS_NOTAUDIODATA));
+ snd_iprintf(buffer, "Copyright : %s\n", str_yes_no(status & SPCS_COPYRIGHT));
snd_iprintf(buffer, "Emphasis : %s\n", emphasis[(status & SPCS_EMPHASISMASK) >> 3]);
snd_iprintf(buffer, "Mode : %i\n", (status & SPCS_MODEMASK) >> 6);
snd_iprintf(buffer, "Category Code : 0x%x\n", (status & SPCS_CATEGORYCODEMASK) >> 8);
@@ -46,9 +47,9 @@ static void snd_emu10k1_proc_spdif_status(struct snd_emu10k1 * emu,
if (rate_reg > 0) {
rate = snd_emu10k1_ptr_read(emu, rate_reg, 0);
- snd_iprintf(buffer, "S/PDIF Valid : %s\n", rate & SRCS_SPDIFVALID ? "on" : "off");
- snd_iprintf(buffer, "S/PDIF Locked : %s\n", rate & SRCS_SPDIFLOCKED ? "on" : "off");
- snd_iprintf(buffer, "Rate Locked : %s\n", rate & SRCS_RATELOCKED ? "on" : "off");
+ snd_iprintf(buffer, "S/PDIF Valid : %s\n", str_on_off(rate & SRCS_SPDIFVALID));
+ snd_iprintf(buffer, "S/PDIF Locked : %s\n", str_on_off(rate & SRCS_SPDIFLOCKED));
+ snd_iprintf(buffer, "Rate Locked : %s\n", str_on_off(rate & SRCS_RATELOCKED));
/* From ((Rate * 48000 ) / 262144); */
snd_iprintf(buffer, "Estimated Sample Rate : %d\n", ((rate & 0xFFFFF ) * 375) >> 11);
}
@@ -208,7 +209,7 @@ static void snd_emu10k1_proc_spdif_read(struct snd_info_entry *entry,
#if 0
val = snd_emu10k1_ptr_read(emu, ZVSRCS, 0);
snd_iprintf(buffer, "\nZoomed Video\n");
- snd_iprintf(buffer, "Rate Locked : %s\n", val & SRCS_RATELOCKED ? "on" : "off");
+ snd_iprintf(buffer, "Rate Locked : %s\n", str_on_off(val & SRCS_RATELOCKED));
snd_iprintf(buffer, "Estimated Sample Rate : 0x%x\n", val & SRCS_ESTSAMPLERATE);
#endif
}
diff --git a/sound/pci/ens1370.c b/sound/pci/ens1370.c
index 18928b905939..71c89e4e3090 100644
--- a/sound/pci/ens1370.c
+++ b/sound/pci/ens1370.c
@@ -1850,12 +1850,12 @@ static void snd_ensoniq_proc_read(struct snd_info_entry *entry,
snd_iprintf(buffer, "Ensoniq AudioPCI " CHIP_NAME "\n\n");
snd_iprintf(buffer, "Joystick enable : %s\n",
- ensoniq->ctrl & ES_JYSTK_EN ? "on" : "off");
+ str_on_off(ensoniq->ctrl & ES_JYSTK_EN));
#ifdef CHIP1370
snd_iprintf(buffer, "MIC +5V bias : %s\n",
- ensoniq->ctrl & ES_1370_XCTL1 ? "on" : "off");
+ str_on_off(ensoniq->ctrl & ES_1370_XCTL1));
snd_iprintf(buffer, "Line In to AOUT : %s\n",
- ensoniq->ctrl & ES_1370_XCTL0 ? "on" : "off");
+ str_on_off(ensoniq->ctrl & ES_1370_XCTL0));
#else
snd_iprintf(buffer, "Joystick port : 0x%x\n",
(ES_1371_JOY_ASELI(ensoniq->ctrl) * 8) + 0x200);
diff --git a/sound/pci/hda/Kconfig b/sound/pci/hda/Kconfig
index 68f1eee9e5c9..e393578cbe68 100644
--- a/sound/pci/hda/Kconfig
+++ b/sound/pci/hda/Kconfig
@@ -206,6 +206,20 @@ config SND_HDA_SCODEC_TAS2781_I2C
comment "Set to Y if you want auto-loading the side codec driver"
depends on SND_HDA=y && SND_HDA_SCODEC_TAS2781_I2C=m
+config SND_HDA_SCODEC_TAS2781_SPI
+ tristate "Build TAS2781 HD-audio side codec support for SPI Bus"
+ depends on SPI_MASTER
+ depends on ACPI
+ depends on EFI
+ depends on SND_SOC
+ select CRC32
+ help
+ Say Y or M here to include TAS2781 SPI HD-audio side codec support
+ in snd-hda-intel driver, such as ALC287.
+
+comment "Set to Y if you want auto-loading the side codec driver"
+ depends on SND_HDA=y && SND_HDA_SCODEC_TAS2781_SPI=m
+
config SND_HDA_CODEC_REALTEK
tristate "Build Realtek HD-audio codec support"
select SND_HDA_GENERIC
diff --git a/sound/pci/hda/Makefile b/sound/pci/hda/Makefile
index 80210f845df2..210c406dfbc5 100644
--- a/sound/pci/hda/Makefile
+++ b/sound/pci/hda/Makefile
@@ -40,6 +40,7 @@ snd-hda-scodec-cs35l56-spi-y := cs35l56_hda_spi.o
snd-hda-cs-dsp-ctls-y := hda_cs_dsp_ctl.o
snd-hda-scodec-component-y := hda_component.o
snd-hda-scodec-tas2781-i2c-y := tas2781_hda_i2c.o
+snd-hda-scodec-tas2781-spi-y := tas2781_hda_spi.o tas2781_spi_fwlib.o
# common driver
obj-$(CONFIG_SND_HDA) := snd-hda-codec.o
@@ -72,6 +73,7 @@ obj-$(CONFIG_SND_HDA_SCODEC_CS35L56_SPI) += snd-hda-scodec-cs35l56-spi.o
obj-$(CONFIG_SND_HDA_CS_DSP_CONTROLS) += snd-hda-cs-dsp-ctls.o
obj-$(CONFIG_SND_HDA_SCODEC_COMPONENT) += snd-hda-scodec-component.o
obj-$(CONFIG_SND_HDA_SCODEC_TAS2781_I2C) += snd-hda-scodec-tas2781-i2c.o
+obj-$(CONFIG_SND_HDA_SCODEC_TAS2781_SPI) += snd-hda-scodec-tas2781-spi.o
# this must be the last entry after codec drivers;
# otherwise the codec patches won't be hooked before the PCI probe
diff --git a/sound/pci/hda/hda_hwdep.c b/sound/pci/hda/hda_hwdep.c
index 727f39acedfc..9325e5c3cbe6 100644
--- a/sound/pci/hda/hda_hwdep.c
+++ b/sound/pci/hda/hda_hwdep.c
@@ -84,10 +84,8 @@ static int hda_hwdep_ioctl_compat(struct snd_hwdep *hw, struct file *file,
static int hda_hwdep_open(struct snd_hwdep *hw, struct file *file)
{
-#ifndef CONFIG_SND_DEBUG_VERBOSE
if (!capable(CAP_SYS_RAWIO))
return -EACCES;
-#endif
return 0;
}
diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c
index 4a62440adfaf..7d7f9aac50a9 100644
--- a/sound/pci/hda/hda_intel.c
+++ b/sound/pci/hda/hda_intel.c
@@ -2738,9 +2738,9 @@ static const struct pci_device_id azx_ids[] = {
{ PCI_VDEVICE(ZHAOXIN, 0x3288), .driver_data = AZX_DRIVER_ZHAOXIN },
/* Loongson HDAudio*/
{ PCI_VDEVICE(LOONGSON, PCI_DEVICE_ID_LOONGSON_HDA),
- .driver_data = AZX_DRIVER_LOONGSON },
+ .driver_data = AZX_DRIVER_LOONGSON | AZX_DCAPS_NO_TCSEL },
{ PCI_VDEVICE(LOONGSON, PCI_DEVICE_ID_LOONGSON_HDMI),
- .driver_data = AZX_DRIVER_LOONGSON },
+ .driver_data = AZX_DRIVER_LOONGSON | AZX_DCAPS_NO_TCSEL },
{ 0, }
};
MODULE_DEVICE_TABLE(pci, azx_ids);
diff --git a/sound/pci/hda/hda_sysfs.c b/sound/pci/hda/hda_sysfs.c
index 265fd4737893..140e24bf4d7f 100644
--- a/sound/pci/hda/hda_sysfs.c
+++ b/sound/pci/hda/hda_sysfs.c
@@ -648,7 +648,7 @@ static const struct hda_patch_item patch_items[NUM_LINE_MODES] = {
},
};
-/* check the line starting with '[' -- change the parser mode accodingly */
+/* check the line starting with '[' -- change the parser mode accordingly */
static int parse_line_mode(char *buf, struct hda_bus *bus)
{
int i;
diff --git a/sound/pci/hda/ideapad_hotkey_led_helper.c b/sound/pci/hda/ideapad_hotkey_led_helper.c
new file mode 100644
index 000000000000..c10d97964d49
--- /dev/null
+++ b/sound/pci/hda/ideapad_hotkey_led_helper.c
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Ideapad helper functions for Lenovo Ideapad LED control,
+ * It should be included from codec driver.
+ */
+
+#if IS_ENABLED(CONFIG_IDEAPAD_LAPTOP)
+
+#include <linux/acpi.h>
+#include <linux/leds.h>
+
+static bool is_ideapad(struct hda_codec *codec)
+{
+ return (codec->core.subsystem_id >> 16 == 0x17aa) &&
+ (acpi_dev_found("LHK2019") || acpi_dev_found("VPC2004"));
+}
+
+static void hda_fixup_ideapad_acpi(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action == HDA_FIXUP_ACT_PRE_PROBE) {
+ if (!is_ideapad(codec))
+ return;
+ snd_hda_gen_add_mute_led_cdev(codec, NULL);
+ snd_hda_gen_add_micmute_led_cdev(codec, NULL);
+ }
+}
+
+#else /* CONFIG_IDEAPAD_LAPTOP */
+
+static void hda_fixup_ideapad_acpi(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+}
+
+#endif /* CONFIG_IDEAPAD_LAPTOP */
diff --git a/sound/pci/hda/patch_conexant.c b/sound/pci/hda/patch_conexant.c
index 538c37a78a56..4985e72b9094 100644
--- a/sound/pci/hda/patch_conexant.c
+++ b/sound/pci/hda/patch_conexant.c
@@ -291,6 +291,7 @@ enum {
CXT_FIXUP_GPIO1,
CXT_FIXUP_ASPIRE_DMIC,
CXT_FIXUP_THINKPAD_ACPI,
+ CXT_FIXUP_LENOVO_XPAD_ACPI,
CXT_FIXUP_OLPC_XO,
CXT_FIXUP_CAP_MIX_AMP,
CXT_FIXUP_TOSHIBA_P105,
@@ -313,6 +314,9 @@ enum {
/* for hda_fixup_thinkpad_acpi() */
#include "thinkpad_helper.c"
+/* for hda_fixup_ideapad_acpi() */
+#include "ideapad_hotkey_led_helper.c"
+
static void cxt_fixup_stereo_dmic(struct hda_codec *codec,
const struct hda_fixup *fix, int action)
{
@@ -928,6 +932,12 @@ static const struct hda_fixup cxt_fixups[] = {
.type = HDA_FIXUP_FUNC,
.v.func = hda_fixup_thinkpad_acpi,
},
+ [CXT_FIXUP_LENOVO_XPAD_ACPI] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = hda_fixup_ideapad_acpi,
+ .chained = true,
+ .chain_id = CXT_FIXUP_THINKPAD_ACPI,
+ },
[CXT_FIXUP_OLPC_XO] = {
.type = HDA_FIXUP_FUNC,
.v.func = cxt_fixup_olpc_xo,
@@ -1119,7 +1129,7 @@ static const struct hda_quirk cxt5066_fixups[] = {
SND_PCI_QUIRK(0x17aa, 0x3977, "Lenovo IdeaPad U310", CXT_FIXUP_STEREO_DMIC),
SND_PCI_QUIRK(0x17aa, 0x3978, "Lenovo G50-70", CXT_FIXUP_STEREO_DMIC),
SND_PCI_QUIRK(0x17aa, 0x397b, "Lenovo S205", CXT_FIXUP_STEREO_DMIC),
- SND_PCI_QUIRK_VENDOR(0x17aa, "Thinkpad", CXT_FIXUP_THINKPAD_ACPI),
+ SND_PCI_QUIRK_VENDOR(0x17aa, "Thinkpad/Ideapad", CXT_FIXUP_LENOVO_XPAD_ACPI),
SND_PCI_QUIRK(0x1c06, 0x2011, "Lemote A1004", CXT_PINCFG_LEMOTE_A1004),
SND_PCI_QUIRK(0x1c06, 0x2012, "Lemote A1205", CXT_PINCFG_LEMOTE_A1205),
HDA_CODEC_QUIRK(0x2782, 0x12c3, "Sirius Gen1", CXT_PINCFG_TOP_SPEAKER),
@@ -1133,6 +1143,7 @@ static const struct hda_model_fixup cxt5066_fixup_models[] = {
{ .id = CXT_FIXUP_HEADPHONE_MIC_PIN, .name = "headphone-mic-pin" },
{ .id = CXT_PINCFG_LENOVO_TP410, .name = "tp410" },
{ .id = CXT_FIXUP_THINKPAD_ACPI, .name = "thinkpad" },
+ { .id = CXT_FIXUP_LENOVO_XPAD_ACPI, .name = "thinkpad-ideapad" },
{ .id = CXT_PINCFG_LEMOTE_A1004, .name = "lemote-a1004" },
{ .id = CXT_PINCFG_LEMOTE_A1205, .name = "lemote-a1205" },
{ .id = CXT_FIXUP_OLPC_XO, .name = "olpc-xo" },
diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c
index b74b566f675e..46623cd615a4 100644
--- a/sound/pci/hda/patch_realtek.c
+++ b/sound/pci/hda/patch_realtek.c
@@ -890,9 +890,7 @@ static void alc_ssid_check(struct hda_codec *codec, const hda_nid_t *ports)
}
}
-/*
- */
-
+/* inverted digital-mic */
static void alc_fixup_inv_dmic(struct hda_codec *codec,
const struct hda_fixup *fix, int action)
{
@@ -5902,7 +5900,7 @@ static void alc_determine_headset_type(struct hda_codec *codec)
}
codec_dbg(codec, "Headset jack detected iPhone-style headset: %s\n",
- is_ctia ? "yes" : "no");
+ str_yes_no(is_ctia));
spec->current_headset_type = is_ctia ? ALC_HEADSET_TYPE_CTIA : ALC_HEADSET_TYPE_OMTP;
}
@@ -6934,6 +6932,15 @@ static void alc_fixup_thinkpad_acpi(struct hda_codec *codec,
hda_fixup_thinkpad_acpi(codec, fix, action);
}
+/* for hda_fixup_ideapad_acpi() */
+#include "ideapad_hotkey_led_helper.c"
+
+static void alc_fixup_ideapad_acpi(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ hda_fixup_ideapad_acpi(codec, fix, action);
+}
+
/* Fixup for Lenovo Legion 15IMHg05 speaker output on headset removal. */
static void alc287_fixup_legion_15imhg05_speakers(struct hda_codec *codec,
const struct hda_fixup *fix,
@@ -7128,6 +7135,11 @@ static void tas2781_fixup_i2c(struct hda_codec *cdc,
comp_generic_fixup(cdc, action, "i2c", "TIAS2781", "-%s:00", 1);
}
+static void tas2781_fixup_spi(struct hda_codec *cdc, const struct hda_fixup *fix, int action)
+{
+ comp_generic_fixup(cdc, action, "spi", "TXNW2781", "-%s:00-tas2781-hda.%d", 2);
+}
+
static void yoga7_14arb7_fixup_i2c(struct hda_codec *cdc,
const struct hda_fixup *fix, int action)
{
@@ -7556,6 +7568,7 @@ enum {
ALC290_FIXUP_SUBWOOFER,
ALC290_FIXUP_SUBWOOFER_HSJACK,
ALC269_FIXUP_THINKPAD_ACPI,
+ ALC269_FIXUP_LENOVO_XPAD_ACPI,
ALC269_FIXUP_DMIC_THINKPAD_ACPI,
ALC269VB_FIXUP_INFINIX_ZERO_BOOK_13,
ALC269VC_FIXUP_INFINIX_Y4_MAX,
@@ -7762,6 +7775,7 @@ enum {
ALC236_FIXUP_DELL_DUAL_CODECS,
ALC287_FIXUP_CS35L41_I2C_2_THINKPAD_ACPI,
ALC287_FIXUP_TAS2781_I2C,
+ ALC245_FIXUP_TAS2781_SPI_2,
ALC287_FIXUP_YOGA7_14ARB7_I2C,
ALC245_FIXUP_HP_MUTE_LED_COEFBIT,
ALC245_FIXUP_HP_X360_MUTE_LEDS,
@@ -8327,6 +8341,12 @@ static const struct hda_fixup alc269_fixups[] = {
.chained = true,
.chain_id = ALC269_FIXUP_SKU_IGNORE,
},
+ [ALC269_FIXUP_LENOVO_XPAD_ACPI] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc_fixup_ideapad_acpi,
+ .chained = true,
+ .chain_id = ALC269_FIXUP_THINKPAD_ACPI,
+ },
[ALC269_FIXUP_DMIC_THINKPAD_ACPI] = {
.type = HDA_FIXUP_FUNC,
.v.func = alc_fixup_inv_dmic,
@@ -9986,6 +10006,12 @@ static const struct hda_fixup alc269_fixups[] = {
.chained = true,
.chain_id = ALC285_FIXUP_THINKPAD_HEADSET_JACK,
},
+ [ALC245_FIXUP_TAS2781_SPI_2] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = tas2781_fixup_spi,
+ .chained = true,
+ .chain_id = ALC285_FIXUP_HP_GPIO_LED,
+ },
[ALC287_FIXUP_YOGA7_14ARB7_I2C] = {
.type = HDA_FIXUP_FUNC,
.v.func = yoga7_14arb7_fixup_i2c,
@@ -10158,6 +10184,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x1025, 0x1308, "Acer Aspire Z24-890", ALC286_FIXUP_ACER_AIO_HEADSET_MIC),
SND_PCI_QUIRK(0x1025, 0x132a, "Acer TravelMate B114-21", ALC233_FIXUP_ACER_HEADSET_MIC),
SND_PCI_QUIRK(0x1025, 0x1330, "Acer TravelMate X514-51T", ALC255_FIXUP_ACER_HEADSET_MIC),
+ SND_PCI_QUIRK(0x1025, 0x1360, "Acer Aspire A115", ALC255_FIXUP_ACER_MIC_NO_PRESENCE),
SND_PCI_QUIRK(0x1025, 0x141f, "Acer Spin SP513-54N", ALC255_FIXUP_ACER_MIC_NO_PRESENCE),
SND_PCI_QUIRK(0x1025, 0x142b, "Acer Swift SF314-42", ALC255_FIXUP_ACER_MIC_NO_PRESENCE),
SND_PCI_QUIRK(0x1025, 0x1430, "Acer TravelMate B311R-31", ALC256_FIXUP_ACER_MIC_NO_PRESENCE),
@@ -10548,6 +10575,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x103c, 0x8d84, "HP EliteBook X G1i", ALC285_FIXUP_HP_GPIO_LED),
SND_PCI_QUIRK(0x103c, 0x8d91, "HP ZBook Firefly 14 G12", ALC285_FIXUP_HP_GPIO_LED),
SND_PCI_QUIRK(0x103c, 0x8d92, "HP ZBook Firefly 16 G12", ALC285_FIXUP_HP_GPIO_LED),
+ SND_PCI_QUIRK(0x103c, 0x8de8, "HP Gemtree", ALC245_FIXUP_TAS2781_SPI_2),
+ SND_PCI_QUIRK(0x103c, 0x8de9, "HP Gemtree", ALC245_FIXUP_TAS2781_SPI_2),
SND_PCI_QUIRK(0x103c, 0x8e18, "HP ZBook Firefly 14 G12A", ALC285_FIXUP_HP_GPIO_LED),
SND_PCI_QUIRK(0x103c, 0x8e19, "HP ZBook Firefly 14 G12A", ALC285_FIXUP_HP_GPIO_LED),
SND_PCI_QUIRK(0x103c, 0x8e1a, "HP ZBook Firefly 14 G12A", ALC285_FIXUP_HP_GPIO_LED),
@@ -10641,6 +10670,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x1043, 0x1e1f, "ASUS Vivobook 15 X1504VAP", ALC2XX_FIXUP_HEADSET_MIC),
SND_PCI_QUIRK(0x1043, 0x1e51, "ASUS Zephyrus M15", ALC294_FIXUP_ASUS_GU502_PINS),
SND_PCI_QUIRK(0x1043, 0x1e5e, "ASUS ROG Strix G513", ALC294_FIXUP_ASUS_G513_PINS),
+ SND_PCI_QUIRK(0x1043, 0x1e63, "ASUS H7606W", ALC285_FIXUP_ASUS_GU605_SPI_SPEAKER2_TO_DAC1),
+ SND_PCI_QUIRK(0x1043, 0x1e83, "ASUS GA605W", ALC285_FIXUP_ASUS_GU605_SPI_SPEAKER2_TO_DAC1),
SND_PCI_QUIRK(0x1043, 0x1e8e, "ASUS Zephyrus G15", ALC289_FIXUP_ASUS_GA401),
SND_PCI_QUIRK(0x1043, 0x1eb3, "ASUS Ally RCLA72", ALC287_FIXUP_TAS2781_I2C),
SND_PCI_QUIRK(0x1043, 0x1ed3, "ASUS HN7306W", ALC287_FIXUP_CS35L41_I2C_2),
@@ -10930,8 +10961,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x17aa, 0x38e0, "Yoga Y990 Intel VECO Dual", ALC287_FIXUP_TAS2781_I2C),
SND_PCI_QUIRK(0x17aa, 0x38f8, "Yoga Book 9i", ALC287_FIXUP_TAS2781_I2C),
SND_PCI_QUIRK(0x17aa, 0x38df, "Y990 YG DUAL", ALC287_FIXUP_TAS2781_I2C),
- SND_PCI_QUIRK(0x17aa, 0x38f9, "Thinkbook 16P Gen5", ALC287_FIXUP_CS35L41_I2C_2),
- SND_PCI_QUIRK(0x17aa, 0x38fa, "Thinkbook 16P Gen5", ALC287_FIXUP_CS35L41_I2C_2),
+ SND_PCI_QUIRK(0x17aa, 0x38f9, "Thinkbook 16P Gen5", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD),
+ SND_PCI_QUIRK(0x17aa, 0x38fa, "Thinkbook 16P Gen5", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD),
SND_PCI_QUIRK(0x17aa, 0x38fd, "ThinkBook plus Gen5 Hybrid", ALC287_FIXUP_TAS2781_I2C),
SND_PCI_QUIRK(0x17aa, 0x3902, "Lenovo E50-80", ALC269_FIXUP_DMIC_THINKPAD_ACPI),
SND_PCI_QUIRK(0x17aa, 0x3913, "Lenovo 145", ALC236_FIXUP_LENOVO_INV_DMIC),
@@ -10961,6 +10992,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x17aa, 0x511f, "Thinkpad", ALC298_FIXUP_TPT470_DOCK),
SND_PCI_QUIRK(0x17aa, 0x9e54, "LENOVO NB", ALC269_FIXUP_LENOVO_EAPD),
SND_PCI_QUIRK(0x17aa, 0x9e56, "Lenovo ZhaoYang CF4620Z", ALC286_FIXUP_SONY_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1849, 0x0269, "Positivo Master C6400", ALC269VB_FIXUP_ASUS_ZENBOOK),
SND_PCI_QUIRK(0x1849, 0x1233, "ASRock NUC Box 1100", ALC233_FIXUP_NO_AUDIO_JACK),
SND_PCI_QUIRK(0x1849, 0xa233, "Positivo Master C6300", ALC269_FIXUP_HEADSET_MIC),
SND_PCI_QUIRK(0x1854, 0x0440, "LG CQ6", ALC256_FIXUP_HEADPHONE_AMP_VOL),
@@ -10995,6 +11027,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x1d72, 0x1901, "RedmiBook 14", ALC256_FIXUP_ASUS_HEADSET_MIC),
SND_PCI_QUIRK(0x1d72, 0x1945, "Redmi G", ALC256_FIXUP_ASUS_HEADSET_MIC),
SND_PCI_QUIRK(0x1d72, 0x1947, "RedmiBook Air", ALC255_FIXUP_XIAOMI_HEADSET_MIC),
+ SND_PCI_QUIRK(0x1f66, 0x0105, "Ayaneo Portable Game Player", ALC287_FIXUP_CS35L41_I2C_2),
SND_PCI_QUIRK(0x2782, 0x0214, "VAIO VJFE-CL", ALC269_FIXUP_LIMIT_INT_MIC_BOOST),
SND_PCI_QUIRK(0x2782, 0x0228, "Infinix ZERO BOOK 13", ALC269VB_FIXUP_INFINIX_ZERO_BOOK_13),
SND_PCI_QUIRK(0x2782, 0x0232, "CHUWI CoreBook XPro", ALC269VB_FIXUP_CHUWI_COREBOOK_XPRO),
@@ -11066,7 +11099,7 @@ static const struct hda_quirk alc269_fixup_vendor_tbl[] = {
SND_PCI_QUIRK_VENDOR(0x1025, "Acer Aspire", ALC271_FIXUP_DMIC),
SND_PCI_QUIRK_VENDOR(0x103c, "HP", ALC269_FIXUP_HP_MUTE_LED),
SND_PCI_QUIRK_VENDOR(0x104d, "Sony VAIO", ALC269_FIXUP_SONY_VAIO),
- SND_PCI_QUIRK_VENDOR(0x17aa, "Thinkpad", ALC269_FIXUP_THINKPAD_ACPI),
+ SND_PCI_QUIRK_VENDOR(0x17aa, "Lenovo XPAD", ALC269_FIXUP_LENOVO_XPAD_ACPI),
SND_PCI_QUIRK_VENDOR(0x19e5, "Huawei Matebook", ALC255_FIXUP_MIC_MUTE_LED),
{}
};
@@ -11131,6 +11164,7 @@ static const struct hda_model_fixup alc269_fixup_models[] = {
{.id = ALC290_FIXUP_MONO_SPEAKERS_HSJACK, .name = "mono-speakers"},
{.id = ALC290_FIXUP_SUBWOOFER_HSJACK, .name = "alc290-subwoofer"},
{.id = ALC269_FIXUP_THINKPAD_ACPI, .name = "thinkpad"},
+ {.id = ALC269_FIXUP_LENOVO_XPAD_ACPI, .name = "lenovo-xpad-led"},
{.id = ALC269_FIXUP_DMIC_THINKPAD_ACPI, .name = "dmic-thinkpad"},
{.id = ALC255_FIXUP_ACER_MIC_NO_PRESENCE, .name = "alc255-acer"},
{.id = ALC255_FIXUP_ASUS_MIC_NO_PRESENCE, .name = "alc255-asus"},
diff --git a/sound/pci/hda/tas2781-spi.h b/sound/pci/hda/tas2781-spi.h
new file mode 100644
index 000000000000..ecfc3c8bb821
--- /dev/null
+++ b/sound/pci/hda/tas2781-spi.h
@@ -0,0 +1,158 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+//
+// ALSA SoC Texas Instruments TAS2781 Audio Smart Amplifier
+//
+// Copyright (C) 2024 Texas Instruments Incorporated
+// https://www.ti.com
+//
+// The TAS2781 driver implements a flexible and configurable
+// algo coefficient setting for TAS2781 chips.
+//
+// Author: Baojun Xu <baojun.xu@ti.com>
+//
+
+#ifndef __TAS2781_SPI_H__
+#define __TAS2781_SPI_H__
+
+#define TASDEVICE_RATES \
+ (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | \
+ SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_88200)
+
+#define TASDEVICE_FORMATS \
+ (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+#define TASDEVICE_MAX_BOOK_NUM 256
+#define TASDEVICE_MAX_PAGE 256
+
+#define TASDEVICE_MAX_SIZE (TASDEVICE_MAX_BOOK_NUM * TASDEVICE_MAX_PAGE)
+
+/* PAGE Control Register (available in page0 of each book) */
+#define TASDEVICE_PAGE_SELECT 0x00
+#define TASDEVICE_BOOKCTL_PAGE 0x00
+#define TASDEVICE_BOOKCTL_REG GENMASK(7, 1)
+#define TASDEVICE_BOOK_ID(reg) (((reg) & GENMASK(24, 16)) >> 16)
+#define TASDEVICE_PAGE_ID(reg) (((reg) & GENMASK(15, 8)) >> 8)
+#define TASDEVICE_REG_ID(reg) (((reg) & GENMASK(7, 1)) >> 1)
+#define TASDEVICE_PAGE_REG(reg) ((reg) & GENMASK(15, 1))
+#define TASDEVICE_REG(book, page, reg) \
+ (((book) << 16) | ((page) << 8) | ((reg) << 1))
+
+/* Software Reset */
+#define TAS2781_REG_SWRESET TASDEVICE_REG(0x0, 0x0, 0x01)
+#define TAS2781_REG_SWRESET_RESET BIT(0)
+
+/* System Reset Check Register */
+#define TAS2781_REG_CLK_CONFIG TASDEVICE_REG(0x0, 0x0, 0x5c)
+#define TAS2781_REG_CLK_CONFIG_RESET (0x19)
+#define TAS2781_PRE_POST_RESET_CFG 3
+
+/* Block Checksum */
+#define TASDEVICE_CHECKSUM TASDEVICE_REG(0x0, 0x0, 0x7e)
+
+/* Volume control */
+#define TAS2781_DVC_LVL TASDEVICE_REG(0x0, 0x0, 0x1a)
+#define TAS2781_AMP_LEVEL TASDEVICE_REG(0x0, 0x0, 0x03)
+#define TAS2781_AMP_LEVEL_MASK GENMASK(5, 1)
+
+#define TASDEVICE_CMD_SING_W 0x1
+#define TASDEVICE_CMD_BURST 0x2
+#define TASDEVICE_CMD_DELAY 0x3
+#define TASDEVICE_CMD_FIELD_W 0x4
+
+#define TAS2781_SPI_MAX_FREQ (4 * HZ_PER_MHZ)
+
+#define TASDEVICE_CRC8_POLYNOMIAL 0x4d
+#define TASDEVICE_SPEAKER_CALIBRATION_SIZE 20
+
+/* Flag of calibration registers address. */
+#define TASDEVICE_CALIBRATION_REG_ADDRESS BIT(7)
+
+#define TASDEVICE_CALIBRATION_DATA_NAME L"CALI_DATA"
+#define TASDEVICE_CALIBRATION_DATA_SIZE 256
+
+enum calib_data {
+ R0_VAL = 0,
+ INV_R0,
+ R0LOW,
+ POWER,
+ TLIM,
+ CALIB_MAX
+};
+
+struct tasdevice_priv {
+ struct tasdevice_fw *cali_data_fmw;
+ struct tasdevice_rca rcabin;
+ struct tasdevice_fw *fmw;
+ struct gpio_desc *reset;
+ struct mutex codec_lock;
+ struct regmap *regmap;
+ struct device *dev;
+ struct tm tm;
+
+ unsigned char crc8_lkp_tbl[CRC8_TABLE_SIZE];
+ unsigned char coef_binaryname[64];
+ unsigned char rca_binaryname[64];
+ unsigned char dev_name[32];
+
+ bool force_fwload_status;
+ bool playback_started;
+ bool is_loading;
+ bool is_loaderr;
+ unsigned int cali_reg_array[CALIB_MAX];
+ unsigned int cali_data[CALIB_MAX];
+ unsigned int err_code;
+ void *codec;
+ int cur_book;
+ int cur_prog;
+ int cur_conf;
+ int fw_state;
+ int index;
+ int irq;
+
+ int (*fw_parse_variable_header)(struct tasdevice_priv *tas_priv,
+ const struct firmware *fmw,
+ int offset);
+ int (*fw_parse_program_data)(struct tasdevice_priv *tas_priv,
+ struct tasdevice_fw *tas_fmw,
+ const struct firmware *fmw, int offset);
+ int (*fw_parse_configuration_data)(struct tasdevice_priv *tas_priv,
+ struct tasdevice_fw *tas_fmw,
+ const struct firmware *fmw,
+ int offset);
+ int (*tasdevice_load_block)(struct tasdevice_priv *tas_priv,
+ struct tasdev_blk *block);
+
+ int (*save_calibration)(struct tasdevice_priv *tas_priv);
+ void (*apply_calibration)(struct tasdevice_priv *tas_priv);
+};
+
+int tasdevice_spi_dev_read(struct tasdevice_priv *tas_priv,
+ unsigned int reg, unsigned int *value);
+int tasdevice_spi_dev_write(struct tasdevice_priv *tas_priv,
+ unsigned int reg, unsigned int value);
+int tasdevice_spi_dev_bulk_write(struct tasdevice_priv *tas_priv,
+ unsigned int reg, unsigned char *p_data,
+ unsigned int n_length);
+int tasdevice_spi_dev_bulk_read(struct tasdevice_priv *tas_priv,
+ unsigned int reg, unsigned char *p_data,
+ unsigned int n_length);
+int tasdevice_spi_dev_update_bits(struct tasdevice_priv *tasdevice,
+ unsigned int reg, unsigned int mask,
+ unsigned int value);
+
+void tasdevice_spi_select_cfg_blk(void *context, int conf_no,
+ unsigned char block_type);
+void tasdevice_spi_config_info_remove(void *context);
+int tasdevice_spi_dsp_parser(void *context);
+int tasdevice_spi_rca_parser(void *context, const struct firmware *fmw);
+void tasdevice_spi_dsp_remove(void *context);
+void tasdevice_spi_calbin_remove(void *context);
+int tasdevice_spi_select_tuningprm_cfg(void *context, int prm, int cfg_no,
+ int rca_conf_no);
+int tasdevice_spi_prmg_load(void *context, int prm_no);
+int tasdevice_spi_prmg_calibdata_load(void *context, int prm_no);
+void tasdevice_spi_tuning_switch(void *context, int state);
+int tas2781_spi_load_calibration(void *context, char *file_name,
+ unsigned short i);
+#endif /* __TAS2781_SPI_H__ */
diff --git a/sound/pci/hda/tas2781_hda_spi.c b/sound/pci/hda/tas2781_hda_spi.c
new file mode 100644
index 000000000000..5be71b538ce0
--- /dev/null
+++ b/sound/pci/hda/tas2781_hda_spi.c
@@ -0,0 +1,1270 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// TAS2781 HDA SPI driver
+//
+// Copyright 2024 Texas Instruments, Inc.
+//
+// Author: Baojun Xu <baojun.xu@ti.com>
+
+#include <linux/acpi.h>
+#include <linux/array_size.h>
+#include <linux/bits.h>
+#include <linux/cleanup.h>
+#include <linux/crc8.h>
+#include <linux/crc32.h>
+#include <linux/efi.h>
+#include <linux/firmware.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/pm_runtime.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/spi/spi.h>
+#include <linux/time.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+#include <sound/hda_codec.h>
+#include <sound/soc.h>
+#include <sound/tas2781-dsp.h>
+#include <sound/tlv.h>
+#include <sound/tas2781-tlv.h>
+
+#include "tas2781-spi.h"
+
+#include "hda_local.h"
+#include "hda_auto_parser.h"
+#include "hda_component.h"
+#include "hda_jack.h"
+#include "hda_generic.h"
+
+/*
+ * No standard control callbacks for SNDRV_CTL_ELEM_IFACE_CARD
+ * Define two controls, one is Volume control callbacks, the other is
+ * flag setting control callbacks.
+ */
+
+/* Volume control callbacks for tas2781 */
+#define ACARD_SINGLE_RANGE_EXT_TLV(xname, xreg, xshift, xmin, xmax, xinvert, \
+ xhandler_get, xhandler_put, tlv_array) { \
+ .iface = SNDRV_CTL_ELEM_IFACE_CARD, .name = (xname), \
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \
+ SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+ .tlv.p = (tlv_array), \
+ .info = snd_soc_info_volsw_range, \
+ .get = xhandler_get, .put = xhandler_put, \
+ .private_value = (unsigned long)&(struct soc_mixer_control) { \
+ .reg = xreg, .rreg = xreg, \
+ .shift = xshift, .rshift = xshift,\
+ .min = xmin, .max = xmax, .invert = xinvert, \
+ } \
+}
+
+/* Flag control callbacks for tas2781 */
+#define ACARD_SINGLE_BOOL_EXT(xname, xdata, xhandler_get, xhandler_put) { \
+ .iface = SNDRV_CTL_ELEM_IFACE_CARD, \
+ .name = xname, \
+ .info = snd_ctl_boolean_mono_info, \
+ .get = xhandler_get, \
+ .put = xhandler_put, \
+ .private_value = xdata, \
+}
+
+struct tas2781_hda {
+ struct tasdevice_priv *priv;
+ struct acpi_device *dacpi;
+ struct snd_kcontrol *dsp_prog_ctl;
+ struct snd_kcontrol *dsp_conf_ctl;
+ struct snd_kcontrol *snd_ctls[3];
+ struct snd_kcontrol *prof_ctl;
+};
+
+static const struct regmap_range_cfg tasdevice_ranges[] = {
+ {
+ .range_min = 0,
+ .range_max = TASDEVICE_MAX_SIZE,
+ .selector_reg = TASDEVICE_PAGE_SELECT,
+ .selector_mask = GENMASK(7, 0),
+ .selector_shift = 0,
+ .window_start = 0,
+ .window_len = TASDEVICE_MAX_PAGE,
+ },
+};
+
+static const struct regmap_config tasdevice_regmap = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .zero_flag_mask = true,
+ .cache_type = REGCACHE_NONE,
+ .ranges = tasdevice_ranges,
+ .num_ranges = ARRAY_SIZE(tasdevice_ranges),
+ .max_register = TASDEVICE_MAX_SIZE,
+};
+
+static int tasdevice_spi_switch_book(struct tasdevice_priv *tas_priv, int reg)
+{
+ struct regmap *map = tas_priv->regmap;
+
+ if (tas_priv->cur_book != TASDEVICE_BOOK_ID(reg)) {
+ int ret = regmap_write(map, TASDEVICE_BOOKCTL_REG,
+ TASDEVICE_BOOK_ID(reg));
+ if (ret < 0) {
+ dev_err(tas_priv->dev, "Switch Book E=%d\n", ret);
+ return ret;
+ }
+ tas_priv->cur_book = TASDEVICE_BOOK_ID(reg);
+ }
+ return 0;
+}
+
+int tasdevice_spi_dev_read(struct tasdevice_priv *tas_priv,
+ unsigned int reg,
+ unsigned int *val)
+{
+ struct regmap *map = tas_priv->regmap;
+ int ret;
+
+ ret = tasdevice_spi_switch_book(tas_priv, reg);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * In our TAS2781 SPI mode, if read from other book (not book 0),
+ * or read from page number larger than 1 in book 0, one more byte
+ * read is needed, and first byte is a dummy byte, need to be ignored.
+ */
+ if ((TASDEVICE_BOOK_ID(reg) > 0) || (TASDEVICE_PAGE_ID(reg) > 1)) {
+ unsigned char data[2];
+
+ ret = regmap_bulk_read(map, TASDEVICE_PAGE_REG(reg) | 1,
+ data, sizeof(data));
+ *val = data[1];
+ } else {
+ ret = regmap_read(map, TASDEVICE_PAGE_REG(reg) | 1, val);
+ }
+ if (ret < 0)
+ dev_err(tas_priv->dev, "%s, E=%d\n", __func__, ret);
+
+ return ret;
+}
+
+int tasdevice_spi_dev_write(struct tasdevice_priv *tas_priv,
+ unsigned int reg,
+ unsigned int value)
+{
+ struct regmap *map = tas_priv->regmap;
+ int ret;
+
+ ret = tasdevice_spi_switch_book(tas_priv, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write(map, TASDEVICE_PAGE_REG(reg), value);
+ if (ret < 0)
+ dev_err(tas_priv->dev, "%s, E=%d\n", __func__, ret);
+
+ return ret;
+}
+
+int tasdevice_spi_dev_bulk_write(struct tasdevice_priv *tas_priv,
+ unsigned int reg,
+ unsigned char *data,
+ unsigned int len)
+{
+ struct regmap *map = tas_priv->regmap;
+ int ret;
+
+ ret = tasdevice_spi_switch_book(tas_priv, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_bulk_write(map, TASDEVICE_PAGE_REG(reg), data, len);
+ if (ret < 0)
+ dev_err(tas_priv->dev, "%s, E=%d\n", __func__, ret);
+
+ return ret;
+}
+
+int tasdevice_spi_dev_bulk_read(struct tasdevice_priv *tas_priv,
+ unsigned int reg,
+ unsigned char *data,
+ unsigned int len)
+{
+ struct regmap *map = tas_priv->regmap;
+ int ret;
+
+ ret = tasdevice_spi_switch_book(tas_priv, reg);
+ if (ret < 0)
+ return ret;
+
+ if (len > TASDEVICE_MAX_PAGE)
+ len = TASDEVICE_MAX_PAGE;
+ /*
+ * In our TAS2781 SPI mode, if read from other book (not book 0),
+ * or read from page number larger than 1 in book 0, one more byte
+ * read is needed, and first byte is a dummy byte, need to be ignored.
+ */
+ if ((TASDEVICE_BOOK_ID(reg) > 0) || (TASDEVICE_PAGE_ID(reg) > 1)) {
+ unsigned char buf[TASDEVICE_MAX_PAGE+1];
+
+ ret = regmap_bulk_read(map, TASDEVICE_PAGE_REG(reg) | 1, buf,
+ len + 1);
+ memcpy(data, buf + 1, len);
+ } else {
+ ret = regmap_bulk_read(map, TASDEVICE_PAGE_REG(reg) | 1, data,
+ len);
+ }
+ if (ret < 0)
+ dev_err(tas_priv->dev, "%s, E=%d\n", __func__, ret);
+
+ return ret;
+}
+
+int tasdevice_spi_dev_update_bits(struct tasdevice_priv *tas_priv,
+ unsigned int reg,
+ unsigned int mask,
+ unsigned int value)
+{
+ struct regmap *map = tas_priv->regmap;
+ int ret, val;
+
+ /*
+ * In our TAS2781 SPI mode, read/write was masked in last bit of
+ * address, it cause regmap_update_bits() not work as expected.
+ */
+ ret = tasdevice_spi_dev_read(tas_priv, reg, &val);
+ if (ret < 0) {
+ dev_err(tas_priv->dev, "%s, E=%d\n", __func__, ret);
+ return ret;
+ }
+ ret = regmap_write(map, TASDEVICE_PAGE_REG(reg),
+ (val & ~mask) | (mask & value));
+ if (ret < 0)
+ dev_err(tas_priv->dev, "%s, E=%d\n", __func__, ret);
+
+ return ret;
+}
+
+static void tas2781_spi_reset(struct tasdevice_priv *tas_dev)
+{
+ int ret;
+
+ if (tas_dev->reset) {
+ gpiod_set_value_cansleep(tas_dev->reset, 0);
+ fsleep(800);
+ gpiod_set_value_cansleep(tas_dev->reset, 1);
+ }
+ ret = tasdevice_spi_dev_write(tas_dev, TAS2781_REG_SWRESET,
+ TAS2781_REG_SWRESET_RESET);
+ if (ret < 0)
+ dev_err(tas_dev->dev, "dev sw-reset fail, %d\n", ret);
+ fsleep(1000);
+}
+
+static int tascodec_spi_init(struct tasdevice_priv *tas_priv,
+ void *codec, struct module *module,
+ void (*cont)(const struct firmware *fw, void *context))
+{
+ int ret;
+
+ /*
+ * Codec Lock Hold to ensure that codec_probe and firmware parsing and
+ * loading do not simultaneously execute.
+ */
+ guard(mutex)(&tas_priv->codec_lock);
+
+ ret = scnprintf(tas_priv->rca_binaryname,
+ sizeof(tas_priv->rca_binaryname), "%sRCA%d.bin",
+ tas_priv->dev_name, tas_priv->index);
+ if (ret <= 0) {
+ dev_err(tas_priv->dev, "rca name err:0x%08x\n", ret);
+ return ret;
+ }
+ crc8_populate_msb(tas_priv->crc8_lkp_tbl, TASDEVICE_CRC8_POLYNOMIAL);
+ tas_priv->codec = codec;
+ ret = request_firmware_nowait(module, FW_ACTION_UEVENT,
+ tas_priv->rca_binaryname, tas_priv->dev, GFP_KERNEL, tas_priv,
+ cont);
+ if (ret)
+ dev_err(tas_priv->dev, "request_firmware_nowait err:0x%08x\n",
+ ret);
+
+ return ret;
+}
+
+static void tasdevice_spi_init(struct tasdevice_priv *tas_priv)
+{
+ tas_priv->cur_prog = -1;
+ tas_priv->cur_conf = -1;
+
+ tas_priv->cur_book = -1;
+ tas_priv->cur_prog = -1;
+ tas_priv->cur_conf = -1;
+
+ /* Store default registers address for calibration data. */
+ tas_priv->cali_reg_array[0] = TASDEVICE_REG(0, 0x17, 0x74);
+ tas_priv->cali_reg_array[1] = TASDEVICE_REG(0, 0x18, 0x0c);
+ tas_priv->cali_reg_array[2] = TASDEVICE_REG(0, 0x18, 0x14);
+ tas_priv->cali_reg_array[3] = TASDEVICE_REG(0, 0x13, 0x70);
+ tas_priv->cali_reg_array[4] = TASDEVICE_REG(0, 0x18, 0x7c);
+
+ mutex_init(&tas_priv->codec_lock);
+}
+
+static int tasdevice_spi_amp_putvol(struct tasdevice_priv *tas_priv,
+ struct snd_ctl_elem_value *ucontrol,
+ struct soc_mixer_control *mc)
+{
+ unsigned int invert = mc->invert;
+ unsigned char mask;
+ int max = mc->max;
+ int val, ret;
+
+ mask = rounddown_pow_of_two(max);
+ mask <<= mc->shift;
+ val = clamp(invert ? max - ucontrol->value.integer.value[0] :
+ ucontrol->value.integer.value[0], 0, max);
+ ret = tasdevice_spi_dev_update_bits(tas_priv,
+ mc->reg, mask, (unsigned int)(val << mc->shift));
+ if (ret)
+ dev_err(tas_priv->dev, "set AMP vol error in dev %d\n",
+ tas_priv->index);
+
+ return ret;
+}
+
+static int tasdevice_spi_amp_getvol(struct tasdevice_priv *tas_priv,
+ struct snd_ctl_elem_value *ucontrol,
+ struct soc_mixer_control *mc)
+{
+ unsigned int invert = mc->invert;
+ unsigned char mask = 0;
+ int max = mc->max;
+ int ret, val;
+
+ /* Read the primary device */
+ ret = tasdevice_spi_dev_read(tas_priv, mc->reg, &val);
+ if (ret) {
+ dev_err(tas_priv->dev, "%s, get AMP vol error\n", __func__);
+ return ret;
+ }
+
+ mask = rounddown_pow_of_two(max);
+ mask <<= mc->shift;
+ val = (val & mask) >> mc->shift;
+ val = clamp(invert ? max - val : val, 0, max);
+ ucontrol->value.integer.value[0] = val;
+
+ return ret;
+}
+
+static int tasdevice_spi_digital_putvol(struct tasdevice_priv *tas_priv,
+ struct snd_ctl_elem_value *ucontrol,
+ struct soc_mixer_control *mc)
+{
+ unsigned int invert = mc->invert;
+ int max = mc->max;
+ int val, ret;
+
+ val = clamp(invert ? max - ucontrol->value.integer.value[0] :
+ ucontrol->value.integer.value[0], 0, max);
+ ret = tasdevice_spi_dev_write(tas_priv, mc->reg, (unsigned int)val);
+ if (ret)
+ dev_err(tas_priv->dev, "set digital vol err in dev %d\n",
+ tas_priv->index);
+
+ return ret;
+}
+
+static int tasdevice_spi_digital_getvol(struct tasdevice_priv *tas_priv,
+ struct snd_ctl_elem_value *ucontrol,
+ struct soc_mixer_control *mc)
+{
+ unsigned int invert = mc->invert;
+ int max = mc->max;
+ int ret, val;
+
+ /* Read the primary device as the whole */
+ ret = tasdevice_spi_dev_read(tas_priv, mc->reg, &val);
+ if (ret) {
+ dev_err(tas_priv->dev, "%s, get digital vol err\n", __func__);
+ return ret;
+ }
+
+ val = clamp(invert ? max - val : val, 0, max);
+ ucontrol->value.integer.value[0] = val;
+
+ return ret;
+}
+
+static int tas2781_read_acpi(struct tas2781_hda *tas_hda,
+ const char *hid,
+ int id)
+{
+ struct tasdevice_priv *p = tas_hda->priv;
+ struct acpi_device *adev;
+ struct device *physdev;
+ u32 values[HDA_MAX_COMPONENTS];
+ const char *property;
+ size_t nval;
+ int ret, i;
+
+ adev = acpi_dev_get_first_match_dev(hid, NULL, -1);
+ if (!adev) {
+ dev_err(p->dev, "Failed to find ACPI device: %s\n", hid);
+ return -ENODEV;
+ }
+
+ strscpy(p->dev_name, hid, sizeof(p->dev_name));
+ tas_hda->dacpi = adev;
+ physdev = get_device(acpi_get_first_physical_node(adev));
+ acpi_dev_put(adev);
+
+ property = "ti,dev-index";
+ ret = device_property_count_u32(physdev, property);
+ if (ret <= 0 || ret > ARRAY_SIZE(values)) {
+ ret = -EINVAL;
+ goto err;
+ }
+ nval = ret;
+
+ ret = device_property_read_u32_array(physdev, property, values, nval);
+ if (ret)
+ goto err;
+
+ p->index = U8_MAX;
+ for (i = 0; i < nval; i++) {
+ if (values[i] == id) {
+ p->index = i;
+ break;
+ }
+ }
+ if (p->index == U8_MAX) {
+ dev_dbg(p->dev, "No index found in %s\n", property);
+ ret = -ENODEV;
+ goto err;
+ }
+
+ if (p->index == 0) {
+ /* All of amps share same RESET pin. */
+ p->reset = devm_gpiod_get_index_optional(physdev, "reset",
+ p->index, GPIOD_OUT_LOW);
+ if (IS_ERR(p->reset)) {
+ dev_err_probe(p->dev, ret, "Failed on reset GPIO\n");
+ goto err;
+ }
+ }
+ put_device(physdev);
+
+ return 0;
+err:
+ dev_err(p->dev, "read acpi error, ret: %d\n", ret);
+ put_device(physdev);
+ acpi_dev_put(adev);
+
+ return ret;
+}
+
+static void tas2781_hda_playback_hook(struct device *dev, int action)
+{
+ struct tas2781_hda *tas_hda = dev_get_drvdata(dev);
+
+ if (action == HDA_GEN_PCM_ACT_OPEN) {
+ pm_runtime_get_sync(dev);
+ guard(mutex)(&tas_hda->priv->codec_lock);
+ tasdevice_spi_tuning_switch(tas_hda->priv, 0);
+ } else if (action == HDA_GEN_PCM_ACT_CLOSE) {
+ guard(mutex)(&tas_hda->priv->codec_lock);
+ tasdevice_spi_tuning_switch(tas_hda->priv, 1);
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
+ }
+}
+
+static int tasdevice_info_profile(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = tas_priv->rcabin.ncfgs - 1;
+
+ return 0;
+}
+
+static int tasdevice_get_profile_id(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.integer.value[0] = tas_priv->rcabin.profile_cfg_id;
+
+ return 0;
+}
+
+static int tasdevice_set_profile_id(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
+ int max = tas_priv->rcabin.ncfgs - 1;
+ int val;
+
+ val = clamp(ucontrol->value.integer.value[0], 0, max);
+ if (tas_priv->rcabin.profile_cfg_id != val) {
+ tas_priv->rcabin.profile_cfg_id = val;
+ return 1;
+ }
+
+ return 0;
+}
+
+static int tasdevice_info_programs(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = tas_priv->fmw->nr_programs - 1;
+
+ return 0;
+}
+
+static int tasdevice_info_config(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = tas_priv->fmw->nr_configurations - 1;
+
+ return 0;
+}
+
+static int tasdevice_program_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.integer.value[0] = tas_priv->cur_prog;
+
+ return 0;
+}
+
+static int tasdevice_program_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
+ int nr_program = ucontrol->value.integer.value[0];
+ int max = tas_priv->fmw->nr_programs - 1;
+ int val;
+
+ val = clamp(nr_program, 0, max);
+
+ if (tas_priv->cur_prog != val) {
+ tas_priv->cur_prog = val;
+ return 1;
+ }
+
+ return 0;
+}
+
+static int tasdevice_config_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.integer.value[0] = tas_priv->cur_conf;
+
+ return 0;
+}
+
+static int tasdevice_config_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
+ int max = tas_priv->fmw->nr_configurations - 1;
+ int val;
+
+ val = clamp(ucontrol->value.integer.value[0], 0, max);
+
+ if (tas_priv->cur_conf != val) {
+ tas_priv->cur_conf = val;
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+ * tas2781_digital_getvol - get the volum control
+ * @kcontrol: control pointer
+ * @ucontrol: User data
+ *
+ * Customer Kcontrol for tas2781 is primarily for regmap booking, paging
+ * depends on internal regmap mechanism.
+ * tas2781 contains book and page two-level register map, especially
+ * book switching will set the register BXXP00R7F, after switching to the
+ * correct book, then leverage the mechanism for paging to access the
+ * register.
+ *
+ * Return 0 if succeeded.
+ */
+static int tas2781_digital_getvol(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+
+ return tasdevice_spi_digital_getvol(tas_priv, ucontrol, mc);
+}
+
+static int tas2781_amp_getvol(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+
+ return tasdevice_spi_amp_getvol(tas_priv, ucontrol, mc);
+}
+
+static int tas2781_digital_putvol(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+
+ /* The check of the given value is in tasdevice_digital_putvol. */
+ return tasdevice_spi_digital_putvol(tas_priv, ucontrol, mc);
+}
+
+static int tas2781_amp_putvol(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+
+ /* The check of the given value is in tasdevice_amp_putvol. */
+ return tasdevice_spi_amp_putvol(tas_priv, ucontrol, mc);
+}
+
+static int tas2781_force_fwload_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.integer.value[0] = (int)tas_priv->force_fwload_status;
+ dev_dbg(tas_priv->dev, "%s : Force FWload %s\n", __func__,
+ str_on_off(tas_priv->force_fwload_status));
+
+ return 0;
+}
+
+static int tas2781_force_fwload_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct tasdevice_priv *tas_priv = snd_kcontrol_chip(kcontrol);
+ bool change, val = (bool)ucontrol->value.integer.value[0];
+
+ if (tas_priv->force_fwload_status == val) {
+ change = false;
+ } else {
+ change = true;
+ tas_priv->force_fwload_status = val;
+ }
+ dev_dbg(tas_priv->dev, "%s : Force FWload %s\n", __func__,
+ str_on_off(tas_priv->force_fwload_status));
+
+ return change;
+}
+
+static const struct snd_kcontrol_new tas2781_snd_controls[] = {
+ ACARD_SINGLE_RANGE_EXT_TLV("Speaker Analog Gain 0", TAS2781_AMP_LEVEL,
+ 1, 0, 20, 0, tas2781_amp_getvol,
+ tas2781_amp_putvol, amp_vol_tlv),
+ ACARD_SINGLE_RANGE_EXT_TLV("Speaker Digital Gain 0", TAS2781_DVC_LVL,
+ 0, 0, 200, 1, tas2781_digital_getvol,
+ tas2781_digital_putvol, dvc_tlv),
+ ACARD_SINGLE_BOOL_EXT("Speaker Force Firmware Load 0", 0,
+ tas2781_force_fwload_get, tas2781_force_fwload_put),
+ ACARD_SINGLE_RANGE_EXT_TLV("Speaker Analog Gain 1", TAS2781_AMP_LEVEL,
+ 1, 0, 20, 0, tas2781_amp_getvol,
+ tas2781_amp_putvol, amp_vol_tlv),
+ ACARD_SINGLE_RANGE_EXT_TLV("Speaker Digital Gain 1", TAS2781_DVC_LVL,
+ 0, 0, 200, 1, tas2781_digital_getvol,
+ tas2781_digital_putvol, dvc_tlv),
+ ACARD_SINGLE_BOOL_EXT("Speaker Force Firmware Load 1", 0,
+ tas2781_force_fwload_get, tas2781_force_fwload_put),
+};
+
+static const struct snd_kcontrol_new tas2781_prof_ctrl[] = {
+{
+ .name = "Speaker Profile Id - 0",
+ .iface = SNDRV_CTL_ELEM_IFACE_CARD,
+ .info = tasdevice_info_profile,
+ .get = tasdevice_get_profile_id,
+ .put = tasdevice_set_profile_id,
+},
+{
+ .name = "Speaker Profile Id - 1",
+ .iface = SNDRV_CTL_ELEM_IFACE_CARD,
+ .info = tasdevice_info_profile,
+ .get = tasdevice_get_profile_id,
+ .put = tasdevice_set_profile_id,
+},
+};
+static const struct snd_kcontrol_new tas2781_dsp_prog_ctrl[] = {
+{
+ .name = "Speaker Program Id 0",
+ .iface = SNDRV_CTL_ELEM_IFACE_CARD,
+ .info = tasdevice_info_programs,
+ .get = tasdevice_program_get,
+ .put = tasdevice_program_put,
+},
+{
+ .name = "Speaker Program Id 1",
+ .iface = SNDRV_CTL_ELEM_IFACE_CARD,
+ .info = tasdevice_info_programs,
+ .get = tasdevice_program_get,
+ .put = tasdevice_program_put,
+},
+};
+
+static const struct snd_kcontrol_new tas2781_dsp_conf_ctrl[] = {
+{
+ .name = "Speaker Config Id 0",
+ .iface = SNDRV_CTL_ELEM_IFACE_CARD,
+ .info = tasdevice_info_config,
+ .get = tasdevice_config_get,
+ .put = tasdevice_config_put,
+},
+{
+ .name = "Speaker Config Id 1",
+ .iface = SNDRV_CTL_ELEM_IFACE_CARD,
+ .info = tasdevice_info_config,
+ .get = tasdevice_config_get,
+ .put = tasdevice_config_put,
+},
+};
+
+static void tas2781_apply_calib(struct tasdevice_priv *tas_priv)
+{
+ int i, rc;
+
+ /*
+ * If no calibration data exist in tasdevice_priv *tas_priv,
+ * calibration apply will be ignored, and use default values
+ * in firmware binary, which was loaded during firmware download.
+ */
+ if (tas_priv->cali_data[0] == 0)
+ return;
+ /*
+ * Calibration data was saved in tasdevice_priv *tas_priv as:
+ * unsigned int cali_data[CALIB_MAX];
+ * and every data (in 4 bytes) will be saved in register which in
+ * book 0, and page number in page_array[], offset was saved in
+ * rgno_array[].
+ */
+ for (i = 0; i < CALIB_MAX; i++) {
+ rc = tasdevice_spi_dev_bulk_write(tas_priv,
+ tas_priv->cali_reg_array[i],
+ (unsigned char *)&tas_priv->cali_data[i], 4);
+ if (rc < 0)
+ dev_err(tas_priv->dev,
+ "chn %d calib %d bulk_wr err = %d\n",
+ tas_priv->index, i, rc);
+ }
+}
+
+/*
+ * Update the calibration data, including speaker impedance, f0, etc,
+ * into algo. Calibrate data is done by manufacturer in the factory.
+ * These data are used by Algo for calculating the speaker temperature,
+ * speaker membrane excursion and f0 in real time during playback.
+ * Calibration data format in EFI is V2, since 2024.
+ */
+static int tas2781_save_calibration(struct tasdevice_priv *tas_priv)
+{
+ /*
+ * GUID was used for data access in BIOS, it was provided by board
+ * manufactory, like HP: "{02f9af02-7734-4233-b43d-93fe5aa35db3}"
+ */
+ efi_guid_t efi_guid =
+ EFI_GUID(0x02f9af02, 0x7734, 0x4233,
+ 0xb4, 0x3d, 0x93, 0xfe, 0x5a, 0xa3, 0x5d, 0xb3);
+ static efi_char16_t efi_name[] = TASDEVICE_CALIBRATION_DATA_NAME;
+ unsigned char data[TASDEVICE_CALIBRATION_DATA_SIZE], *buf;
+ unsigned int attr, crc, offset, *tmp_val;
+ struct tm *tm = &tas_priv->tm;
+ unsigned long total_sz = 0;
+ efi_status_t status;
+
+ tas_priv->cali_data[0] = 0;
+ status = efi.get_variable(efi_name, &efi_guid, &attr, &total_sz, data);
+ if (status == EFI_BUFFER_TOO_SMALL) {
+ if (total_sz > TASDEVICE_CALIBRATION_DATA_SIZE)
+ return -ENOMEM;
+ /* Get variable contents into buffer */
+ status = efi.get_variable(efi_name, &efi_guid, &attr,
+ &total_sz, data);
+ }
+ if (status != EFI_SUCCESS)
+ return status;
+
+ tmp_val = (unsigned int *)data;
+ if (tmp_val[0] == 2781) {
+ /*
+ * New features were added in calibrated Data V3:
+ * 1. Added calibration registers address define in
+ * a node, marked as Device id == 0x80.
+ * New features were added in calibrated Data V2:
+ * 1. Added some the fields to store the link_id and
+ * uniqie_id for multi-link solutions
+ * 2. Support flexible number of devices instead of
+ * fixed one in V1.
+ * Layout of calibrated data V2 in UEFI(total 256 bytes):
+ * ChipID (2781, 4 bytes)
+ * Device-Sum (4 bytes)
+ * TimeStamp of Calibration (4 bytes)
+ * for (i = 0; i < Device-Sum; i++) {
+ * Device #i index_info () {
+ * SDW link id (2bytes)
+ * SDW unique_id (2bytes)
+ * } // if Device number is 0x80, mean it's
+ * calibration registers address.
+ * Calibrated Data of Device #i (20 bytes)
+ * }
+ * CRC (4 bytes)
+ * Reserved (the rest)
+ */
+ crc = crc32(~0, data, (3 + tmp_val[1] * 6) * 4) ^ ~0;
+
+ if (crc != tmp_val[3 + tmp_val[1] * 6])
+ return 0;
+
+ time64_to_tm(tmp_val[2], 0, tm);
+ for (int j = 0; j < tmp_val[1]; j++) {
+ offset = j * 6 + 3;
+ if (tmp_val[offset] == tas_priv->index) {
+ for (int i = 0; i < CALIB_MAX; i++)
+ tas_priv->cali_data[i] =
+ tmp_val[offset + i + 1];
+ } else if (tmp_val[offset] ==
+ TASDEVICE_CALIBRATION_REG_ADDRESS) {
+ for (int i = 0; i < CALIB_MAX; i++) {
+ buf = &data[(offset + i + 1) * 4];
+ tas_priv->cali_reg_array[i] =
+ TASDEVICE_REG(buf[1], buf[2],
+ buf[3]);
+ }
+ }
+ tas_priv->apply_calibration(tas_priv);
+ }
+ } else {
+ /*
+ * Calibration data is in V1 format.
+ * struct cali_data {
+ * char cali_data[20];
+ * }
+ *
+ * struct {
+ * struct cali_data cali_data[4];
+ * int TimeStamp of Calibration (4 bytes)
+ * int CRC (4 bytes)
+ * } ueft;
+ */
+ crc = crc32(~0, data, 84) ^ ~0;
+ if (crc == tmp_val[21]) {
+ time64_to_tm(tmp_val[20], 0, tm);
+ for (int i = 0; i < CALIB_MAX; i++)
+ tas_priv->cali_data[i] =
+ tmp_val[tas_priv->index * 5 + i];
+ tas_priv->apply_calibration(tas_priv);
+ }
+ }
+
+ return 0;
+}
+
+static void tas2781_hda_remove_controls(struct tas2781_hda *tas_hda)
+{
+ struct hda_codec *codec = tas_hda->priv->codec;
+
+ snd_ctl_remove(codec->card, tas_hda->dsp_prog_ctl);
+
+ snd_ctl_remove(codec->card, tas_hda->dsp_conf_ctl);
+
+ for (int i = ARRAY_SIZE(tas_hda->snd_ctls) - 1; i >= 0; i--)
+ snd_ctl_remove(codec->card, tas_hda->snd_ctls[i]);
+
+ snd_ctl_remove(codec->card, tas_hda->prof_ctl);
+}
+
+static void tasdev_fw_ready(const struct firmware *fmw, void *context)
+{
+ struct tasdevice_priv *tas_priv = context;
+ struct tas2781_hda *tas_hda = dev_get_drvdata(tas_priv->dev);
+ struct hda_codec *codec = tas_priv->codec;
+ int i, j, ret;
+
+ pm_runtime_get_sync(tas_priv->dev);
+ guard(mutex)(&tas_priv->codec_lock);
+
+ ret = tasdevice_spi_rca_parser(tas_priv, fmw);
+ if (ret)
+ goto out;
+
+ /* Add control one time only. */
+ tas_hda->prof_ctl = snd_ctl_new1(&tas2781_prof_ctrl[tas_priv->index],
+ tas_priv);
+ ret = snd_ctl_add(codec->card, tas_hda->prof_ctl);
+ if (ret) {
+ dev_err(tas_priv->dev, "Failed to add KControl %s = %d\n",
+ tas2781_prof_ctrl[tas_priv->index].name, ret);
+ goto out;
+ }
+ j = tas_priv->index * ARRAY_SIZE(tas2781_snd_controls) / 2;
+ for (i = 0; i < 3; i++) {
+ tas_hda->snd_ctls[i] = snd_ctl_new1(&tas2781_snd_controls[i+j],
+ tas_priv);
+ ret = snd_ctl_add(codec->card, tas_hda->snd_ctls[i]);
+ if (ret) {
+ dev_err(tas_priv->dev,
+ "Failed to add KControl %s = %d\n",
+ tas2781_snd_controls[i+tas_priv->index*3].name,
+ ret);
+ goto out;
+ }
+ }
+
+ tasdevice_spi_dsp_remove(tas_priv);
+
+ tas_priv->fw_state = TASDEVICE_DSP_FW_PENDING;
+ scnprintf(tas_priv->coef_binaryname, 64, "TAS2XXX%08X-%01d.bin",
+ codec->core.subsystem_id, tas_priv->index);
+ ret = tasdevice_spi_dsp_parser(tas_priv);
+ if (ret) {
+ dev_err(tas_priv->dev, "dspfw load %s error\n",
+ tas_priv->coef_binaryname);
+ tas_priv->fw_state = TASDEVICE_DSP_FW_FAIL;
+ goto out;
+ }
+
+ /* Add control one time only. */
+ tas_hda->dsp_prog_ctl =
+ snd_ctl_new1(&tas2781_dsp_prog_ctrl[tas_priv->index],
+ tas_priv);
+ ret = snd_ctl_add(codec->card, tas_hda->dsp_prog_ctl);
+ if (ret) {
+ dev_err(tas_priv->dev,
+ "Failed to add KControl %s = %d\n",
+ tas2781_dsp_prog_ctrl[tas_priv->index].name, ret);
+ goto out;
+ }
+
+ tas_hda->dsp_conf_ctl =
+ snd_ctl_new1(&tas2781_dsp_conf_ctrl[tas_priv->index],
+ tas_priv);
+ ret = snd_ctl_add(codec->card, tas_hda->dsp_conf_ctl);
+ if (ret) {
+ dev_err(tas_priv->dev, "Failed to add KControl %s = %d\n",
+ tas2781_dsp_conf_ctrl[tas_priv->index].name, ret);
+ goto out;
+ }
+
+ /* Perform AMP reset before firmware download. */
+ tas_priv->rcabin.profile_cfg_id = TAS2781_PRE_POST_RESET_CFG;
+ tasdevice_spi_tuning_switch(tas_priv, 0);
+ tas2781_spi_reset(tas_priv);
+ tas_priv->rcabin.profile_cfg_id = 0;
+ tasdevice_spi_tuning_switch(tas_priv, 1);
+
+ tas_priv->fw_state = TASDEVICE_DSP_FW_ALL_OK;
+ ret = tasdevice_spi_prmg_load(tas_priv, 0);
+ if (ret < 0) {
+ dev_err(tas_priv->dev, "FW download failed = %d\n", ret);
+ goto out;
+ }
+ if (tas_priv->fmw->nr_programs > 0)
+ tas_priv->cur_prog = 0;
+ if (tas_priv->fmw->nr_configurations > 0)
+ tas_priv->cur_conf = 0;
+
+ /*
+ * If calibrated data occurs error, dsp will still works with default
+ * calibrated data inside algo.
+ */
+ tas_priv->save_calibration(tas_priv);
+
+out:
+ if (fmw)
+ release_firmware(fmw);
+ pm_runtime_mark_last_busy(tas_hda->priv->dev);
+ pm_runtime_put_autosuspend(tas_hda->priv->dev);
+}
+
+static int tas2781_hda_bind(struct device *dev, struct device *master,
+ void *master_data)
+{
+ struct tas2781_hda *tas_hda = dev_get_drvdata(dev);
+ struct hda_component_parent *parent = master_data;
+ struct hda_component *comp;
+ struct hda_codec *codec;
+ int ret;
+
+ comp = hda_component_from_index(parent, tas_hda->priv->index);
+ if (!comp)
+ return -EINVAL;
+
+ if (comp->dev)
+ return -EBUSY;
+
+ codec = parent->codec;
+
+ pm_runtime_get_sync(dev);
+
+ comp->dev = dev;
+
+ strscpy(comp->name, dev_name(dev), sizeof(comp->name));
+
+ ret = tascodec_spi_init(tas_hda->priv, codec, THIS_MODULE,
+ tasdev_fw_ready);
+ if (!ret)
+ comp->playback_hook = tas2781_hda_playback_hook;
+
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
+
+ return ret;
+}
+
+static void tas2781_hda_unbind(struct device *dev, struct device *master,
+ void *master_data)
+{
+ struct tas2781_hda *tas_hda = dev_get_drvdata(dev);
+ struct hda_component_parent *parent = master_data;
+ struct hda_component *comp;
+
+ comp = hda_component_from_index(parent, tas_hda->priv->index);
+ if (comp && (comp->dev == dev)) {
+ comp->dev = NULL;
+ memset(comp->name, 0, sizeof(comp->name));
+ comp->playback_hook = NULL;
+ }
+
+ tas2781_hda_remove_controls(tas_hda);
+
+ tasdevice_spi_config_info_remove(tas_hda->priv);
+ tasdevice_spi_dsp_remove(tas_hda->priv);
+
+ tas_hda->priv->fw_state = TASDEVICE_DSP_FW_PENDING;
+}
+
+static const struct component_ops tas2781_hda_comp_ops = {
+ .bind = tas2781_hda_bind,
+ .unbind = tas2781_hda_unbind,
+};
+
+static void tas2781_hda_remove(struct device *dev)
+{
+ struct tas2781_hda *tas_hda = dev_get_drvdata(dev);
+
+ component_del(tas_hda->priv->dev, &tas2781_hda_comp_ops);
+
+ pm_runtime_get_sync(tas_hda->priv->dev);
+ pm_runtime_disable(tas_hda->priv->dev);
+
+ pm_runtime_put_noidle(tas_hda->priv->dev);
+
+ mutex_destroy(&tas_hda->priv->codec_lock);
+}
+
+static int tas2781_hda_spi_probe(struct spi_device *spi)
+{
+ struct tasdevice_priv *tas_priv;
+ struct tas2781_hda *tas_hda;
+ const char *device_name;
+ int ret = 0;
+
+ tas_hda = devm_kzalloc(&spi->dev, sizeof(*tas_hda), GFP_KERNEL);
+ if (!tas_hda)
+ return -ENOMEM;
+
+ spi->max_speed_hz = TAS2781_SPI_MAX_FREQ;
+
+ tas_priv = devm_kzalloc(&spi->dev, sizeof(*tas_priv), GFP_KERNEL);
+ if (!tas_priv)
+ goto err;
+ tas_priv->dev = &spi->dev;
+ tas_hda->priv = tas_priv;
+ tas_priv->regmap = devm_regmap_init_spi(spi, &tasdevice_regmap);
+ if (IS_ERR(tas_priv->regmap)) {
+ ret = PTR_ERR(tas_priv->regmap);
+ dev_err(tas_priv->dev, "Failed to allocate regmap: %d\n",
+ ret);
+ goto err;
+ }
+ if (strstr(dev_name(&spi->dev), "TXNW2781")) {
+ device_name = "TXNW2781";
+ tas_priv->save_calibration = tas2781_save_calibration;
+ tas_priv->apply_calibration = tas2781_apply_calib;
+ } else {
+ goto err;
+ }
+
+ tas_priv->irq = spi->irq;
+ dev_set_drvdata(&spi->dev, tas_hda);
+ ret = tas2781_read_acpi(tas_hda, device_name,
+ spi_get_chipselect(spi, 0));
+ if (ret)
+ return dev_err_probe(tas_priv->dev, ret,
+ "Platform not supported\n");
+
+ tasdevice_spi_init(tas_priv);
+
+ pm_runtime_set_autosuspend_delay(tas_priv->dev, 3000);
+ pm_runtime_use_autosuspend(tas_priv->dev);
+ pm_runtime_mark_last_busy(tas_priv->dev);
+ pm_runtime_set_active(tas_priv->dev);
+ pm_runtime_get_noresume(tas_priv->dev);
+ pm_runtime_enable(tas_priv->dev);
+
+ pm_runtime_put_autosuspend(tas_priv->dev);
+
+ ret = component_add(tas_priv->dev, &tas2781_hda_comp_ops);
+ if (ret) {
+ dev_err(tas_priv->dev, "Register component fail: %d\n", ret);
+ pm_runtime_disable(tas_priv->dev);
+ }
+
+err:
+ if (ret)
+ tas2781_hda_remove(&spi->dev);
+
+ return ret;
+}
+
+static void tas2781_hda_spi_remove(struct spi_device *spi)
+{
+ tas2781_hda_remove(&spi->dev);
+}
+
+static int tas2781_runtime_suspend(struct device *dev)
+{
+ struct tas2781_hda *tas_hda = dev_get_drvdata(dev);
+
+ guard(mutex)(&tas_hda->priv->codec_lock);
+
+ tasdevice_spi_tuning_switch(tas_hda->priv, 1);
+
+ tas_hda->priv->cur_book = -1;
+ tas_hda->priv->cur_conf = -1;
+
+ return 0;
+}
+
+static int tas2781_runtime_resume(struct device *dev)
+{
+ struct tas2781_hda *tas_hda = dev_get_drvdata(dev);
+
+ guard(mutex)(&tas_hda->priv->codec_lock);
+
+ tasdevice_spi_tuning_switch(tas_hda->priv, 0);
+
+ return 0;
+}
+
+static int tas2781_system_suspend(struct device *dev)
+{
+ struct tas2781_hda *tas_hda = dev_get_drvdata(dev);
+ int ret;
+
+ ret = pm_runtime_force_suspend(dev);
+ if (ret)
+ return ret;
+
+ /* Shutdown chip before system suspend */
+ tasdevice_spi_tuning_switch(tas_hda->priv, 1);
+ tas2781_spi_reset(tas_hda->priv);
+ /*
+ * Reset GPIO may be shared, so cannot reset here.
+ * However beyond this point, amps may be powered down.
+ */
+ return 0;
+}
+
+static int tas2781_system_resume(struct device *dev)
+{
+ struct tas2781_hda *tas_hda = dev_get_drvdata(dev);
+ int ret, val;
+
+ ret = pm_runtime_force_resume(dev);
+ if (ret)
+ return ret;
+
+ guard(mutex)(&tas_hda->priv->codec_lock);
+ ret = tasdevice_spi_dev_read(tas_hda->priv, TAS2781_REG_CLK_CONFIG,
+ &val);
+ if (ret < 0)
+ return ret;
+
+ if (val == TAS2781_REG_CLK_CONFIG_RESET) {
+ tas_hda->priv->cur_book = -1;
+ tas_hda->priv->cur_conf = -1;
+ tas_hda->priv->cur_prog = -1;
+
+ ret = tasdevice_spi_prmg_load(tas_hda->priv, 0);
+ if (ret < 0) {
+ dev_err(tas_hda->priv->dev,
+ "FW download failed = %d\n", ret);
+ return ret;
+ }
+
+ if (tas_hda->priv->playback_started)
+ tasdevice_spi_tuning_switch(tas_hda->priv, 0);
+ }
+
+ return ret;
+}
+
+static const struct dev_pm_ops tas2781_hda_pm_ops = {
+ RUNTIME_PM_OPS(tas2781_runtime_suspend, tas2781_runtime_resume, NULL)
+ SYSTEM_SLEEP_PM_OPS(tas2781_system_suspend, tas2781_system_resume)
+};
+
+static const struct spi_device_id tas2781_hda_spi_id[] = {
+ { "tas2781-hda", },
+ {}
+};
+
+static const struct acpi_device_id tas2781_acpi_hda_match[] = {
+ {"TXNW2781", },
+ {}
+};
+MODULE_DEVICE_TABLE(acpi, tas2781_acpi_hda_match);
+
+static struct spi_driver tas2781_hda_spi_driver = {
+ .driver = {
+ .name = "tas2781-hda",
+ .acpi_match_table = tas2781_acpi_hda_match,
+ .pm = &tas2781_hda_pm_ops,
+ },
+ .id_table = tas2781_hda_spi_id,
+ .probe = tas2781_hda_spi_probe,
+ .remove = tas2781_hda_spi_remove,
+};
+module_spi_driver(tas2781_hda_spi_driver);
+
+MODULE_DESCRIPTION("TAS2781 HDA SPI Driver");
+MODULE_AUTHOR("Baojun, Xu, <baojun.xug@ti.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/pci/hda/tas2781_spi_fwlib.c b/sound/pci/hda/tas2781_spi_fwlib.c
new file mode 100644
index 000000000000..0e2acbc3c900
--- /dev/null
+++ b/sound/pci/hda/tas2781_spi_fwlib.c
@@ -0,0 +1,2006 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// TAS2781 HDA SPI driver
+//
+// Copyright 2024 Texas Instruments, Inc.
+//
+// Author: Baojun Xu <baojun.xu@ti.com>
+
+#include <linux/crc8.h>
+#include <linux/firmware.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/unaligned.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/tas2781-dsp.h>
+#include <sound/tlv.h>
+
+#include "tas2781-spi.h"
+
+#define OFFSET_ERROR_BIT BIT(31)
+
+#define ERROR_PRAM_CRCCHK 0x0000000
+#define ERROR_YRAM_CRCCHK 0x0000001
+#define PPC_DRIVER_CRCCHK 0x00000200
+
+#define TAS2781_SA_COEFF_SWAP_REG TASDEVICE_REG(0, 0x35, 0x2c)
+#define TAS2781_YRAM_BOOK1 140
+#define TAS2781_YRAM1_PAGE 42
+#define TAS2781_YRAM1_START_REG 88
+
+#define TAS2781_YRAM2_START_PAGE 43
+#define TAS2781_YRAM2_END_PAGE 49
+#define TAS2781_YRAM2_START_REG 8
+#define TAS2781_YRAM2_END_REG 127
+
+#define TAS2781_YRAM3_PAGE 50
+#define TAS2781_YRAM3_START_REG 8
+#define TAS2781_YRAM3_END_REG 27
+
+/* should not include B0_P53_R44-R47 */
+#define TAS2781_YRAM_BOOK2 0
+#define TAS2781_YRAM4_START_PAGE 50
+#define TAS2781_YRAM4_END_PAGE 60
+
+#define TAS2781_YRAM5_PAGE 61
+#define TAS2781_YRAM5_START_REG TAS2781_YRAM3_START_REG
+#define TAS2781_YRAM5_END_REG TAS2781_YRAM3_END_REG
+
+#define TASDEVICE_MAXPROGRAM_NUM_KERNEL 5
+#define TASDEVICE_MAXCONFIG_NUM_KERNEL_MULTIPLE_AMPS 64
+#define TASDEVICE_MAXCONFIG_NUM_KERNEL 10
+#define MAIN_ALL_DEVICES_1X 0x01
+#define MAIN_DEVICE_A_1X 0x02
+#define MAIN_DEVICE_B_1X 0x03
+#define MAIN_DEVICE_C_1X 0x04
+#define MAIN_DEVICE_D_1X 0x05
+#define COEFF_DEVICE_A_1X 0x12
+#define COEFF_DEVICE_B_1X 0x13
+#define COEFF_DEVICE_C_1X 0x14
+#define COEFF_DEVICE_D_1X 0x15
+#define PRE_DEVICE_A_1X 0x22
+#define PRE_DEVICE_B_1X 0x23
+#define PRE_DEVICE_C_1X 0x24
+#define PRE_DEVICE_D_1X 0x25
+#define PRE_SOFTWARE_RESET_DEVICE_A 0x41
+#define PRE_SOFTWARE_RESET_DEVICE_B 0x42
+#define PRE_SOFTWARE_RESET_DEVICE_C 0x43
+#define PRE_SOFTWARE_RESET_DEVICE_D 0x44
+#define POST_SOFTWARE_RESET_DEVICE_A 0x45
+#define POST_SOFTWARE_RESET_DEVICE_B 0x46
+#define POST_SOFTWARE_RESET_DEVICE_C 0x47
+#define POST_SOFTWARE_RESET_DEVICE_D 0x48
+
+struct tas_crc {
+ unsigned char offset;
+ unsigned char len;
+};
+
+struct blktyp_devidx_map {
+ unsigned char blktyp;
+ unsigned char dev_idx;
+};
+
+/* fixed m68k compiling issue: mapping table can save code field */
+static const struct blktyp_devidx_map ppc3_tas2781_mapping_table[] = {
+ { MAIN_ALL_DEVICES_1X, 0x80 },
+ { MAIN_DEVICE_A_1X, 0x81 },
+ { COEFF_DEVICE_A_1X, 0x81 },
+ { PRE_DEVICE_A_1X, 0x81 },
+ { PRE_SOFTWARE_RESET_DEVICE_A, 0xC1 },
+ { POST_SOFTWARE_RESET_DEVICE_A, 0xC1 },
+ { MAIN_DEVICE_B_1X, 0x82 },
+ { COEFF_DEVICE_B_1X, 0x82 },
+ { PRE_DEVICE_B_1X, 0x82 },
+ { PRE_SOFTWARE_RESET_DEVICE_B, 0xC2 },
+ { POST_SOFTWARE_RESET_DEVICE_B, 0xC2 },
+ { MAIN_DEVICE_C_1X, 0x83 },
+ { COEFF_DEVICE_C_1X, 0x83 },
+ { PRE_DEVICE_C_1X, 0x83 },
+ { PRE_SOFTWARE_RESET_DEVICE_C, 0xC3 },
+ { POST_SOFTWARE_RESET_DEVICE_C, 0xC3 },
+ { MAIN_DEVICE_D_1X, 0x84 },
+ { COEFF_DEVICE_D_1X, 0x84 },
+ { PRE_DEVICE_D_1X, 0x84 },
+ { PRE_SOFTWARE_RESET_DEVICE_D, 0xC4 },
+ { POST_SOFTWARE_RESET_DEVICE_D, 0xC4 },
+};
+
+static const struct blktyp_devidx_map ppc3_mapping_table[] = {
+ { MAIN_ALL_DEVICES_1X, 0x80 },
+ { MAIN_DEVICE_A_1X, 0x81 },
+ { COEFF_DEVICE_A_1X, 0xC1 },
+ { PRE_DEVICE_A_1X, 0xC1 },
+ { MAIN_DEVICE_B_1X, 0x82 },
+ { COEFF_DEVICE_B_1X, 0xC2 },
+ { PRE_DEVICE_B_1X, 0xC2 },
+ { MAIN_DEVICE_C_1X, 0x83 },
+ { COEFF_DEVICE_C_1X, 0xC3 },
+ { PRE_DEVICE_C_1X, 0xC3 },
+ { MAIN_DEVICE_D_1X, 0x84 },
+ { COEFF_DEVICE_D_1X, 0xC4 },
+ { PRE_DEVICE_D_1X, 0xC4 },
+};
+
+static const struct blktyp_devidx_map non_ppc3_mapping_table[] = {
+ { MAIN_ALL_DEVICES, 0x80 },
+ { MAIN_DEVICE_A, 0x81 },
+ { COEFF_DEVICE_A, 0xC1 },
+ { PRE_DEVICE_A, 0xC1 },
+ { MAIN_DEVICE_B, 0x82 },
+ { COEFF_DEVICE_B, 0xC2 },
+ { PRE_DEVICE_B, 0xC2 },
+ { MAIN_DEVICE_C, 0x83 },
+ { COEFF_DEVICE_C, 0xC3 },
+ { PRE_DEVICE_C, 0xC3 },
+ { MAIN_DEVICE_D, 0x84 },
+ { COEFF_DEVICE_D, 0xC4 },
+ { PRE_DEVICE_D, 0xC4 },
+};
+
+/*
+ * Device support different configurations for different scene,
+ * like voice, music, calibration, was write in regbin file.
+ * Will be stored into tas_priv after regbin was loaded.
+ */
+static struct tasdevice_config_info *tasdevice_add_config(
+ struct tasdevice_priv *tas_priv, unsigned char *config_data,
+ unsigned int config_size, int *status)
+{
+ struct tasdevice_config_info *cfg_info;
+ struct tasdev_blk_data **bk_da;
+ unsigned int config_offset = 0;
+ unsigned int i;
+
+ /*
+ * In most projects are many audio cases, such as music, handfree,
+ * receiver, games, audio-to-haptics, PMIC record, bypass mode,
+ * portrait, landscape, etc. Even in multiple audios, one or
+ * two of the chips will work for the special case, such as
+ * ultrasonic application. In order to support these variable-numbers
+ * of audio cases, flexible configs have been introduced in the
+ * DSP firmware.
+ */
+ cfg_info = kzalloc(sizeof(*cfg_info), GFP_KERNEL);
+ if (!cfg_info) {
+ *status = -ENOMEM;
+ return NULL;
+ }
+
+ if (tas_priv->rcabin.fw_hdr.binary_version_num >= 0x105) {
+ if ((config_offset + 64) > config_size) {
+ *status = -EINVAL;
+ dev_err(tas_priv->dev, "add conf: Out of boundary\n");
+ goto config_err;
+ }
+ config_offset += 64;
+ }
+
+ if ((config_offset + 4) > config_size) {
+ *status = -EINVAL;
+ dev_err(tas_priv->dev, "add config: Out of boundary\n");
+ goto config_err;
+ }
+
+ /*
+ * convert data[offset], data[offset + 1], data[offset + 2] and
+ * data[offset + 3] into host
+ */
+ cfg_info->nblocks = get_unaligned_be32(&config_data[config_offset]);
+ config_offset += 4;
+
+ /*
+ * Several kinds of dsp/algorithm firmwares can run on tas2781,
+ * the number and size of blk are not fixed and different among
+ * these firmwares.
+ */
+ bk_da = cfg_info->blk_data = kcalloc(cfg_info->nblocks,
+ sizeof(*bk_da), GFP_KERNEL);
+ if (!bk_da) {
+ *status = -ENOMEM;
+ goto config_err;
+ }
+ cfg_info->real_nblocks = 0;
+ for (i = 0; i < cfg_info->nblocks; i++) {
+ if (config_offset + 12 > config_size) {
+ *status = -EINVAL;
+ dev_err(tas_priv->dev,
+ "%s: Out of boundary: i = %d nblocks = %u!\n",
+ __func__, i, cfg_info->nblocks);
+ goto block_err;
+ }
+ bk_da[i] = kzalloc(sizeof(*bk_da[i]), GFP_KERNEL);
+ if (!bk_da[i]) {
+ *status = -ENOMEM;
+ goto block_err;
+ }
+
+ bk_da[i]->dev_idx = config_data[config_offset];
+ config_offset++;
+
+ bk_da[i]->block_type = config_data[config_offset];
+ config_offset++;
+
+ bk_da[i]->yram_checksum =
+ get_unaligned_be16(&config_data[config_offset]);
+ config_offset += 2;
+ bk_da[i]->block_size =
+ get_unaligned_be32(&config_data[config_offset]);
+ config_offset += 4;
+
+ bk_da[i]->n_subblks =
+ get_unaligned_be32(&config_data[config_offset]);
+
+ config_offset += 4;
+
+ if (config_offset + bk_da[i]->block_size > config_size) {
+ *status = -EINVAL;
+ dev_err(tas_priv->dev,
+ "%s: Out of boundary: i = %d blks = %u!\n",
+ __func__, i, cfg_info->nblocks);
+ goto block_err;
+ }
+ /* instead of kzalloc+memcpy */
+ bk_da[i]->regdata = kmemdup(&config_data[config_offset],
+ bk_da[i]->block_size, GFP_KERNEL);
+ if (!bk_da[i]->regdata) {
+ *status = -ENOMEM;
+ i++;
+ goto block_err;
+ }
+
+ config_offset += bk_da[i]->block_size;
+ cfg_info->real_nblocks += 1;
+ }
+
+ return cfg_info;
+block_err:
+ for (int j = 0; j < i; j++)
+ kfree(bk_da[j]);
+ kfree(bk_da);
+config_err:
+ kfree(cfg_info);
+ return NULL;
+}
+
+/* Regbin file parser function. */
+int tasdevice_spi_rca_parser(void *context, const struct firmware *fmw)
+{
+ struct tasdevice_priv *tas_priv = context;
+ struct tasdevice_config_info **cfg_info;
+ struct tasdevice_rca_hdr *fw_hdr;
+ struct tasdevice_rca *rca;
+ unsigned int total_config_sz = 0;
+ int offset = 0, ret = 0, i;
+ unsigned char *buf;
+
+ rca = &tas_priv->rcabin;
+ fw_hdr = &rca->fw_hdr;
+ if (!fmw || !fmw->data) {
+ dev_err(tas_priv->dev, "Failed to read %s\n",
+ tas_priv->rca_binaryname);
+ tas_priv->fw_state = TASDEVICE_DSP_FW_FAIL;
+ return -EINVAL;
+ }
+ buf = (unsigned char *)fmw->data;
+ fw_hdr->img_sz = get_unaligned_be32(&buf[offset]);
+ offset += 4;
+ if (fw_hdr->img_sz != fmw->size) {
+ dev_err(tas_priv->dev,
+ "File size not match, %d %u", (int)fmw->size,
+ fw_hdr->img_sz);
+ tas_priv->fw_state = TASDEVICE_DSP_FW_FAIL;
+ return -EINVAL;
+ }
+
+ fw_hdr->checksum = get_unaligned_be32(&buf[offset]);
+ offset += 4;
+ fw_hdr->binary_version_num = get_unaligned_be32(&buf[offset]);
+ if (fw_hdr->binary_version_num < 0x103) {
+ dev_err(tas_priv->dev, "File version 0x%04x is too low",
+ fw_hdr->binary_version_num);
+ tas_priv->fw_state = TASDEVICE_DSP_FW_FAIL;
+ return -EINVAL;
+ }
+ offset += 4;
+ fw_hdr->drv_fw_version = get_unaligned_be32(&buf[offset]);
+ offset += 8;
+ fw_hdr->plat_type = buf[offset++];
+ fw_hdr->dev_family = buf[offset++];
+ fw_hdr->reserve = buf[offset++];
+ fw_hdr->ndev = buf[offset++];
+ if (offset + TASDEVICE_DEVICE_SUM > fw_hdr->img_sz) {
+ dev_err(tas_priv->dev, "rca_ready: Out of boundary!\n");
+ tas_priv->fw_state = TASDEVICE_DSP_FW_FAIL;
+ return -EINVAL;
+ }
+
+ for (i = 0; i < TASDEVICE_DEVICE_SUM; i++, offset++)
+ fw_hdr->devs[i] = buf[offset];
+
+ fw_hdr->nconfig = get_unaligned_be32(&buf[offset]);
+ offset += 4;
+
+ for (i = 0; i < TASDEVICE_CONFIG_SUM; i++) {
+ fw_hdr->config_size[i] = get_unaligned_be32(&buf[offset]);
+ offset += 4;
+ total_config_sz += fw_hdr->config_size[i];
+ }
+
+ if (fw_hdr->img_sz - total_config_sz != (unsigned int)offset) {
+ dev_err(tas_priv->dev, "Bin file err %d - %d != %d!\n",
+ fw_hdr->img_sz, total_config_sz, (int)offset);
+ tas_priv->fw_state = TASDEVICE_DSP_FW_FAIL;
+ return -EINVAL;
+ }
+
+ cfg_info = kcalloc(fw_hdr->nconfig, sizeof(*cfg_info), GFP_KERNEL);
+ if (!cfg_info) {
+ tas_priv->fw_state = TASDEVICE_DSP_FW_FAIL;
+ return -ENOMEM;
+ }
+ rca->cfg_info = cfg_info;
+ rca->ncfgs = 0;
+ for (i = 0; i < (int)fw_hdr->nconfig; i++) {
+ rca->ncfgs += 1;
+ cfg_info[i] = tasdevice_add_config(tas_priv, &buf[offset],
+ fw_hdr->config_size[i], &ret);
+ if (ret) {
+ tas_priv->fw_state = TASDEVICE_DSP_FW_FAIL;
+ return ret;
+ }
+ offset += (int)fw_hdr->config_size[i];
+ }
+
+ return ret;
+}
+
+/* fixed m68k compiling issue: mapping table can save code field */
+static unsigned char map_dev_idx(struct tasdevice_fw *tas_fmw,
+ struct tasdev_blk *block)
+{
+ struct blktyp_devidx_map *p =
+ (struct blktyp_devidx_map *)non_ppc3_mapping_table;
+ struct tasdevice_dspfw_hdr *fw_hdr = &tas_fmw->fw_hdr;
+ struct tasdevice_fw_fixed_hdr *fw_fixed_hdr = &fw_hdr->fixed_hdr;
+ int i, n = ARRAY_SIZE(non_ppc3_mapping_table);
+ unsigned char dev_idx = 0;
+
+ if (fw_fixed_hdr->ppcver >= PPC3_VERSION_TAS2781) {
+ p = (struct blktyp_devidx_map *)ppc3_tas2781_mapping_table;
+ n = ARRAY_SIZE(ppc3_tas2781_mapping_table);
+ } else if (fw_fixed_hdr->ppcver >= PPC3_VERSION) {
+ p = (struct blktyp_devidx_map *)ppc3_mapping_table;
+ n = ARRAY_SIZE(ppc3_mapping_table);
+ }
+
+ for (i = 0; i < n; i++) {
+ if (block->type == p[i].blktyp) {
+ dev_idx = p[i].dev_idx;
+ break;
+ }
+ }
+
+ return dev_idx;
+}
+
+/* Block parser function. */
+static int fw_parse_block_data_kernel(struct tasdevice_fw *tas_fmw,
+ struct tasdev_blk *block, const struct firmware *fmw, int offset)
+{
+ const unsigned char *data = fmw->data;
+
+ if (offset + 16 > fmw->size) {
+ dev_err(tas_fmw->dev, "%s: File Size error\n", __func__);
+ return -EINVAL;
+ }
+
+ /*
+ * Convert data[offset], data[offset + 1], data[offset + 2] and
+ * data[offset + 3] into host.
+ */
+ block->type = get_unaligned_be32(&data[offset]);
+ offset += 4;
+
+ block->is_pchksum_present = data[offset++];
+ block->pchksum = data[offset++];
+ block->is_ychksum_present = data[offset++];
+ block->ychksum = data[offset++];
+ block->blk_size = get_unaligned_be32(&data[offset]);
+ offset += 4;
+ block->nr_subblocks = get_unaligned_be32(&data[offset]);
+ offset += 4;
+
+ /*
+ * Fixed m68k compiling issue:
+ * 1. mapping table can save code field.
+ * 2. storing the dev_idx as a member of block can reduce unnecessary
+ * time and system resource comsumption of dev_idx mapping every
+ * time the block data writing to the dsp.
+ */
+ block->dev_idx = map_dev_idx(tas_fmw, block);
+
+ if (offset + block->blk_size > fmw->size) {
+ dev_err(tas_fmw->dev, "%s: nSublocks error\n", __func__);
+ return -EINVAL;
+ }
+ /* instead of kzalloc+memcpy */
+ block->data = kmemdup(&data[offset], block->blk_size, GFP_KERNEL);
+ if (!block->data)
+ return -ENOMEM;
+
+ offset += block->blk_size;
+
+ return offset;
+}
+
+/* Data of block parser function. */
+static int fw_parse_data_kernel(struct tasdevice_fw *tas_fmw,
+ struct tasdevice_data *img_data, const struct firmware *fmw,
+ int offset)
+{
+ const unsigned char *data = fmw->data;
+ struct tasdev_blk *blk;
+ unsigned int i;
+
+ if (offset + 4 > fmw->size) {
+ dev_err(tas_fmw->dev, "%s: File Size error\n", __func__);
+ return -EINVAL;
+ }
+ img_data->nr_blk = get_unaligned_be32(&data[offset]);
+ offset += 4;
+
+ img_data->dev_blks = kcalloc(img_data->nr_blk,
+ sizeof(struct tasdev_blk), GFP_KERNEL);
+ if (!img_data->dev_blks)
+ return -ENOMEM;
+
+ for (i = 0; i < img_data->nr_blk; i++) {
+ blk = &img_data->dev_blks[i];
+ offset = fw_parse_block_data_kernel(
+ tas_fmw, blk, fmw, offset);
+ if (offset < 0) {
+ kfree(img_data->dev_blks);
+ return -EINVAL;
+ }
+ }
+
+ return offset;
+}
+
+/* Data of DSP program parser function. */
+static int fw_parse_program_data_kernel(
+ struct tasdevice_priv *tas_priv, struct tasdevice_fw *tas_fmw,
+ const struct firmware *fmw, int offset)
+{
+ struct tasdevice_prog *program;
+ unsigned int i;
+
+ for (i = 0; i < tas_fmw->nr_programs; i++) {
+ program = &tas_fmw->programs[i];
+ if (offset + 72 > fmw->size) {
+ dev_err(tas_priv->dev, "%s: mpName error\n", __func__);
+ return -EINVAL;
+ }
+ /* skip 72 unused byts */
+ offset += 72;
+
+ offset = fw_parse_data_kernel(tas_fmw, &program->dev_data,
+ fmw, offset);
+ if (offset < 0)
+ break;
+ }
+
+ return offset;
+}
+
+/* Data of DSP configurations parser function. */
+static int fw_parse_configuration_data_kernel(struct tasdevice_priv *tas_priv,
+ struct tasdevice_fw *tas_fmw, const struct firmware *fmw, int offset)
+{
+ const unsigned char *data = fmw->data;
+ struct tasdevice_config *config;
+ unsigned int i;
+
+ for (i = 0; i < tas_fmw->nr_configurations; i++) {
+ config = &tas_fmw->configs[i];
+ if (offset + 80 > fmw->size) {
+ dev_err(tas_priv->dev, "%s: mpName error\n", __func__);
+ return -EINVAL;
+ }
+ memcpy(config->name, &data[offset], 64);
+ /* skip extra 16 bytes */
+ offset += 80;
+
+ offset = fw_parse_data_kernel(tas_fmw, &config->dev_data,
+ fmw, offset);
+ if (offset < 0)
+ break;
+ }
+
+ return offset;
+}
+
+/* DSP firmware file header parser function for early PPC3 firmware binary. */
+static int fw_parse_variable_header_kernel(struct tasdevice_priv *tas_priv,
+ const struct firmware *fmw, int offset)
+{
+ struct tasdevice_fw *tas_fmw = tas_priv->fmw;
+ struct tasdevice_dspfw_hdr *fw_hdr = &tas_fmw->fw_hdr;
+ struct tasdevice_config *config;
+ struct tasdevice_prog *program;
+ const unsigned char *buf = fmw->data;
+ unsigned short max_confs;
+ unsigned int i;
+
+ if (offset + 12 + 4 * TASDEVICE_MAXPROGRAM_NUM_KERNEL > fmw->size) {
+ dev_err(tas_priv->dev, "%s: File Size error\n", __func__);
+ return -EINVAL;
+ }
+ fw_hdr->device_family = get_unaligned_be16(&buf[offset]);
+ if (fw_hdr->device_family != 0) {
+ dev_err(tas_priv->dev, "%s:not TAS device\n", __func__);
+ return -EINVAL;
+ }
+ offset += 2;
+ fw_hdr->device = get_unaligned_be16(&buf[offset]);
+ if (fw_hdr->device >= TASDEVICE_DSP_TAS_MAX_DEVICE ||
+ fw_hdr->device == 6) {
+ dev_err(tas_priv->dev, "Unsupported dev %d\n", fw_hdr->device);
+ return -EINVAL;
+ }
+ offset += 2;
+
+ tas_fmw->nr_programs = get_unaligned_be32(&buf[offset]);
+ offset += 4;
+
+ if (tas_fmw->nr_programs == 0 ||
+ tas_fmw->nr_programs > TASDEVICE_MAXPROGRAM_NUM_KERNEL) {
+ dev_err(tas_priv->dev, "mnPrograms is invalid\n");
+ return -EINVAL;
+ }
+
+ tas_fmw->programs = kcalloc(tas_fmw->nr_programs,
+ sizeof(*tas_fmw->programs), GFP_KERNEL);
+ if (!tas_fmw->programs)
+ return -ENOMEM;
+
+ for (i = 0; i < tas_fmw->nr_programs; i++) {
+ program = &tas_fmw->programs[i];
+ program->prog_size = get_unaligned_be32(&buf[offset]);
+ offset += 4;
+ }
+
+ /* Skip the unused prog_size */
+ offset += 4 * (TASDEVICE_MAXPROGRAM_NUM_KERNEL - tas_fmw->nr_programs);
+
+ tas_fmw->nr_configurations = get_unaligned_be32(&buf[offset]);
+ offset += 4;
+
+ /*
+ * The max number of config in firmware greater than 4 pieces of
+ * tas2781s is different from the one lower than 4 pieces of
+ * tas2781s.
+ */
+ max_confs = TASDEVICE_MAXCONFIG_NUM_KERNEL;
+ if (tas_fmw->nr_configurations == 0 ||
+ tas_fmw->nr_configurations > max_confs) {
+ dev_err(tas_priv->dev, "%s: Conf is invalid\n", __func__);
+ kfree(tas_fmw->programs);
+ return -EINVAL;
+ }
+
+ if (offset + 4 * max_confs > fmw->size) {
+ dev_err(tas_priv->dev, "%s: mpConfigurations err\n", __func__);
+ kfree(tas_fmw->programs);
+ return -EINVAL;
+ }
+
+ tas_fmw->configs = kcalloc(tas_fmw->nr_configurations,
+ sizeof(*tas_fmw->configs), GFP_KERNEL);
+ if (!tas_fmw->configs) {
+ kfree(tas_fmw->programs);
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < tas_fmw->nr_programs; i++) {
+ config = &tas_fmw->configs[i];
+ config->cfg_size = get_unaligned_be32(&buf[offset]);
+ offset += 4;
+ }
+
+ /* Skip the unused configs */
+ offset += 4 * (max_confs - tas_fmw->nr_programs);
+
+ return offset;
+}
+
+/*
+ * In sub-block data, have three type sub-block:
+ * 1. Single byte write.
+ * 2. Multi-byte write.
+ * 3. Delay.
+ * 4. Bits update.
+ * This function perform single byte write to device.
+ */
+static int tasdevice_single_byte_wr(void *context, int dev_idx,
+ unsigned char *data, int sublocksize)
+{
+ struct tasdevice_priv *tas_priv = context;
+ unsigned short len = get_unaligned_be16(&data[2]);
+ int i, subblk_offset, rc;
+
+ subblk_offset = 4;
+ if (subblk_offset + 4 * len > sublocksize) {
+ dev_err(tas_priv->dev, "process_block: Out of boundary\n");
+ return 0;
+ }
+
+ for (i = 0; i < len; i++) {
+ if (dev_idx == (tas_priv->index + 1) || dev_idx == 0) {
+ rc = tasdevice_spi_dev_write(tas_priv,
+ TASDEVICE_REG(data[subblk_offset],
+ data[subblk_offset + 1],
+ data[subblk_offset + 2]),
+ data[subblk_offset + 3]);
+ if (rc < 0) {
+ dev_err(tas_priv->dev,
+ "process_block: single write error\n");
+ subblk_offset |= OFFSET_ERROR_BIT;
+ }
+ }
+ subblk_offset += 4;
+ }
+
+ return subblk_offset;
+}
+
+/*
+ * In sub-block data, have three type sub-block:
+ * 1. Single byte write.
+ * 2. Multi-byte write.
+ * 3. Delay.
+ * 4. Bits update.
+ * This function perform multi-write to device.
+ */
+static int tasdevice_burst_wr(void *context, int dev_idx, unsigned char *data,
+ int sublocksize)
+{
+ struct tasdevice_priv *tas_priv = context;
+ unsigned short len = get_unaligned_be16(&data[2]);
+ int subblk_offset, rc;
+
+ subblk_offset = 4;
+ if (subblk_offset + 4 + len > sublocksize) {
+ dev_err(tas_priv->dev, "%s: BST Out of boundary\n", __func__);
+ subblk_offset |= OFFSET_ERROR_BIT;
+ }
+ if (len % 4) {
+ dev_err(tas_priv->dev, "%s:Bst-len(%u)not div by 4\n",
+ __func__, len);
+ subblk_offset |= OFFSET_ERROR_BIT;
+ }
+
+ if (dev_idx == (tas_priv->index + 1) || dev_idx == 0) {
+ rc = tasdevice_spi_dev_bulk_write(tas_priv,
+ TASDEVICE_REG(data[subblk_offset],
+ data[subblk_offset + 1],
+ data[subblk_offset + 2]),
+ &data[subblk_offset + 4], len);
+ if (rc < 0) {
+ dev_err(tas_priv->dev, "%s: bulk_write error = %d\n",
+ __func__, rc);
+ subblk_offset |= OFFSET_ERROR_BIT;
+ }
+ }
+ subblk_offset += (len + 4);
+
+ return subblk_offset;
+}
+
+/* Just delay for ms.*/
+static int tasdevice_delay(void *context, int dev_idx, unsigned char *data,
+ int sublocksize)
+{
+ struct tasdevice_priv *tas_priv = context;
+ unsigned int sleep_time, subblk_offset = 2;
+
+ if (subblk_offset + 2 > sublocksize) {
+ dev_err(tas_priv->dev, "%s: delay Out of boundary\n",
+ __func__);
+ subblk_offset |= OFFSET_ERROR_BIT;
+ }
+ if (dev_idx == (tas_priv->index + 1) || dev_idx == 0) {
+ sleep_time = get_unaligned_be16(&data[2]) * 1000;
+ fsleep(sleep_time);
+ }
+ subblk_offset += 2;
+
+ return subblk_offset;
+}
+
+/*
+ * In sub-block data, have three type sub-block:
+ * 1. Single byte write.
+ * 2. Multi-byte write.
+ * 3. Delay.
+ * 4. Bits update.
+ * This function perform bits update.
+ */
+static int tasdevice_field_wr(void *context, int dev_idx, unsigned char *data,
+ int sublocksize)
+{
+ struct tasdevice_priv *tas_priv = context;
+ int rc, subblk_offset = 2;
+
+ if (subblk_offset + 6 > sublocksize) {
+ dev_err(tas_priv->dev, "%s: bit write Out of boundary\n",
+ __func__);
+ subblk_offset |= OFFSET_ERROR_BIT;
+ }
+ if (dev_idx == (tas_priv->index + 1) || dev_idx == 0) {
+ rc = tasdevice_spi_dev_update_bits(tas_priv,
+ TASDEVICE_REG(data[subblk_offset + 2],
+ data[subblk_offset + 3],
+ data[subblk_offset + 4]),
+ data[subblk_offset + 1],
+ data[subblk_offset + 5]);
+ if (rc < 0) {
+ dev_err(tas_priv->dev, "%s: update_bits error = %d\n",
+ __func__, rc);
+ subblk_offset |= OFFSET_ERROR_BIT;
+ }
+ }
+ subblk_offset += 6;
+
+ return subblk_offset;
+}
+
+/* Data block process function. */
+static int tasdevice_process_block(void *context, unsigned char *data,
+ unsigned char dev_idx, int sublocksize)
+{
+ struct tasdevice_priv *tas_priv = context;
+ int blktyp = dev_idx & 0xC0, subblk_offset;
+ unsigned char subblk_typ = data[1];
+
+ switch (subblk_typ) {
+ case TASDEVICE_CMD_SING_W:
+ subblk_offset = tasdevice_single_byte_wr(tas_priv,
+ dev_idx & 0x4f, data, sublocksize);
+ break;
+ case TASDEVICE_CMD_BURST:
+ subblk_offset = tasdevice_burst_wr(tas_priv,
+ dev_idx & 0x4f, data, sublocksize);
+ break;
+ case TASDEVICE_CMD_DELAY:
+ subblk_offset = tasdevice_delay(tas_priv,
+ dev_idx & 0x4f, data, sublocksize);
+ break;
+ case TASDEVICE_CMD_FIELD_W:
+ subblk_offset = tasdevice_field_wr(tas_priv,
+ dev_idx & 0x4f, data, sublocksize);
+ break;
+ default:
+ subblk_offset = 2;
+ break;
+ }
+ if (((subblk_offset & OFFSET_ERROR_BIT) != 0) && blktyp != 0) {
+ if (blktyp == 0x80) {
+ tas_priv->cur_prog = -1;
+ tas_priv->cur_conf = -1;
+ } else
+ tas_priv->cur_conf = -1;
+ }
+ subblk_offset &= ~OFFSET_ERROR_BIT;
+
+ return subblk_offset;
+}
+
+/*
+ * Device support different configurations for different scene,
+ * this function was used for choose different config.
+ */
+void tasdevice_spi_select_cfg_blk(void *pContext, int conf_no,
+ unsigned char block_type)
+{
+ struct tasdevice_priv *tas_priv = pContext;
+ struct tasdevice_rca *rca = &tas_priv->rcabin;
+ struct tasdevice_config_info **cfg_info = rca->cfg_info;
+ struct tasdev_blk_data **blk_data;
+ unsigned int j, k;
+
+ if (conf_no >= rca->ncfgs || conf_no < 0 || !cfg_info) {
+ dev_err(tas_priv->dev, "conf_no should be not more than %u\n",
+ rca->ncfgs);
+ return;
+ }
+ blk_data = cfg_info[conf_no]->blk_data;
+
+ for (j = 0; j < cfg_info[conf_no]->real_nblocks; j++) {
+ unsigned int length = 0, rc = 0;
+
+ if (block_type > 5 || block_type < 2) {
+ dev_err(tas_priv->dev,
+ "block_type should be in range from 2 to 5\n");
+ break;
+ }
+ if (block_type != blk_data[j]->block_type)
+ continue;
+
+ for (k = 0; k < blk_data[j]->n_subblks; k++) {
+ tas_priv->is_loading = true;
+
+ rc = tasdevice_process_block(tas_priv,
+ blk_data[j]->regdata + length,
+ blk_data[j]->dev_idx,
+ blk_data[j]->block_size - length);
+ length += rc;
+ if (blk_data[j]->block_size < length) {
+ dev_err(tas_priv->dev,
+ "%s: %u %u out of boundary\n",
+ __func__, length,
+ blk_data[j]->block_size);
+ break;
+ }
+ }
+ if (length != blk_data[j]->block_size)
+ dev_err(tas_priv->dev, "%s: %u %u size is not same\n",
+ __func__, length, blk_data[j]->block_size);
+ }
+}
+
+/* Block process function. */
+static int tasdevice_load_block_kernel(
+ struct tasdevice_priv *tasdevice, struct tasdev_blk *block)
+{
+ const unsigned int blk_size = block->blk_size;
+ unsigned char *data = block->data;
+ unsigned int i, length;
+
+ for (i = 0, length = 0; i < block->nr_subblocks; i++) {
+ int rc = tasdevice_process_block(tasdevice, data + length,
+ block->dev_idx, blk_size - length);
+ if (rc < 0) {
+ dev_err(tasdevice->dev,
+ "%s: %u %u sublock write error\n",
+ __func__, length, blk_size);
+ return rc;
+ }
+ length += rc;
+ if (blk_size < length) {
+ dev_err(tasdevice->dev, "%s: %u %u out of boundary\n",
+ __func__, length, blk_size);
+ rc = -ENOMEM;
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+/* DSP firmware file header parser function. */
+static int fw_parse_variable_hdr(struct tasdevice_priv *tas_priv,
+ struct tasdevice_dspfw_hdr *fw_hdr,
+ const struct firmware *fmw, int offset)
+{
+ const unsigned char *buf = fmw->data;
+ int len = strlen((char *)&buf[offset]);
+
+ len++;
+
+ if (offset + len + 8 > fmw->size) {
+ dev_err(tas_priv->dev, "%s: File Size error\n", __func__);
+ return -EINVAL;
+ }
+
+ offset += len;
+
+ fw_hdr->device_family = get_unaligned_be32(&buf[offset]);
+ if (fw_hdr->device_family != 0) {
+ dev_err(tas_priv->dev, "%s: not TAS device\n", __func__);
+ return -EINVAL;
+ }
+ offset += 4;
+
+ fw_hdr->device = get_unaligned_be32(&buf[offset]);
+ if (fw_hdr->device >= TASDEVICE_DSP_TAS_MAX_DEVICE ||
+ fw_hdr->device == 6) {
+ dev_err(tas_priv->dev, "Unsupported dev %d\n", fw_hdr->device);
+ return -EINVAL;
+ }
+ offset += 4;
+ fw_hdr->ndev = 1;
+
+ return offset;
+}
+
+/* DSP firmware file header parser function for size variabled header. */
+static int fw_parse_variable_header_git(struct tasdevice_priv
+ *tas_priv, const struct firmware *fmw, int offset)
+{
+ struct tasdevice_fw *tas_fmw = tas_priv->fmw;
+ struct tasdevice_dspfw_hdr *fw_hdr = &tas_fmw->fw_hdr;
+
+ offset = fw_parse_variable_hdr(tas_priv, fw_hdr, fmw, offset);
+
+ return offset;
+}
+
+/* DSP firmware file block parser function. */
+static int fw_parse_block_data(struct tasdevice_fw *tas_fmw,
+ struct tasdev_blk *block, const struct firmware *fmw, int offset)
+{
+ unsigned char *data = (unsigned char *)fmw->data;
+ int n;
+
+ if (offset + 8 > fmw->size) {
+ dev_err(tas_fmw->dev, "%s: Type error\n", __func__);
+ return -EINVAL;
+ }
+ block->type = get_unaligned_be32(&data[offset]);
+ offset += 4;
+
+ if (tas_fmw->fw_hdr.fixed_hdr.drv_ver >= PPC_DRIVER_CRCCHK) {
+ if (offset + 8 > fmw->size) {
+ dev_err(tas_fmw->dev, "PChkSumPresent error\n");
+ return -EINVAL;
+ }
+ block->is_pchksum_present = data[offset];
+ offset++;
+
+ block->pchksum = data[offset];
+ offset++;
+
+ block->is_ychksum_present = data[offset];
+ offset++;
+
+ block->ychksum = data[offset];
+ offset++;
+ } else {
+ block->is_pchksum_present = 0;
+ block->is_ychksum_present = 0;
+ }
+
+ block->nr_cmds = get_unaligned_be32(&data[offset]);
+ offset += 4;
+
+ n = block->nr_cmds * 4;
+ if (offset + n > fmw->size) {
+ dev_err(tas_fmw->dev,
+ "%s: File Size(%lu) error offset = %d n = %d\n",
+ __func__, (unsigned long)fmw->size, offset, n);
+ return -EINVAL;
+ }
+ /* instead of kzalloc+memcpy */
+ block->data = kmemdup(&data[offset], n, GFP_KERNEL);
+ if (!block->data)
+ return -ENOMEM;
+
+ offset += n;
+
+ return offset;
+}
+
+/*
+ * When parsing error occurs, all the memory resource will be released
+ * in the end of tasdevice_rca_ready.
+ */
+static int fw_parse_data(struct tasdevice_fw *tas_fmw,
+ struct tasdevice_data *img_data, const struct firmware *fmw,
+ int offset)
+{
+ const unsigned char *data = (unsigned char *)fmw->data;
+ struct tasdev_blk *blk;
+ unsigned int i, n;
+
+ if (offset + 64 > fmw->size) {
+ dev_err(tas_fmw->dev, "%s: Name error\n", __func__);
+ return -EINVAL;
+ }
+ memcpy(img_data->name, &data[offset], 64);
+ offset += 64;
+
+ n = strlen((char *)&data[offset]);
+ n++;
+ if (offset + n + 2 > fmw->size) {
+ dev_err(tas_fmw->dev, "%s: Description error\n", __func__);
+ return -EINVAL;
+ }
+ offset += n;
+ img_data->nr_blk = get_unaligned_be16(&data[offset]);
+ offset += 2;
+
+ img_data->dev_blks = kcalloc(img_data->nr_blk,
+ sizeof(*img_data->dev_blks), GFP_KERNEL);
+ if (!img_data->dev_blks)
+ return -ENOMEM;
+
+ for (i = 0; i < img_data->nr_blk; i++) {
+ blk = &img_data->dev_blks[i];
+ offset = fw_parse_block_data(tas_fmw, blk, fmw, offset);
+ if (offset < 0)
+ return -EINVAL;
+ }
+
+ return offset;
+}
+
+/*
+ * When parsing error occurs, all the memory resource will be released
+ * in the end of tasdevice_rca_ready.
+ */
+static int fw_parse_program_data(struct tasdevice_priv *tas_priv,
+ struct tasdevice_fw *tas_fmw, const struct firmware *fmw, int offset)
+{
+ unsigned char *buf = (unsigned char *)fmw->data;
+ struct tasdevice_prog *program;
+ int i;
+
+ if (offset + 2 > fmw->size) {
+ dev_err(tas_priv->dev, "%s: File Size error\n", __func__);
+ return -EINVAL;
+ }
+ tas_fmw->nr_programs = get_unaligned_be16(&buf[offset]);
+ offset += 2;
+
+ if (tas_fmw->nr_programs == 0) {
+ /* Not error in calibration Data file, return directly */
+ dev_dbg(tas_priv->dev, "%s: No Programs data, maybe calbin\n",
+ __func__);
+ return offset;
+ }
+
+ tas_fmw->programs =
+ kcalloc(tas_fmw->nr_programs, sizeof(*tas_fmw->programs),
+ GFP_KERNEL);
+ if (!tas_fmw->programs)
+ return -ENOMEM;
+
+ for (i = 0; i < tas_fmw->nr_programs; i++) {
+ int n = 0;
+
+ program = &tas_fmw->programs[i];
+ if (offset + 64 > fmw->size) {
+ dev_err(tas_priv->dev, "%s: mpName error\n", __func__);
+ return -EINVAL;
+ }
+ offset += 64;
+
+ n = strlen((char *)&buf[offset]);
+ /* skip '\0' and 5 unused bytes */
+ n += 6;
+ if (offset + n > fmw->size) {
+ dev_err(tas_priv->dev, "Description err\n");
+ return -EINVAL;
+ }
+
+ offset += n;
+
+ offset = fw_parse_data(tas_fmw, &program->dev_data, fmw,
+ offset);
+ if (offset < 0)
+ return offset;
+ }
+
+ return offset;
+}
+
+/*
+ * When parsing error occurs, all the memory resource will be released
+ * in the end of tasdevice_rca_ready.
+ */
+static int fw_parse_configuration_data(struct tasdevice_priv *tas_priv,
+ struct tasdevice_fw *tas_fmw, const struct firmware *fmw, int offset)
+{
+ unsigned char *data = (unsigned char *)fmw->data;
+ struct tasdevice_config *config;
+ unsigned int i, n;
+
+ if (offset + 2 > fmw->size) {
+ dev_err(tas_priv->dev, "%s: File Size error\n", __func__);
+ return -EINVAL;
+ }
+ tas_fmw->nr_configurations = get_unaligned_be16(&data[offset]);
+ offset += 2;
+
+ if (tas_fmw->nr_configurations == 0) {
+ dev_err(tas_priv->dev, "%s: Conf is zero\n", __func__);
+ /* Not error for calibration Data file, return directly */
+ return offset;
+ }
+ tas_fmw->configs = kcalloc(tas_fmw->nr_configurations,
+ sizeof(*tas_fmw->configs), GFP_KERNEL);
+ if (!tas_fmw->configs)
+ return -ENOMEM;
+ for (i = 0; i < tas_fmw->nr_configurations; i++) {
+ config = &tas_fmw->configs[i];
+ if (offset + 64 > fmw->size) {
+ dev_err(tas_priv->dev, "File Size err\n");
+ return -EINVAL;
+ }
+ memcpy(config->name, &data[offset], 64);
+ offset += 64;
+
+ n = strlen((char *)&data[offset]);
+ n += 15;
+ if (offset + n > fmw->size) {
+ dev_err(tas_priv->dev, "Description err\n");
+ return -EINVAL;
+ }
+ offset += n;
+ offset = fw_parse_data(tas_fmw, &config->dev_data,
+ fmw, offset);
+ if (offset < 0)
+ break;
+ }
+
+ return offset;
+}
+
+/* yram5 page check. */
+static bool check_inpage_yram_rg(struct tas_crc *cd,
+ unsigned char reg, unsigned char len)
+{
+ bool in = false;
+
+ if (reg <= TAS2781_YRAM5_END_REG &&
+ reg >= TAS2781_YRAM5_START_REG) {
+ if (reg + len > TAS2781_YRAM5_END_REG)
+ cd->len = TAS2781_YRAM5_END_REG - reg + 1;
+ else
+ cd->len = len;
+ cd->offset = reg;
+ in = true;
+ } else if (reg < TAS2781_YRAM5_START_REG) {
+ if (reg + len > TAS2781_YRAM5_START_REG) {
+ cd->offset = TAS2781_YRAM5_START_REG;
+ cd->len = len - TAS2781_YRAM5_START_REG + reg;
+ in = true;
+ }
+ }
+
+ return in;
+}
+
+/* DSP firmware yram block check. */
+static bool check_inpage_yram_bk1(struct tas_crc *cd,
+ unsigned char page, unsigned char reg, unsigned char len)
+{
+ bool in = false;
+
+ if (page == TAS2781_YRAM1_PAGE) {
+ if (reg >= TAS2781_YRAM1_START_REG) {
+ cd->offset = reg;
+ cd->len = len;
+ in = true;
+ } else if (reg + len > TAS2781_YRAM1_START_REG) {
+ cd->offset = TAS2781_YRAM1_START_REG;
+ cd->len = len - TAS2781_YRAM1_START_REG + reg;
+ in = true;
+ }
+ } else if (page == TAS2781_YRAM3_PAGE) {
+ in = check_inpage_yram_rg(cd, reg, len);
+ }
+
+ return in;
+}
+
+/*
+ * Return Code:
+ * true -- the registers are in the inpage yram
+ * false -- the registers are NOT in the inpage yram
+ */
+static bool check_inpage_yram(struct tas_crc *cd, unsigned char book,
+ unsigned char page, unsigned char reg, unsigned char len)
+{
+ bool in = false;
+
+ if (book == TAS2781_YRAM_BOOK1)
+ in = check_inpage_yram_bk1(cd, page, reg, len);
+ else if (book == TAS2781_YRAM_BOOK2 && page == TAS2781_YRAM5_PAGE)
+ in = check_inpage_yram_rg(cd, reg, len);
+
+ return in;
+}
+
+/* yram4 page check. */
+static bool check_inblock_yram_bk(struct tas_crc *cd,
+ unsigned char page, unsigned char reg, unsigned char len)
+{
+ bool in = false;
+
+ if ((page >= TAS2781_YRAM4_START_PAGE &&
+ page <= TAS2781_YRAM4_END_PAGE) ||
+ (page >= TAS2781_YRAM2_START_PAGE &&
+ page <= TAS2781_YRAM2_END_PAGE)) {
+ if (reg <= TAS2781_YRAM2_END_REG &&
+ reg >= TAS2781_YRAM2_START_REG) {
+ cd->offset = reg;
+ cd->len = len;
+ in = true;
+ } else if (reg < TAS2781_YRAM2_START_REG) {
+ if (reg + len - 1 >= TAS2781_YRAM2_START_REG) {
+ cd->offset = TAS2781_YRAM2_START_REG;
+ cd->len = reg + len - TAS2781_YRAM2_START_REG;
+ in = true;
+ }
+ }
+ }
+
+ return in;
+}
+
+/*
+ * Return Code:
+ * true -- the registers are in the inblock yram
+ * false -- the registers are NOT in the inblock yram
+ */
+static bool check_inblock_yram(struct tas_crc *cd, unsigned char book,
+ unsigned char page, unsigned char reg, unsigned char len)
+{
+ bool in = false;
+
+ if (book == TAS2781_YRAM_BOOK1 || book == TAS2781_YRAM_BOOK2)
+ in = check_inblock_yram_bk(cd, page, reg, len);
+
+ return in;
+}
+
+/* yram page check. */
+static bool check_yram(struct tas_crc *cd, unsigned char book,
+ unsigned char page, unsigned char reg, unsigned char len)
+{
+ bool in;
+
+ in = check_inpage_yram(cd, book, page, reg, len);
+ if (!in)
+ in = check_inblock_yram(cd, book, page, reg, len);
+
+ return in;
+}
+
+/* Checksum for data block. */
+static int tasdev_multibytes_chksum(struct tasdevice_priv *tasdevice,
+ unsigned char book, unsigned char page,
+ unsigned char reg, unsigned int len)
+{
+ struct tas_crc crc_data;
+ unsigned char crc_chksum = 0;
+ unsigned char nBuf1[128];
+ int ret = 0, i;
+ bool in;
+
+ if ((reg + len - 1) > 127) {
+ ret = -EINVAL;
+ dev_err(tasdevice->dev, "firmware error\n");
+ goto end;
+ }
+
+ if ((book == TASDEVICE_BOOK_ID(TAS2781_SA_COEFF_SWAP_REG)) &&
+ (page == TASDEVICE_PAGE_ID(TAS2781_SA_COEFF_SWAP_REG)) &&
+ (reg == TASDEVICE_REG_ID(TAS2781_SA_COEFF_SWAP_REG)) &&
+ (len == 4)) {
+ /* DSP swap command, pass */
+ ret = 0;
+ goto end;
+ }
+
+ in = check_yram(&crc_data, book, page, reg, len);
+ if (!in)
+ goto end;
+
+ if (len == 1) {
+ dev_err(tasdevice->dev, "firmware error\n");
+ ret = -EINVAL;
+ goto end;
+ }
+
+ ret = tasdevice_spi_dev_bulk_read(tasdevice,
+ TASDEVICE_REG(book, page, crc_data.offset),
+ nBuf1, crc_data.len);
+ if (ret < 0)
+ goto end;
+
+ for (i = 0; i < crc_data.len; i++) {
+ if ((book == TASDEVICE_BOOK_ID(TAS2781_SA_COEFF_SWAP_REG)) &&
+ (page == TASDEVICE_PAGE_ID(TAS2781_SA_COEFF_SWAP_REG)) &&
+ ((i + crc_data.offset) >=
+ TASDEVICE_REG_ID(TAS2781_SA_COEFF_SWAP_REG)) &&
+ ((i + crc_data.offset) <=
+ (TASDEVICE_REG_ID(TAS2781_SA_COEFF_SWAP_REG) + 4)))
+ /* DSP swap command, bypass */
+ continue;
+ else
+ crc_chksum += crc8(tasdevice->crc8_lkp_tbl, &nBuf1[i],
+ 1, 0);
+ }
+
+ ret = crc_chksum;
+
+end:
+ return ret;
+}
+
+/* Checksum for single register. */
+static int do_singlereg_checksum(struct tasdevice_priv *tasdevice,
+ unsigned char book, unsigned char page,
+ unsigned char reg, unsigned char val)
+{
+ struct tas_crc crc_data;
+ unsigned int nData1;
+ int ret = 0;
+ bool in;
+
+ /* DSP swap command, pass */
+ if ((book == TASDEVICE_BOOK_ID(TAS2781_SA_COEFF_SWAP_REG)) &&
+ (page == TASDEVICE_PAGE_ID(TAS2781_SA_COEFF_SWAP_REG)) &&
+ (reg >= TASDEVICE_REG_ID(TAS2781_SA_COEFF_SWAP_REG)) &&
+ (reg <= (TASDEVICE_REG_ID(TAS2781_SA_COEFF_SWAP_REG) + 4)))
+ return 0;
+
+ in = check_yram(&crc_data, book, page, reg, 1);
+ if (!in)
+ return 0;
+ ret = tasdevice_spi_dev_read(tasdevice,
+ TASDEVICE_REG(book, page, reg), &nData1);
+ if (ret < 0)
+ return ret;
+
+ if (nData1 != val) {
+ dev_err(tasdevice->dev,
+ "B[0x%x]P[0x%x]R[0x%x] W[0x%x], R[0x%x]\n",
+ book, page, reg, val, nData1);
+ tasdevice->err_code |= ERROR_YRAM_CRCCHK;
+ return -EAGAIN;
+ }
+
+ ret = crc8(tasdevice->crc8_lkp_tbl, &val, 1, 0);
+
+ return ret;
+}
+
+/* Block type check. */
+static void set_err_prg_cfg(unsigned int type, struct tasdevice_priv *p)
+{
+ if ((type == MAIN_ALL_DEVICES) || (type == MAIN_DEVICE_A) ||
+ (type == MAIN_DEVICE_B) || (type == MAIN_DEVICE_C) ||
+ (type == MAIN_DEVICE_D))
+ p->cur_prog = -1;
+ else
+ p->cur_conf = -1;
+}
+
+/* Checksum for data bytes. */
+static int tasdev_bytes_chksum(struct tasdevice_priv *tas_priv,
+ struct tasdev_blk *block, unsigned char book,
+ unsigned char page, unsigned char reg, unsigned int len,
+ unsigned char val, unsigned char *crc_chksum)
+{
+ int ret;
+
+ if (len > 1)
+ ret = tasdev_multibytes_chksum(tas_priv, book, page, reg,
+ len);
+ else
+ ret = do_singlereg_checksum(tas_priv, book, page, reg, val);
+
+ if (ret > 0) {
+ *crc_chksum += ret;
+ goto end;
+ }
+
+ if (ret != -EAGAIN)
+ goto end;
+
+ block->nr_retry--;
+ if (block->nr_retry > 0)
+ goto end;
+
+ set_err_prg_cfg(block->type, tas_priv);
+
+end:
+ return ret;
+}
+
+/* Multi-data byte write. */
+static int tasdev_multibytes_wr(struct tasdevice_priv *tas_priv,
+ struct tasdev_blk *block, unsigned char book,
+ unsigned char page, unsigned char reg, unsigned char *data,
+ unsigned int len, unsigned int *nr_cmds,
+ unsigned char *crc_chksum)
+{
+ int ret;
+
+ if (len > 1) {
+ ret = tasdevice_spi_dev_bulk_write(tas_priv,
+ TASDEVICE_REG(book, page, reg), data + 3, len);
+ if (ret < 0)
+ return ret;
+ if (block->is_ychksum_present)
+ ret = tasdev_bytes_chksum(tas_priv, block,
+ book, page, reg, len, 0, crc_chksum);
+ } else {
+ ret = tasdevice_spi_dev_write(tas_priv,
+ TASDEVICE_REG(book, page, reg), data[3]);
+ if (ret < 0)
+ return ret;
+ if (block->is_ychksum_present)
+ ret = tasdev_bytes_chksum(tas_priv, block, book,
+ page, reg, 1, data[3], crc_chksum);
+ }
+
+ if (!block->is_ychksum_present || ret >= 0) {
+ *nr_cmds += 1;
+ if (len >= 2)
+ *nr_cmds += ((len - 2) / 4) + 1;
+ }
+
+ return ret;
+}
+
+/* Checksum for block. */
+static int tasdev_block_chksum(struct tasdevice_priv *tas_priv,
+ struct tasdev_blk *block)
+{
+ unsigned int nr_value;
+ int ret;
+
+ ret = tasdevice_spi_dev_read(tas_priv, TASDEVICE_CHECKSUM, &nr_value);
+ if (ret < 0) {
+ dev_err(tas_priv->dev, "%s: read error %d.\n", __func__, ret);
+ set_err_prg_cfg(block->type, tas_priv);
+ return ret;
+ }
+
+ if ((nr_value & 0xff) != block->pchksum) {
+ dev_err(tas_priv->dev, "%s: PChkSum err %d ", __func__, ret);
+ dev_err(tas_priv->dev, "PChkSum = 0x%x, Reg = 0x%x\n",
+ block->pchksum, (nr_value & 0xff));
+ tas_priv->err_code |= ERROR_PRAM_CRCCHK;
+ ret = -EAGAIN;
+ block->nr_retry--;
+
+ if (block->nr_retry <= 0)
+ set_err_prg_cfg(block->type, tas_priv);
+ } else {
+ tas_priv->err_code &= ~ERROR_PRAM_CRCCHK;
+ }
+
+ return ret;
+}
+
+/* Firmware block load function. */
+static int tasdev_load_blk(struct tasdevice_priv *tas_priv,
+ struct tasdev_blk *block)
+{
+ unsigned int sleep_time, len, nr_cmds;
+ unsigned char offset, book, page, val;
+ unsigned char *data = block->data;
+ unsigned char crc_chksum = 0;
+ int ret = 0;
+
+ while (block->nr_retry > 0) {
+ if (block->is_pchksum_present) {
+ ret = tasdevice_spi_dev_write(tas_priv,
+ TASDEVICE_CHECKSUM, 0);
+ if (ret < 0)
+ break;
+ }
+
+ if (block->is_ychksum_present)
+ crc_chksum = 0;
+
+ nr_cmds = 0;
+
+ while (nr_cmds < block->nr_cmds) {
+ data = block->data + nr_cmds * 4;
+
+ book = data[0];
+ page = data[1];
+ offset = data[2];
+ val = data[3];
+
+ nr_cmds++;
+ /* Single byte write */
+ if (offset <= 0x7F) {
+ ret = tasdevice_spi_dev_write(tas_priv,
+ TASDEVICE_REG(book, page, offset),
+ val);
+ if (ret < 0)
+ break;
+ if (block->is_ychksum_present) {
+ ret = tasdev_bytes_chksum(tas_priv,
+ block, book, page, offset,
+ 1, val, &crc_chksum);
+ if (ret < 0)
+ break;
+ }
+ continue;
+ }
+ /* sleep command */
+ if (offset == 0x81) {
+ /* book -- data[0] page -- data[1] */
+ sleep_time = ((book << 8) + page)*1000;
+ fsleep(sleep_time);
+ continue;
+ }
+ /* Multiple bytes write */
+ if (offset == 0x85) {
+ data += 4;
+ len = (book << 8) + page;
+ book = data[0];
+ page = data[1];
+ offset = data[2];
+ ret = tasdev_multibytes_wr(tas_priv,
+ block, book, page, offset, data,
+ len, &nr_cmds, &crc_chksum);
+ if (ret < 0)
+ break;
+ }
+ }
+ if (ret == -EAGAIN) {
+ if (block->nr_retry > 0)
+ continue;
+ } else if (ret < 0) {
+ /* err in current device, skip it */
+ break;
+ }
+
+ if (block->is_pchksum_present) {
+ ret = tasdev_block_chksum(tas_priv, block);
+ if (ret == -EAGAIN) {
+ if (block->nr_retry > 0)
+ continue;
+ } else if (ret < 0) {
+ /* err in current device, skip it */
+ break;
+ }
+ }
+
+ if (block->is_ychksum_present) {
+ /* TBD, open it when FW ready */
+ dev_err(tas_priv->dev,
+ "Blk YChkSum: FW = 0x%x, YCRC = 0x%x\n",
+ block->ychksum, crc_chksum);
+
+ tas_priv->err_code &=
+ ~ERROR_YRAM_CRCCHK;
+ ret = 0;
+ }
+ /* skip current blk */
+ break;
+ }
+
+ return ret;
+}
+
+/* Firmware block load function. */
+static int tasdevice_load_block(struct tasdevice_priv *tas_priv,
+ struct tasdev_blk *block)
+{
+ int ret = 0;
+
+ block->nr_retry = 6;
+ if (tas_priv->is_loading == false)
+ return 0;
+ ret = tasdev_load_blk(tas_priv, block);
+ if (ret < 0)
+ dev_err(tas_priv->dev, "Blk (%d) load error\n", block->type);
+
+ return ret;
+}
+
+/*
+ * Select firmware binary parser & load callback functions by ppc3 version
+ * and firmware binary version.
+ */
+static int dspfw_default_callback(struct tasdevice_priv *tas_priv,
+ unsigned int drv_ver, unsigned int ppcver)
+{
+ int rc = 0;
+
+ if (drv_ver == 0x100) {
+ if (ppcver >= PPC3_VERSION) {
+ tas_priv->fw_parse_variable_header =
+ fw_parse_variable_header_kernel;
+ tas_priv->fw_parse_program_data =
+ fw_parse_program_data_kernel;
+ tas_priv->fw_parse_configuration_data =
+ fw_parse_configuration_data_kernel;
+ tas_priv->tasdevice_load_block =
+ tasdevice_load_block_kernel;
+ } else if (ppcver == 0x00) {
+ tas_priv->fw_parse_variable_header =
+ fw_parse_variable_header_git;
+ tas_priv->fw_parse_program_data =
+ fw_parse_program_data;
+ tas_priv->fw_parse_configuration_data =
+ fw_parse_configuration_data;
+ tas_priv->tasdevice_load_block =
+ tasdevice_load_block;
+ } else {
+ dev_err(tas_priv->dev,
+ "Wrong PPCVer :0x%08x\n", ppcver);
+ rc = -EINVAL;
+ }
+ } else {
+ dev_err(tas_priv->dev, "Wrong DrvVer : 0x%02x\n", drv_ver);
+ rc = -EINVAL;
+ }
+
+ return rc;
+}
+
+/* DSP firmware binary file header parser function. */
+static int fw_parse_header(struct tasdevice_priv *tas_priv,
+ struct tasdevice_fw *tas_fmw, const struct firmware *fmw, int offset)
+{
+ struct tasdevice_dspfw_hdr *fw_hdr = &tas_fmw->fw_hdr;
+ struct tasdevice_fw_fixed_hdr *fw_fixed_hdr = &fw_hdr->fixed_hdr;
+ static const unsigned char magic_number[] = {0x35, 0x35, 0x35, 0x32, };
+ const unsigned char *buf = (unsigned char *)fmw->data;
+
+ if (offset + 92 > fmw->size) {
+ dev_err(tas_priv->dev, "%s: File Size error\n", __func__);
+ offset = -EINVAL;
+ goto out;
+ }
+ if (memcmp(&buf[offset], magic_number, 4)) {
+ dev_err(tas_priv->dev, "%s: Magic num NOT match\n", __func__);
+ offset = -EINVAL;
+ goto out;
+ }
+ offset += 4;
+
+ /*
+ * Convert data[offset], data[offset + 1], data[offset + 2] and
+ * data[offset + 3] into host
+ */
+ fw_fixed_hdr->fwsize = get_unaligned_be32(&buf[offset]);
+ offset += 4;
+ if (fw_fixed_hdr->fwsize != fmw->size) {
+ dev_err(tas_priv->dev, "File size not match, %lu %u",
+ (unsigned long)fmw->size, fw_fixed_hdr->fwsize);
+ offset = -EINVAL;
+ goto out;
+ }
+ offset += 4;
+ fw_fixed_hdr->ppcver = get_unaligned_be32(&buf[offset]);
+ offset += 8;
+ fw_fixed_hdr->drv_ver = get_unaligned_be32(&buf[offset]);
+ offset += 72;
+
+out:
+ return offset;
+}
+
+/* DSP firmware binary file parser function. */
+static int tasdevice_dspfw_ready(const struct firmware *fmw, void *context)
+{
+ struct tasdevice_priv *tas_priv = context;
+ struct tasdevice_fw_fixed_hdr *fw_fixed_hdr;
+ struct tasdevice_fw *tas_fmw;
+ int offset = 0, ret = 0;
+
+ if (!fmw || !fmw->data) {
+ dev_err(tas_priv->dev, "%s: Failed to read firmware %s\n",
+ __func__, tas_priv->coef_binaryname);
+ return -EINVAL;
+ }
+
+ tas_priv->fmw = kzalloc(sizeof(*tas_priv->fmw), GFP_KERNEL);
+ if (!tas_priv->fmw)
+ return -ENOMEM;
+ tas_fmw = tas_priv->fmw;
+ tas_fmw->dev = tas_priv->dev;
+ offset = fw_parse_header(tas_priv, tas_fmw, fmw, offset);
+
+ if (offset == -EINVAL)
+ return -EINVAL;
+
+ fw_fixed_hdr = &tas_fmw->fw_hdr.fixed_hdr;
+ /* Support different versions of firmware */
+ switch (fw_fixed_hdr->drv_ver) {
+ case 0x301:
+ case 0x302:
+ case 0x502:
+ case 0x503:
+ tas_priv->fw_parse_variable_header =
+ fw_parse_variable_header_kernel;
+ tas_priv->fw_parse_program_data =
+ fw_parse_program_data_kernel;
+ tas_priv->fw_parse_configuration_data =
+ fw_parse_configuration_data_kernel;
+ tas_priv->tasdevice_load_block =
+ tasdevice_load_block_kernel;
+ break;
+ case 0x202:
+ case 0x400:
+ tas_priv->fw_parse_variable_header =
+ fw_parse_variable_header_git;
+ tas_priv->fw_parse_program_data =
+ fw_parse_program_data;
+ tas_priv->fw_parse_configuration_data =
+ fw_parse_configuration_data;
+ tas_priv->tasdevice_load_block =
+ tasdevice_load_block;
+ break;
+ default:
+ ret = dspfw_default_callback(tas_priv,
+ fw_fixed_hdr->drv_ver, fw_fixed_hdr->ppcver);
+ if (ret)
+ return ret;
+ break;
+ }
+
+ offset = tas_priv->fw_parse_variable_header(tas_priv, fmw, offset);
+ if (offset < 0)
+ return offset;
+
+ offset = tas_priv->fw_parse_program_data(tas_priv, tas_fmw, fmw,
+ offset);
+ if (offset < 0)
+ return offset;
+
+ offset = tas_priv->fw_parse_configuration_data(tas_priv,
+ tas_fmw, fmw, offset);
+ if (offset < 0)
+ ret = offset;
+
+ return ret;
+}
+
+/* DSP firmware binary file parser function. */
+int tasdevice_spi_dsp_parser(void *context)
+{
+ struct tasdevice_priv *tas_priv = context;
+ const struct firmware *fw_entry;
+ int ret;
+
+ ret = request_firmware(&fw_entry, tas_priv->coef_binaryname,
+ tas_priv->dev);
+ if (ret) {
+ dev_err(tas_priv->dev, "%s: load %s error\n", __func__,
+ tas_priv->coef_binaryname);
+ return ret;
+ }
+
+ ret = tasdevice_dspfw_ready(fw_entry, tas_priv);
+ release_firmware(fw_entry);
+ fw_entry = NULL;
+
+ return ret;
+}
+
+/* DSP firmware program block data remove function. */
+static void tasdev_dsp_prog_blk_remove(struct tasdevice_prog *prog)
+{
+ struct tasdevice_data *tas_dt;
+ struct tasdev_blk *blk;
+ unsigned int i;
+
+ if (!prog)
+ return;
+
+ tas_dt = &prog->dev_data;
+
+ if (!tas_dt->dev_blks)
+ return;
+
+ for (i = 0; i < tas_dt->nr_blk; i++) {
+ blk = &tas_dt->dev_blks[i];
+ kfree(blk->data);
+ }
+ kfree(tas_dt->dev_blks);
+}
+
+/* DSP firmware program block data remove function. */
+static void tasdev_dsp_prog_remove(struct tasdevice_prog *prog,
+ unsigned short nr)
+{
+ int i;
+
+ for (i = 0; i < nr; i++)
+ tasdev_dsp_prog_blk_remove(&prog[i]);
+ kfree(prog);
+}
+
+/* DSP firmware config block data remove function. */
+static void tasdev_dsp_cfg_blk_remove(struct tasdevice_config *cfg)
+{
+ struct tasdevice_data *tas_dt;
+ struct tasdev_blk *blk;
+ unsigned int i;
+
+ if (cfg) {
+ tas_dt = &cfg->dev_data;
+
+ if (!tas_dt->dev_blks)
+ return;
+
+ for (i = 0; i < tas_dt->nr_blk; i++) {
+ blk = &tas_dt->dev_blks[i];
+ kfree(blk->data);
+ }
+ kfree(tas_dt->dev_blks);
+ }
+}
+
+/* DSP firmware config remove function. */
+static void tasdev_dsp_cfg_remove(struct tasdevice_config *config,
+ unsigned short nr)
+{
+ int i;
+
+ for (i = 0; i < nr; i++)
+ tasdev_dsp_cfg_blk_remove(&config[i]);
+ kfree(config);
+}
+
+/* DSP firmware remove function. */
+void tasdevice_spi_dsp_remove(void *context)
+{
+ struct tasdevice_priv *tas_dev = context;
+
+ if (!tas_dev->fmw)
+ return;
+
+ if (tas_dev->fmw->programs)
+ tasdev_dsp_prog_remove(tas_dev->fmw->programs,
+ tas_dev->fmw->nr_programs);
+ if (tas_dev->fmw->configs)
+ tasdev_dsp_cfg_remove(tas_dev->fmw->configs,
+ tas_dev->fmw->nr_configurations);
+ kfree(tas_dev->fmw);
+ tas_dev->fmw = NULL;
+}
+
+/* DSP firmware calibration data remove function. */
+static void tas2781_clear_calfirmware(struct tasdevice_fw *tas_fmw)
+{
+ struct tasdevice_calibration *calibration;
+ struct tasdev_blk *block;
+ unsigned int blks;
+ int i;
+
+ if (!tas_fmw->calibrations)
+ goto out;
+
+ for (i = 0; i < tas_fmw->nr_calibrations; i++) {
+ calibration = &tas_fmw->calibrations[i];
+ if (!calibration)
+ continue;
+
+ if (!calibration->dev_data.dev_blks)
+ continue;
+
+ for (blks = 0; blks < calibration->dev_data.nr_blk; blks++) {
+ block = &calibration->dev_data.dev_blks[blks];
+ if (!block)
+ continue;
+ kfree(block->data);
+ }
+ kfree(calibration->dev_data.dev_blks);
+ }
+ kfree(tas_fmw->calibrations);
+out:
+ kfree(tas_fmw);
+}
+
+/* Calibration data from firmware remove function. */
+void tasdevice_spi_calbin_remove(void *context)
+{
+ struct tasdevice_priv *tas_priv = context;
+
+ if (tas_priv->cali_data_fmw) {
+ tas2781_clear_calfirmware(tas_priv->cali_data_fmw);
+ tas_priv->cali_data_fmw = NULL;
+ }
+}
+
+/* Configuration remove function. */
+void tasdevice_spi_config_info_remove(void *context)
+{
+ struct tasdevice_priv *tas_priv = context;
+ struct tasdevice_rca *rca = &tas_priv->rcabin;
+ struct tasdevice_config_info **ci = rca->cfg_info;
+ unsigned int i, j;
+
+ if (!ci)
+ return;
+ for (i = 0; i < rca->ncfgs; i++) {
+ if (!ci[i])
+ continue;
+ if (ci[i]->blk_data) {
+ for (j = 0; j < ci[i]->real_nblocks; j++) {
+ if (!ci[i]->blk_data[j])
+ continue;
+ kfree(ci[i]->blk_data[j]->regdata);
+ kfree(ci[i]->blk_data[j]);
+ }
+ kfree(ci[i]->blk_data);
+ }
+ kfree(ci[i]);
+ }
+ kfree(ci);
+}
+
+/* DSP firmware program block data load function. */
+static int tasdevice_load_data(struct tasdevice_priv *tas_priv,
+ struct tasdevice_data *dev_data)
+{
+ struct tasdev_blk *block;
+ unsigned int i;
+ int ret = 0;
+
+ for (i = 0; i < dev_data->nr_blk; i++) {
+ block = &dev_data->dev_blks[i];
+ ret = tas_priv->tasdevice_load_block(tas_priv, block);
+ if (ret < 0)
+ break;
+ }
+
+ return ret;
+}
+
+/* DSP firmware program load interface function. */
+int tasdevice_spi_prmg_load(void *context, int prm_no)
+{
+ struct tasdevice_priv *tas_priv = context;
+ struct tasdevice_fw *tas_fmw = tas_priv->fmw;
+ struct tasdevice_prog *program;
+ struct tasdevice_config *conf;
+ int ret = 0;
+
+ if (!tas_fmw) {
+ dev_err(tas_priv->dev, "%s: Firmware is NULL\n", __func__);
+ return -EINVAL;
+ }
+ if (prm_no >= 0 && prm_no <= tas_fmw->nr_programs) {
+ tas_priv->cur_conf = 0;
+ tas_priv->is_loading = true;
+ program = &tas_fmw->programs[prm_no];
+ ret = tasdevice_load_data(tas_priv, &program->dev_data);
+ if (ret < 0) {
+ dev_err(tas_priv->dev, "Program failed %d.\n", ret);
+ return ret;
+ }
+ tas_priv->cur_prog = prm_no;
+
+ conf = &tas_fmw->configs[tas_priv->cur_conf];
+ ret = tasdevice_load_data(tas_priv, &conf->dev_data);
+ if (ret < 0)
+ dev_err(tas_priv->dev, "Config failed %d.\n", ret);
+ } else {
+ dev_err(tas_priv->dev,
+ "%s: prm(%d) is not in range of Programs %u\n",
+ __func__, prm_no, tas_fmw->nr_programs);
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+/* RCABIN configuration switch interface function. */
+void tasdevice_spi_tuning_switch(void *context, int state)
+{
+ struct tasdevice_priv *tas_priv = context;
+ int profile_cfg_id = tas_priv->rcabin.profile_cfg_id;
+
+ if (tas_priv->fw_state == TASDEVICE_DSP_FW_FAIL) {
+ dev_err(tas_priv->dev, "DSP bin file not loaded\n");
+ return;
+ }
+
+ if (state == 0)
+ tasdevice_spi_select_cfg_blk(tas_priv, profile_cfg_id,
+ TASDEVICE_BIN_BLK_PRE_POWER_UP);
+ else
+ tasdevice_spi_select_cfg_blk(tas_priv, profile_cfg_id,
+ TASDEVICE_BIN_BLK_PRE_SHUTDOWN);
+}
diff --git a/sound/pci/lola/lola_clock.c b/sound/pci/lola/lola_clock.c
index cafd30e30913..2e73fbf335ed 100644
--- a/sound/pci/lola/lola_clock.c
+++ b/sound/pci/lola/lola_clock.c
@@ -35,7 +35,7 @@ unsigned int lola_sample_rate_convert(unsigned int coded)
default: return 0; /* error */
}
- /* ajustement */
+ /* adjustement */
switch (coded & 0x60) {
case (0 << 5): break;
case (1 << 5): freq = (freq * 999) / 1000; break;
diff --git a/sound/pci/nm256/nm256.c b/sound/pci/nm256/nm256.c
index 11ba7d4eac2a..44085237fb44 100644
--- a/sound/pci/nm256/nm256.c
+++ b/sound/pci/nm256/nm256.c
@@ -696,7 +696,9 @@ snd_nm256_playback_copy(struct snd_pcm_substream *substream,
struct snd_pcm_runtime *runtime = substream->runtime;
struct nm256_stream *s = runtime->private_data;
- return copy_from_iter_toio(s->bufptr + pos, src, count);
+ if (copy_from_iter_toio(s->bufptr + pos, count, src) != count)
+ return -EFAULT;
+ return 0;
}
/*
@@ -710,7 +712,9 @@ snd_nm256_capture_copy(struct snd_pcm_substream *substream,
struct snd_pcm_runtime *runtime = substream->runtime;
struct nm256_stream *s = runtime->private_data;
- return copy_to_iter_fromio(dst, s->bufptr + pos, count);
+ if (copy_to_iter_fromio(s->bufptr + pos, count, dst) != count)
+ return -EFAULT;
+ return 0;
}
#endif /* !__i386__ */
diff --git a/sound/pci/rme32.c b/sound/pci/rme32.c
index 02144bbee6d5..a8c2ceaadef5 100644
--- a/sound/pci/rme32.c
+++ b/sound/pci/rme32.c
@@ -256,8 +256,10 @@ static int snd_rme32_playback_copy(struct snd_pcm_substream *substream,
{
struct rme32 *rme32 = snd_pcm_substream_chip(substream);
- return copy_from_iter_toio(rme32->iobase + RME32_IO_DATA_BUFFER + pos,
- src, count);
+ if (copy_from_iter_toio(rme32->iobase + RME32_IO_DATA_BUFFER + pos,
+ count, src) != count)
+ return -EFAULT;
+ return 0;
}
/* copy callback for halfduplex mode */
@@ -267,9 +269,10 @@ static int snd_rme32_capture_copy(struct snd_pcm_substream *substream,
{
struct rme32 *rme32 = snd_pcm_substream_chip(substream);
- return copy_to_iter_fromio(dst,
- rme32->iobase + RME32_IO_DATA_BUFFER + pos,
- count);
+ if (copy_to_iter_fromio(rme32->iobase + RME32_IO_DATA_BUFFER + pos,
+ count, dst) != count)
+ return -EFAULT;
+ return 0;
}
/*
diff --git a/sound/pci/rme96.c b/sound/pci/rme96.c
index d50ad25574ad..1265a7efac60 100644
--- a/sound/pci/rme96.c
+++ b/sound/pci/rme96.c
@@ -322,8 +322,10 @@ snd_rme96_playback_copy(struct snd_pcm_substream *substream,
{
struct rme96 *rme96 = snd_pcm_substream_chip(substream);
- return copy_from_iter_toio(rme96->iobase + RME96_IO_PLAY_BUFFER + pos,
- src, count);
+ if (copy_from_iter_toio(rme96->iobase + RME96_IO_PLAY_BUFFER + pos,
+ count, src) != count)
+ return -EFAULT;
+ return 0;
}
static int
@@ -333,9 +335,10 @@ snd_rme96_capture_copy(struct snd_pcm_substream *substream,
{
struct rme96 *rme96 = snd_pcm_substream_chip(substream);
- return copy_to_iter_fromio(dst,
- rme96->iobase + RME96_IO_REC_BUFFER + pos,
- count);
+ if (copy_to_iter_fromio(rme96->iobase + RME96_IO_REC_BUFFER + pos,
+ count, dst) != count)
+ return -EFAULT;
+ return 0;
}
/*
diff --git a/sound/pci/rme9652/hdsp.c b/sound/pci/rme9652/hdsp.c
index 1c504a591948..fd3dfbad397a 100644
--- a/sound/pci/rme9652/hdsp.c
+++ b/sound/pci/rme9652/hdsp.c
@@ -3444,7 +3444,7 @@ snd_hdsp_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
snd_iprintf(buffer, "MIDI1 Input status: 0x%x\n", hdsp_read(hdsp, HDSP_midiStatusIn0));
snd_iprintf(buffer, "MIDI2 Output status: 0x%x\n", hdsp_read(hdsp, HDSP_midiStatusOut1));
snd_iprintf(buffer, "MIDI2 Input status: 0x%x\n", hdsp_read(hdsp, HDSP_midiStatusIn1));
- snd_iprintf(buffer, "Use Midi Tasklet: %s\n", hdsp->use_midi_work ? "on" : "off");
+ snd_iprintf(buffer, "Use Midi Tasklet: %s\n", str_on_off(hdsp->use_midi_work));
snd_iprintf(buffer, "\n");
@@ -3452,8 +3452,8 @@ snd_hdsp_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
snd_iprintf(buffer, "Buffer Size (Latency): %d samples (2 periods of %lu bytes)\n", x, (unsigned long) hdsp->period_bytes);
snd_iprintf(buffer, "Hardware pointer (frames): %ld\n", hdsp_hw_pointer(hdsp));
- snd_iprintf(buffer, "Precise pointer: %s\n", hdsp->precise_ptr ? "on" : "off");
- snd_iprintf(buffer, "Line out: %s\n", (hdsp->control_register & HDSP_LineOut) ? "on" : "off");
+ snd_iprintf(buffer, "Precise pointer: %s\n", str_on_off(hdsp->precise_ptr));
+ snd_iprintf(buffer, "Line out: %s\n", str_on_off(hdsp->control_register & HDSP_LineOut));
snd_iprintf(buffer, "Firmware version: %d\n", (status2&HDSP_version0)|(status2&HDSP_version1)<<1|(status2&HDSP_version2)<<2);
@@ -3750,8 +3750,8 @@ snd_hdsp_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
snd_iprintf(buffer, "Phones Gain : %s\n", tmp);
snd_iprintf(buffer, "XLR Breakout Cable : %s\n",
- hdsp_toggle_setting(hdsp, HDSP_XLRBreakoutCable) ?
- "yes" : "no");
+ str_yes_no(hdsp_toggle_setting(hdsp,
+ HDSP_XLRBreakoutCable)));
if (hdsp->control_register & HDSP_AnalogExtensionBoard)
snd_iprintf(buffer, "AEB : on (ADAT1 internal)\n");
diff --git a/sound/pci/rme9652/hdspm.c b/sound/pci/rme9652/hdspm.c
index d7290463d654..f89718b19d23 100644
--- a/sound/pci/rme9652/hdspm.c
+++ b/sound/pci/rme9652/hdspm.c
@@ -4927,14 +4927,14 @@ snd_hdspm_proc_read_madi(struct snd_info_entry *entry,
x, (unsigned long) hdspm->period_bytes);
snd_iprintf(buffer, "Line out: %s\n",
- (hdspm->control_register & HDSPM_LineOut) ? "on " : "off");
+ str_on_off(hdspm->control_register & HDSPM_LineOut));
snd_iprintf(buffer,
"ClearTrackMarker = %s, Transmit in %s Channel Mode, "
"Auto Input %s\n",
- (hdspm->control_register & HDSPM_clr_tms) ? "on" : "off",
+ str_on_off(hdspm->control_register & HDSPM_clr_tms),
(hdspm->control_register & HDSPM_TX_64ch) ? "64" : "56",
- (hdspm->control_register & HDSPM_AutoInp) ? "on" : "off");
+ str_on_off(hdspm->control_register & HDSPM_AutoInp));
if (!(hdspm->control_register & HDSPM_ClockModeMaster))
@@ -5088,12 +5088,9 @@ snd_hdspm_proc_read_aes32(struct snd_info_entry * entry,
snd_iprintf(buffer,
"ClearTrackMarker %s, Emphasis %s, Dolby %s\n",
- (hdspm->
- control_register & HDSPM_clr_tms) ? "on" : "off",
- (hdspm->
- control_register & HDSPM_Emphasis) ? "on" : "off",
- (hdspm->
- control_register & HDSPM_Dolby) ? "on" : "off");
+ str_on_off(hdspm->control_register & HDSPM_clr_tms),
+ str_on_off(hdspm->control_register & HDSPM_Emphasis),
+ str_on_off(hdspm->control_register & HDSPM_Dolby));
pref_syncref = hdspm_pref_sync_ref(hdspm);
diff --git a/sound/pci/rme9652/rme9652.c b/sound/pci/rme9652/rme9652.c
index d066c70ae160..5b8dd7b0a02c 100644
--- a/sound/pci/rme9652/rme9652.c
+++ b/sound/pci/rme9652/rme9652.c
@@ -1561,8 +1561,7 @@ snd_rme9652_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buff
x, (unsigned long) rme9652->period_bytes);
snd_iprintf(buffer, "Hardware pointer (frames): %ld\n",
rme9652_hw_pointer(rme9652));
- snd_iprintf(buffer, "Passthru: %s\n",
- rme9652->passthru ? "yes" : "no");
+ snd_iprintf(buffer, "Passthru: %s\n", str_yes_no(rme9652->passthru));
if ((rme9652->control_register & (RME9652_Master | RME9652_wsel)) == 0) {
snd_iprintf(buffer, "Clock mode: autosync\n");
@@ -1685,7 +1684,7 @@ snd_rme9652_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buff
snd_iprintf(buffer, "\n");
snd_iprintf(buffer, "Timecode signal: %s\n",
- (status & RME9652_tc_valid) ? "yes" : "no");
+ str_yes_no(status & RME9652_tc_valid));
/* thru modes */
diff --git a/sound/pci/sonicvibes.c b/sound/pci/sonicvibes.c
index f91cbf6eeca0..c30eaf1038e7 100644
--- a/sound/pci/sonicvibes.c
+++ b/sound/pci/sonicvibes.c
@@ -1118,7 +1118,7 @@ static void snd_sonicvibes_proc_read(struct snd_info_entry *entry,
tmp = sonic->srs_space & 0x0f;
snd_iprintf(buffer, "SRS 3D : %s\n",
- sonic->srs_space & 0x80 ? "off" : "on");
+ str_off_on(sonic->srs_space & 0x80));
snd_iprintf(buffer, "SRS Space : %s\n",
tmp == 0x00 ? "100%" :
tmp == 0x01 ? "75%" :
@@ -1135,9 +1135,9 @@ static void snd_sonicvibes_proc_read(struct snd_info_entry *entry,
tmp == 0x00 ? "on-board ROM" :
tmp == 0x01 ? "PCI bus" : "on-board ROM + PCI bus");
tmp = sonic->mpu_switch;
- snd_iprintf(buffer, "Onboard synth : %s\n", tmp & 0x01 ? "on" : "off");
- snd_iprintf(buffer, "Ext. Rx to synth : %s\n", tmp & 0x02 ? "on" : "off");
- snd_iprintf(buffer, "MIDI to ext. Tx : %s\n", tmp & 0x04 ? "on" : "off");
+ snd_iprintf(buffer, "Onboard synth : %s\n", str_on_off(tmp & 0x01));
+ snd_iprintf(buffer, "Ext. Rx to synth : %s\n", str_on_off(tmp & 0x02));
+ snd_iprintf(buffer, "MIDI to ext. Tx : %s\n", str_on_off(tmp & 0x04));
}
static void snd_sonicvibes_proc_init(struct sonicvibes *sonic)
diff --git a/sound/pci/trident/trident_main.c b/sound/pci/trident/trident_main.c
index e98eea1e6d81..8039f445bee2 100644
--- a/sound/pci/trident/trident_main.c
+++ b/sound/pci/trident/trident_main.c
@@ -3278,9 +3278,9 @@ static void snd_trident_proc_read(struct snd_info_entry *entry,
snd_iprintf(buffer, "Spurious IRQs : %d\n", trident->spurious_irq_count);
snd_iprintf(buffer, "Spurious IRQ dlta: %d\n", trident->spurious_irq_max_delta);
if (trident->device == TRIDENT_DEVICE_ID_NX || trident->device == TRIDENT_DEVICE_ID_SI7018)
- snd_iprintf(buffer, "IEC958 Mixer Out : %s\n", trident->spdif_ctrl == 0x28 ? "on" : "off");
+ snd_iprintf(buffer, "IEC958 Mixer Out : %s\n", str_on_off(trident->spdif_ctrl == 0x28));
if (trident->device == TRIDENT_DEVICE_ID_NX) {
- snd_iprintf(buffer, "Rear Speakers : %s\n", trident->ac97_ctrl & 0x00000010 ? "on" : "off");
+ snd_iprintf(buffer, "Rear Speakers : %s\n", str_on_off(trident->ac97_ctrl & 0x00000010));
if (trident->tlb.entries) {
snd_iprintf(buffer,"\nVirtual Memory\n");
snd_iprintf(buffer, "Memory Maximum : %d\n", trident->tlb.memhdr->size);
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 0ba319683b6b..ee35f3aa5521 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -2451,6 +2451,7 @@ config SND_SOC_WM8993
config SND_SOC_WM8994
tristate
+ depends on MFD_WM8994
config SND_SOC_WM8995
tristate
diff --git a/sound/soc/codecs/cs42l43.c b/sound/soc/codecs/cs42l43.c
index bee032c3ebbe..d2a2daefc2ec 100644
--- a/sound/soc/codecs/cs42l43.c
+++ b/sound/soc/codecs/cs42l43.c
@@ -2404,6 +2404,7 @@ static int cs42l43_codec_runtime_resume(struct device *dev)
static const struct dev_pm_ops cs42l43_codec_pm_ops = {
RUNTIME_PM_OPS(NULL, cs42l43_codec_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
};
static const struct platform_device_id cs42l43_codec_id_table[] = {
diff --git a/sound/soc/codecs/es8316.c b/sound/soc/codecs/es8316.c
index 61729e5b50a8..f508df01145b 100644
--- a/sound/soc/codecs/es8316.c
+++ b/sound/soc/codecs/es8316.c
@@ -39,7 +39,9 @@ struct es8316_priv {
struct snd_soc_jack *jack;
int irq;
unsigned int sysclk;
- unsigned int allowed_rates[ARRAY_SIZE(supported_mclk_lrck_ratios)];
+ /* ES83xx supports halving the MCLK so it supports twice as many rates
+ */
+ unsigned int allowed_rates[ARRAY_SIZE(supported_mclk_lrck_ratios) * 2];
struct snd_pcm_hw_constraint_list sysclk_constraints;
bool jd_inverted;
};
@@ -386,6 +388,12 @@ static int es8316_set_dai_sysclk(struct snd_soc_dai *codec_dai,
if (freq % ratio == 0)
es8316->allowed_rates[count++] = freq / ratio;
+
+ /* We also check if the halved MCLK produces a valid rate
+ * since the codec supports halving the MCLK.
+ */
+ if ((freq / ratio) % 2 == 0)
+ es8316->allowed_rates[count++] = freq / ratio / 2;
}
if (count) {
diff --git a/sound/soc/codecs/es8326.c b/sound/soc/codecs/es8326.c
index a5603b617688..b06eead7e0f6 100644
--- a/sound/soc/codecs/es8326.c
+++ b/sound/soc/codecs/es8326.c
@@ -616,7 +616,7 @@ static int es8326_mute(struct snd_soc_dai *dai, int mute, int direction)
0x0F, 0x0F);
if (es8326->version > ES8326_VERSION_B) {
regmap_update_bits(es8326->regmap, ES8326_VMIDSEL, 0x40, 0x40);
- regmap_update_bits(es8326->regmap, ES8326_ANA_MICBIAS, 0x70, 0x10);
+ regmap_update_bits(es8326->regmap, ES8326_ANA_MICBIAS, 0x70, 0x30);
}
}
} else {
@@ -631,6 +631,8 @@ static int es8326_mute(struct snd_soc_dai *dai, int mute, int direction)
regmap_write(es8326->regmap, ES8326_HPR_OFFSET_INI, offset_r);
es8326->calibrated = true;
}
+ regmap_update_bits(es8326->regmap, ES8326_CLK_INV, 0xc0, 0x00);
+ regmap_update_bits(es8326->regmap, ES8326_CLK_MUX, 0x80, 0x00);
if (direction == SNDRV_PCM_STREAM_PLAYBACK) {
regmap_update_bits(es8326->regmap, ES8326_DAC_DSM, 0x01, 0x01);
usleep_range(1000, 5000);
@@ -645,7 +647,7 @@ static int es8326_mute(struct snd_soc_dai *dai, int mute, int direction)
} else {
msleep(300);
if (es8326->version > ES8326_VERSION_B) {
- regmap_update_bits(es8326->regmap, ES8326_ANA_MICBIAS, 0x70, 0x50);
+ regmap_update_bits(es8326->regmap, ES8326_ANA_MICBIAS, 0x70, 0x70);
regmap_update_bits(es8326->regmap, ES8326_VMIDSEL, 0x40, 0x00);
}
regmap_update_bits(es8326->regmap, ES8326_ADC_MUTE,
@@ -676,6 +678,10 @@ static int es8326_set_bias_level(struct snd_soc_component *codec,
regmap_write(es8326->regmap, ES8326_ANA_PDN, 0x00);
regmap_update_bits(es8326->regmap, ES8326_CLK_CTL, 0x20, 0x20);
regmap_update_bits(es8326->regmap, ES8326_RESET, 0x02, 0x00);
+ if (es8326->version > ES8326_VERSION_B) {
+ regmap_update_bits(es8326->regmap, ES8326_VMIDSEL, 0x40, 0x40);
+ regmap_update_bits(es8326->regmap, ES8326_ANA_MICBIAS, 0x70, 0x30);
+ }
break;
case SND_SOC_BIAS_PREPARE:
break;
@@ -683,6 +689,12 @@ static int es8326_set_bias_level(struct snd_soc_component *codec,
regmap_write(es8326->regmap, ES8326_ANA_PDN, 0x3b);
regmap_update_bits(es8326->regmap, ES8326_CLK_CTL, 0x20, 0x00);
regmap_write(es8326->regmap, ES8326_SDINOUT1_IO, ES8326_IO_INPUT);
+ if (es8326->version > ES8326_VERSION_B) {
+ regmap_update_bits(es8326->regmap, ES8326_VMIDSEL, 0x40, 0x40);
+ regmap_update_bits(es8326->regmap, ES8326_ANA_MICBIAS, 0x70, 0x10);
+ }
+ regmap_update_bits(es8326->regmap, ES8326_CLK_INV, 0xc0, 0xc0);
+ regmap_update_bits(es8326->regmap, ES8326_CLK_MUX, 0x80, 0x80);
break;
case SND_SOC_BIAS_OFF:
clk_disable_unprepare(es8326->mclk);
@@ -773,7 +785,10 @@ static void es8326_jack_button_handler(struct work_struct *work)
case 0x6f:
case 0x4b:
/* button volume up */
- cur_button = SND_JACK_BTN_1;
+ if ((iface == 0x6f) && (es8326->version > ES8326_VERSION_B))
+ cur_button = SND_JACK_BTN_0;
+ else
+ cur_button = SND_JACK_BTN_1;
break;
case 0x27:
/* button volume down */
@@ -1082,7 +1097,7 @@ static void es8326_init(struct snd_soc_component *component)
regmap_write(es8326->regmap, ES8326_ADC2_SRC, 0x66);
es8326_disable_micbias(es8326->component);
if (es8326->version > ES8326_VERSION_B) {
- regmap_update_bits(es8326->regmap, ES8326_ANA_MICBIAS, 0x73, 0x13);
+ regmap_update_bits(es8326->regmap, ES8326_ANA_MICBIAS, 0x73, 0x10);
regmap_update_bits(es8326->regmap, ES8326_VMIDSEL, 0x40, 0x40);
}
diff --git a/sound/soc/codecs/nau8824.c b/sound/soc/codecs/nau8824.c
index 12540397fd4d..5aaf8c496300 100644
--- a/sound/soc/codecs/nau8824.c
+++ b/sound/soc/codecs/nau8824.c
@@ -368,13 +368,13 @@ static const struct snd_kcontrol_new nau8824_snd_controls[] = {
SOC_ENUM("DAC Oversampling Rate", nau8824_dac_oversampl_enum),
SOC_SINGLE_TLV("Speaker Right DACR Volume",
- NAU8824_REG_CLASSD_GAIN_1, 8, 0x1f, 0, spk_vol_tlv),
+ NAU8824_REG_CLASSD_GAIN_1, 8, 0x19, 0, spk_vol_tlv),
SOC_SINGLE_TLV("Speaker Left DACL Volume",
- NAU8824_REG_CLASSD_GAIN_2, 0, 0x1f, 0, spk_vol_tlv),
+ NAU8824_REG_CLASSD_GAIN_2, 0, 0x19, 0, spk_vol_tlv),
SOC_SINGLE_TLV("Speaker Left DACR Volume",
- NAU8824_REG_CLASSD_GAIN_1, 0, 0x1f, 0, spk_vol_tlv),
+ NAU8824_REG_CLASSD_GAIN_1, 0, 0x19, 0, spk_vol_tlv),
SOC_SINGLE_TLV("Speaker Right DACL Volume",
- NAU8824_REG_CLASSD_GAIN_2, 8, 0x1f, 0, spk_vol_tlv),
+ NAU8824_REG_CLASSD_GAIN_2, 8, 0x19, 0, spk_vol_tlv),
SOC_SINGLE_TLV("Headphone Right DACR Volume",
NAU8824_REG_ATT_PORT0, 8, 0x1f, 0, hp_vol_tlv),
diff --git a/sound/soc/codecs/tas2781-i2c.c b/sound/soc/codecs/tas2781-i2c.c
index 127e2be07318..a730ab6ad4e3 100644
--- a/sound/soc/codecs/tas2781-i2c.c
+++ b/sound/soc/codecs/tas2781-i2c.c
@@ -78,7 +78,7 @@ static const struct bulk_reg_val tas2781_cali_start_reg[] = {
X2781_CL_STT_VAL(TAS2781_PRM_INT_MASK_REG, 0xfe, false),
X2781_CL_STT_VAL(TAS2781_PRM_CLK_CFG_REG, 0xdd, false),
X2781_CL_STT_VAL(TAS2781_PRM_RSVD_REG, 0x20, false),
- X2781_CL_STT_VAL(TAS2781_PRM_TEST_57_REG, 0x14, false),
+ X2781_CL_STT_VAL(TAS2781_PRM_TEST_57_REG, 0x14, true),
X2781_CL_STT_VAL(TAS2781_PRM_TEST_62_REG, 0x45, true),
X2781_CL_STT_VAL(TAS2781_PRM_PVDD_UVLO_REG, 0x03, false),
X2781_CL_STT_VAL(TAS2781_PRM_CHNL_0_REG, 0xa8, false),
diff --git a/sound/soc/generic/simple-card-utils.c b/sound/soc/generic/simple-card-utils.c
index e25b387a9776..dd414634b4ac 100644
--- a/sound/soc/generic/simple-card-utils.c
+++ b/sound/soc/generic/simple-card-utils.c
@@ -295,7 +295,7 @@ int simple_util_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct simple_util_priv *priv = snd_soc_card_get_drvdata(rtd->card);
- struct simple_dai_props *props = simple_priv_to_props(priv, rtd->id);
+ struct simple_dai_props *props = runtime_simple_priv_to_props(priv, rtd);
struct simple_util_dai *dai;
unsigned int fixed_sysclk = 0;
int i1, i2, i;
@@ -356,7 +356,7 @@ void simple_util_shutdown(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct simple_util_priv *priv = snd_soc_card_get_drvdata(rtd->card);
- struct simple_dai_props *props = simple_priv_to_props(priv, rtd->id);
+ struct simple_dai_props *props = runtime_simple_priv_to_props(priv, rtd);
struct simple_util_dai *dai;
int i;
@@ -445,7 +445,7 @@ int simple_util_hw_params(struct snd_pcm_substream *substream,
struct simple_util_dai *pdai;
struct snd_soc_dai *sdai;
struct simple_util_priv *priv = snd_soc_card_get_drvdata(rtd->card);
- struct simple_dai_props *props = simple_priv_to_props(priv, rtd->id);
+ struct simple_dai_props *props = runtime_simple_priv_to_props(priv, rtd);
unsigned int mclk, mclk_fs = 0;
int i, ret;
@@ -516,7 +516,7 @@ int simple_util_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
struct snd_pcm_hw_params *params)
{
struct simple_util_priv *priv = snd_soc_card_get_drvdata(rtd->card);
- struct simple_dai_props *dai_props = simple_priv_to_props(priv, rtd->id);
+ struct simple_dai_props *dai_props = runtime_simple_priv_to_props(priv, rtd);
struct simple_util_data *data = &dai_props->adata;
struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
struct snd_interval *channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
@@ -627,7 +627,7 @@ static int simple_init_for_codec2codec(struct snd_soc_pcm_runtime *rtd,
int simple_util_dai_init(struct snd_soc_pcm_runtime *rtd)
{
struct simple_util_priv *priv = snd_soc_card_get_drvdata(rtd->card);
- struct simple_dai_props *props = simple_priv_to_props(priv, rtd->id);
+ struct simple_dai_props *props = runtime_simple_priv_to_props(priv, rtd);
struct simple_util_dai *dai;
int i, ret;
diff --git a/sound/soc/intel/avs/apl.c b/sound/soc/intel/avs/apl.c
index d443fe8d51ae..3dccf0a57a3a 100644
--- a/sound/soc/intel/avs/apl.c
+++ b/sound/soc/intel/avs/apl.c
@@ -12,6 +12,7 @@
#include "avs.h"
#include "messages.h"
#include "path.h"
+#include "registers.h"
#include "topology.h"
static irqreturn_t avs_apl_dsp_interrupt(struct avs_dev *adev)
diff --git a/sound/soc/intel/avs/cnl.c b/sound/soc/intel/avs/cnl.c
index bd3c4bb8bf5a..03f8fb0dc187 100644
--- a/sound/soc/intel/avs/cnl.c
+++ b/sound/soc/intel/avs/cnl.c
@@ -9,6 +9,7 @@
#include <sound/hdaudio_ext.h>
#include "avs.h"
#include "messages.h"
+#include "registers.h"
static void avs_cnl_ipc_interrupt(struct avs_dev *adev)
{
diff --git a/sound/soc/intel/avs/registers.h b/sound/soc/intel/avs/registers.h
index 64afd56b1897..368ede05f2cd 100644
--- a/sound/soc/intel/avs/registers.h
+++ b/sound/soc/intel/avs/registers.h
@@ -9,6 +9,8 @@
#ifndef __SOUND_SOC_INTEL_AVS_REGS_H
#define __SOUND_SOC_INTEL_AVS_REGS_H
+#include <linux/io-64-nonatomic-lo-hi.h>
+#include <linux/iopoll.h>
#include <linux/sizes.h>
#define AZX_PCIREG_PGCTL 0x44
@@ -98,4 +100,47 @@
#define avs_downlink_addr(adev) \
avs_sram_addr(adev, AVS_DOWNLINK_WINDOW)
+#define snd_hdac_adsp_writeb(adev, reg, value) \
+ snd_hdac_reg_writeb(&(adev)->base.core, (adev)->dsp_ba + (reg), value)
+#define snd_hdac_adsp_readb(adev, reg) \
+ snd_hdac_reg_readb(&(adev)->base.core, (adev)->dsp_ba + (reg))
+#define snd_hdac_adsp_writew(adev, reg, value) \
+ snd_hdac_reg_writew(&(adev)->base.core, (adev)->dsp_ba + (reg), value)
+#define snd_hdac_adsp_readw(adev, reg) \
+ snd_hdac_reg_readw(&(adev)->base.core, (adev)->dsp_ba + (reg))
+#define snd_hdac_adsp_writel(adev, reg, value) \
+ snd_hdac_reg_writel(&(adev)->base.core, (adev)->dsp_ba + (reg), value)
+#define snd_hdac_adsp_readl(adev, reg) \
+ snd_hdac_reg_readl(&(adev)->base.core, (adev)->dsp_ba + (reg))
+#define snd_hdac_adsp_writeq(adev, reg, value) \
+ snd_hdac_reg_writeq(&(adev)->base.core, (adev)->dsp_ba + (reg), value)
+#define snd_hdac_adsp_readq(adev, reg) \
+ snd_hdac_reg_readq(&(adev)->base.core, (adev)->dsp_ba + (reg))
+
+#define snd_hdac_adsp_updateb(adev, reg, mask, val) \
+ snd_hdac_adsp_writeb(adev, reg, \
+ (snd_hdac_adsp_readb(adev, reg) & ~(mask)) | (val))
+#define snd_hdac_adsp_updatew(adev, reg, mask, val) \
+ snd_hdac_adsp_writew(adev, reg, \
+ (snd_hdac_adsp_readw(adev, reg) & ~(mask)) | (val))
+#define snd_hdac_adsp_updatel(adev, reg, mask, val) \
+ snd_hdac_adsp_writel(adev, reg, \
+ (snd_hdac_adsp_readl(adev, reg) & ~(mask)) | (val))
+#define snd_hdac_adsp_updateq(adev, reg, mask, val) \
+ snd_hdac_adsp_writeq(adev, reg, \
+ (snd_hdac_adsp_readq(adev, reg) & ~(mask)) | (val))
+
+#define snd_hdac_adsp_readb_poll(adev, reg, val, cond, delay_us, timeout_us) \
+ readb_poll_timeout((adev)->dsp_ba + (reg), val, cond, \
+ delay_us, timeout_us)
+#define snd_hdac_adsp_readw_poll(adev, reg, val, cond, delay_us, timeout_us) \
+ readw_poll_timeout((adev)->dsp_ba + (reg), val, cond, \
+ delay_us, timeout_us)
+#define snd_hdac_adsp_readl_poll(adev, reg, val, cond, delay_us, timeout_us) \
+ readl_poll_timeout((adev)->dsp_ba + (reg), val, cond, \
+ delay_us, timeout_us)
+#define snd_hdac_adsp_readq_poll(adev, reg, val, cond, delay_us, timeout_us) \
+ readq_poll_timeout((adev)->dsp_ba + (reg), val, cond, \
+ delay_us, timeout_us)
+
#endif /* __SOUND_SOC_INTEL_AVS_REGS_H */
diff --git a/sound/soc/intel/avs/skl.c b/sound/soc/intel/avs/skl.c
index 34f859d6e5a4..d66ef000de9e 100644
--- a/sound/soc/intel/avs/skl.c
+++ b/sound/soc/intel/avs/skl.c
@@ -12,6 +12,7 @@
#include "avs.h"
#include "cldma.h"
#include "messages.h"
+#include "registers.h"
void avs_skl_ipc_interrupt(struct avs_dev *adev)
{
diff --git a/sound/soc/intel/boards/sof_sdw.c b/sound/soc/intel/boards/sof_sdw.c
index 9f721b88e606..b0d35fda7b17 100644
--- a/sound/soc/intel/boards/sof_sdw.c
+++ b/sound/soc/intel/boards/sof_sdw.c
@@ -612,17 +612,32 @@ static const struct dmi_system_id sof_sdw_quirk_table[] = {
.callback = sof_sdw_quirk_cb,
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
- DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "3838")
+ DMI_MATCH(DMI_PRODUCT_NAME, "83JX")
},
- .driver_data = (void *)(SOC_SDW_SIDECAR_AMPS),
+ .driver_data = (void *)(SOC_SDW_SIDECAR_AMPS | SOC_SDW_CODEC_MIC),
},
{
.callback = sof_sdw_quirk_cb,
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
- DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "3832")
+ DMI_MATCH(DMI_PRODUCT_NAME, "83LC")
},
- .driver_data = (void *)(SOC_SDW_SIDECAR_AMPS),
+ .driver_data = (void *)(SOC_SDW_SIDECAR_AMPS | SOC_SDW_CODEC_MIC),
+ },
+ {
+ .callback = sof_sdw_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "83MC")
+ },
+ .driver_data = (void *)(SOC_SDW_SIDECAR_AMPS | SOC_SDW_CODEC_MIC),
+ }, {
+ .callback = sof_sdw_quirk_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "83NM")
+ },
+ .driver_data = (void *)(SOC_SDW_SIDECAR_AMPS | SOC_SDW_CODEC_MIC),
},
{
.callback = sof_sdw_quirk_cb,
diff --git a/sound/soc/qcom/lpass-platform.c b/sound/soc/qcom/lpass-platform.c
index addd2c4bdd3e..9946f12254b3 100644
--- a/sound/soc/qcom/lpass-platform.c
+++ b/sound/soc/qcom/lpass-platform.c
@@ -1232,14 +1232,16 @@ static int lpass_platform_copy(struct snd_soc_component *component,
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
if (is_cdc_dma_port(dai_id)) {
- ret = copy_from_iter_toio(dma_buf, buf, bytes);
+ if (copy_from_iter_toio(dma_buf, bytes, buf) != bytes)
+ ret = -EFAULT;
} else {
if (copy_from_iter((void __force *)dma_buf, bytes, buf) != bytes)
ret = -EFAULT;
}
} else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
if (is_cdc_dma_port(dai_id)) {
- ret = copy_to_iter_fromio(buf, dma_buf, bytes);
+ if (copy_to_iter_fromio(dma_buf, bytes, buf) != bytes)
+ ret = -EFAULT;
} else {
if (copy_to_iter((void __force *)dma_buf, bytes, buf) != bytes)
ret = -EFAULT;
diff --git a/sound/soc/renesas/rcar/adg.c b/sound/soc/renesas/rcar/adg.c
index 0f190abf00e7..191f212d338c 100644
--- a/sound/soc/renesas/rcar/adg.c
+++ b/sound/soc/renesas/rcar/adg.c
@@ -374,12 +374,12 @@ int rsnd_adg_ssi_clk_try_start(struct rsnd_mod *ssi_mod, unsigned int rate)
return 0;
}
-void rsnd_adg_clk_control(struct rsnd_priv *priv, int enable)
+int rsnd_adg_clk_control(struct rsnd_priv *priv, int enable)
{
struct rsnd_adg *adg = rsnd_priv_to_adg(priv);
struct rsnd_mod *adg_mod = rsnd_mod_get(adg);
struct clk *clk;
- int i;
+ int ret = 0, i;
if (enable) {
rsnd_mod_bset(adg_mod, BRGCKR, 0x80770000, adg->ckr);
@@ -389,18 +389,33 @@ void rsnd_adg_clk_control(struct rsnd_priv *priv, int enable)
for_each_rsnd_clkin(clk, adg, i) {
if (enable) {
- clk_prepare_enable(clk);
+ ret = clk_prepare_enable(clk);
/*
* We shouldn't use clk_get_rate() under
* atomic context. Let's keep it when
* rsnd_adg_clk_enable() was called
*/
+ if (ret < 0)
+ break;
+
adg->clkin_rate[i] = clk_get_rate(clk);
} else {
- clk_disable_unprepare(clk);
+ if (adg->clkin_rate[i])
+ clk_disable_unprepare(clk);
+
+ adg->clkin_rate[i] = 0;
}
}
+
+ /*
+ * rsnd_adg_clk_enable() might return error (_disable() will not).
+ * We need to rollback in such case
+ */
+ if (ret < 0)
+ rsnd_adg_clk_disable(priv);
+
+ return ret;
}
static struct clk *rsnd_adg_create_null_clk(struct rsnd_priv *priv,
@@ -753,7 +768,10 @@ int rsnd_adg_probe(struct rsnd_priv *priv)
if (ret)
return ret;
- rsnd_adg_clk_enable(priv);
+ ret = rsnd_adg_clk_enable(priv);
+ if (ret)
+ return ret;
+
rsnd_adg_clk_dbg_info(priv, NULL);
return 0;
diff --git a/sound/soc/renesas/rcar/core.c b/sound/soc/renesas/rcar/core.c
index e2234928c9e8..d3709fd0409e 100644
--- a/sound/soc/renesas/rcar/core.c
+++ b/sound/soc/renesas/rcar/core.c
@@ -2086,9 +2086,7 @@ static int __maybe_unused rsnd_resume(struct device *dev)
{
struct rsnd_priv *priv = dev_get_drvdata(dev);
- rsnd_adg_clk_enable(priv);
-
- return 0;
+ return rsnd_adg_clk_enable(priv);
}
static const struct dev_pm_ops rsnd_pm_ops = {
diff --git a/sound/soc/renesas/rcar/rsnd.h b/sound/soc/renesas/rcar/rsnd.h
index 3c164d8e3b16..a5f54b65313c 100644
--- a/sound/soc/renesas/rcar/rsnd.h
+++ b/sound/soc/renesas/rcar/rsnd.h
@@ -608,7 +608,7 @@ int rsnd_adg_set_cmd_timsel_gen2(struct rsnd_mod *cmd_mod,
struct rsnd_dai_stream *io);
#define rsnd_adg_clk_enable(priv) rsnd_adg_clk_control(priv, 1)
#define rsnd_adg_clk_disable(priv) rsnd_adg_clk_control(priv, 0)
-void rsnd_adg_clk_control(struct rsnd_priv *priv, int enable);
+int rsnd_adg_clk_control(struct rsnd_priv *priv, int enable);
void rsnd_adg_clk_dbg_info(struct rsnd_priv *priv, struct seq_file *m);
/*
diff --git a/sound/soc/samsung/Kconfig b/sound/soc/samsung/Kconfig
index 4b1ea7b2c796..60b4b7b75215 100644
--- a/sound/soc/samsung/Kconfig
+++ b/sound/soc/samsung/Kconfig
@@ -127,8 +127,9 @@ config SND_SOC_SAMSUNG_TM2_WM5110
config SND_SOC_SAMSUNG_ARIES_WM8994
tristate "SoC I2S Audio support for WM8994 on Aries"
- depends on SND_SOC_SAMSUNG && MFD_WM8994 && IIO && EXTCON
+ depends on SND_SOC_SAMSUNG && I2C && IIO && EXTCON
select SND_SOC_BT_SCO
+ select MFD_WM8994
select SND_SOC_WM8994
select SND_SAMSUNG_I2S
help
@@ -140,8 +141,9 @@ config SND_SOC_SAMSUNG_ARIES_WM8994
config SND_SOC_SAMSUNG_MIDAS_WM1811
tristate "SoC I2S Audio support for Midas boards"
- depends on SND_SOC_SAMSUNG && IIO
+ depends on SND_SOC_SAMSUNG && I2C && IIO
select SND_SAMSUNG_I2S
+ select MFD_WM8994
select SND_SOC_WM8994
help
Say Y if you want to add support for SoC audio on the Midas boards.
diff --git a/sound/usb/Makefile b/sound/usb/Makefile
index 0532499dbc6d..30bd5348477b 100644
--- a/sound/usb/Makefile
+++ b/sound/usb/Makefile
@@ -6,6 +6,7 @@
snd-usb-audio-y := card.o \
clock.o \
endpoint.o \
+ fcp.o \
format.o \
helper.o \
implicit.o \
diff --git a/sound/usb/fcp.c b/sound/usb/fcp.c
new file mode 100644
index 000000000000..ecdd18335ab7
--- /dev/null
+++ b/sound/usb/fcp.c
@@ -0,0 +1,1134 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Focusrite Control Protocol Driver for ALSA
+ *
+ * Copyright (c) 2024-2025 by Geoffrey D. Bennett <g at b4.vu>
+ */
+/*
+ * DOC: Theory of Operation
+ *
+ * The Focusrite Control Protocol (FCP) driver provides a minimal
+ * kernel interface that allows a user-space driver (primarily
+ * fcp-server) to communicate with Focusrite USB audio interfaces
+ * using their vendor-specific protocol. This protocol is used by
+ * Scarlett 2nd Gen, 3rd Gen, 4th Gen, Clarett USB, Clarett+, and
+ * Vocaster series devices.
+ *
+ * Unlike the existing scarlett2 driver which implements all controls
+ * in kernel space, this driver takes a lighter-weight approach by
+ * moving most functionality to user space. The only control
+ * implemented in kernel space is the Level Meter, since it requires
+ * frequent polling of volatile data.
+ *
+ * The driver provides an hwdep interface that allows the user-space
+ * driver to:
+ * - Initialise the protocol
+ * - Send arbitrary FCP commands to the device
+ * - Receive notifications from the device
+ * - Configure the Level Meter control
+ *
+ * Usage Flow
+ * ----------
+ * 1. Open the hwdep device (requires CAP_SYS_RAWIO)
+ * 2. Get protocol version using FCP_IOCTL_PVERSION
+ * 3. Initialise protocol using FCP_IOCTL_INIT
+ * 4. Send commands using FCP_IOCTL_CMD
+ * 5. Receive notifications using read()
+ * 6. Optionally set up the Level Meter control using
+ * FCP_IOCTL_SET_METER_MAP
+ * 7. Optionally add labels to the Level Meter control using
+ * FCP_IOCTL_SET_METER_LABELS
+ *
+ * Level Meter
+ * -----------
+ * The Level Meter is implemented as an ALSA control that provides
+ * real-time level monitoring. When the control is read, the driver
+ * requests the current meter levels from the device, translates the
+ * levels using the configured mapping, and returns the result to the
+ * user. The mapping between device meters and the ALSA control's
+ * channels is configured with FCP_IOCTL_SET_METER_MAP.
+ *
+ * Labels for the Level Meter channels can be set using
+ * FCP_IOCTL_SET_METER_LABELS and read by applications through the
+ * control's TLV data. The labels are transferred as a sequence of
+ * null-terminated strings.
+ */
+
+#include <linux/slab.h>
+#include <linux/usb.h>
+
+#include <sound/control.h>
+#include <sound/hwdep.h>
+#include <sound/tlv.h>
+
+#include <uapi/sound/fcp.h>
+
+#include "usbaudio.h"
+#include "mixer.h"
+#include "helper.h"
+
+#include "fcp.h"
+
+/* notify waiting to send to *file */
+struct fcp_notify {
+ wait_queue_head_t queue;
+ u32 event;
+ spinlock_t lock;
+};
+
+struct fcp_data {
+ struct usb_mixer_interface *mixer;
+
+ struct mutex mutex; /* serialise access to the device */
+ struct completion cmd_done; /* wait for command completion */
+ struct file *file; /* hwdep file */
+
+ struct fcp_notify notify;
+
+ u8 bInterfaceNumber;
+ u8 bEndpointAddress;
+ u16 wMaxPacketSize;
+ u8 bInterval;
+
+ uint16_t step0_resp_size;
+ uint16_t step2_resp_size;
+ uint32_t init1_opcode;
+ uint32_t init2_opcode;
+
+ u8 init;
+ u16 seq;
+
+ u8 num_meter_slots;
+ s16 *meter_level_map;
+ u32 *meter_levels;
+ struct snd_kcontrol *meter_ctl;
+
+ unsigned int *meter_labels_tlv;
+ int meter_labels_tlv_size;
+};
+
+/*** USB Interactions ***/
+
+/* FCP Command ACK notification bit */
+#define FCP_NOTIFY_ACK 1
+
+/* Vendor-specific USB control requests */
+#define FCP_USB_REQ_STEP0 0
+#define FCP_USB_REQ_CMD_TX 2
+#define FCP_USB_REQ_CMD_RX 3
+
+/* Focusrite Control Protocol opcodes that the kernel side needs to
+ * know about
+ */
+#define FCP_USB_REBOOT 0x00000003
+#define FCP_USB_GET_METER 0x00001001
+#define FCP_USB_FLASH_ERASE 0x00004002
+#define FCP_USB_FLASH_WRITE 0x00004004
+
+#define FCP_USB_METER_LEVELS_GET_MAGIC 1
+
+#define FCP_SEGMENT_APP_GOLD 0
+
+/* Forward declarations */
+static int fcp_init(struct usb_mixer_interface *mixer,
+ void *step0_resp, void *step2_resp);
+
+/* FCP command request/response format */
+struct fcp_usb_packet {
+ __le32 opcode;
+ __le16 size;
+ __le16 seq;
+ __le32 error;
+ __le32 pad;
+ u8 data[];
+};
+
+static void fcp_fill_request_header(struct fcp_data *private,
+ struct fcp_usb_packet *req,
+ u32 opcode, u16 req_size)
+{
+ /* sequence must go up by 1 for each request */
+ u16 seq = private->seq++;
+
+ req->opcode = cpu_to_le32(opcode);
+ req->size = cpu_to_le16(req_size);
+ req->seq = cpu_to_le16(seq);
+ req->error = 0;
+ req->pad = 0;
+}
+
+static int fcp_usb_tx(struct usb_device *dev, int interface,
+ void *buf, u16 size)
+{
+ return snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0),
+ FCP_USB_REQ_CMD_TX,
+ USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT,
+ 0, interface, buf, size);
+}
+
+static int fcp_usb_rx(struct usb_device *dev, int interface,
+ void *buf, u16 size)
+{
+ return snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0),
+ FCP_USB_REQ_CMD_RX,
+ USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
+ 0, interface, buf, size);
+}
+
+/* Send an FCP command and get the response */
+static int fcp_usb(struct usb_mixer_interface *mixer, u32 opcode,
+ const void *req_data, u16 req_size,
+ void *resp_data, u16 resp_size)
+{
+ struct fcp_data *private = mixer->private_data;
+ struct usb_device *dev = mixer->chip->dev;
+ struct fcp_usb_packet *req __free(kfree) = NULL;
+ struct fcp_usb_packet *resp __free(kfree) = NULL;
+ size_t req_buf_size = struct_size(req, data, req_size);
+ size_t resp_buf_size = struct_size(resp, data, resp_size);
+ int retries = 0;
+ const int max_retries = 5;
+ int err;
+
+ if (!mixer->urb)
+ return -ENODEV;
+
+ req = kmalloc(req_buf_size, GFP_KERNEL);
+ if (!req)
+ return -ENOMEM;
+
+ resp = kmalloc(resp_buf_size, GFP_KERNEL);
+ if (!resp)
+ return -ENOMEM;
+
+ /* build request message */
+ fcp_fill_request_header(private, req, opcode, req_size);
+ if (req_size)
+ memcpy(req->data, req_data, req_size);
+
+ /* send the request and retry on EPROTO */
+retry:
+ err = fcp_usb_tx(dev, private->bInterfaceNumber, req, req_buf_size);
+ if (err == -EPROTO && ++retries <= max_retries) {
+ msleep(1 << (retries - 1));
+ goto retry;
+ }
+
+ if (err != req_buf_size) {
+ usb_audio_err(mixer->chip,
+ "FCP request %08x failed: %d\n", opcode, err);
+ return -EINVAL;
+ }
+
+ if (!wait_for_completion_timeout(&private->cmd_done,
+ msecs_to_jiffies(1000))) {
+ usb_audio_err(mixer->chip,
+ "FCP request %08x timed out\n", opcode);
+
+ return -ETIMEDOUT;
+ }
+
+ /* send a second message to get the response */
+ err = fcp_usb_rx(dev, private->bInterfaceNumber, resp, resp_buf_size);
+
+ /* validate the response */
+
+ if (err < 0) {
+
+ /* ESHUTDOWN and EPROTO are valid responses to a
+ * reboot request
+ */
+ if (opcode == FCP_USB_REBOOT &&
+ (err == -ESHUTDOWN || err == -EPROTO))
+ return 0;
+
+ usb_audio_err(mixer->chip,
+ "FCP read response %08x failed: %d\n",
+ opcode, err);
+ return -EINVAL;
+ }
+
+ if (err < sizeof(*resp)) {
+ usb_audio_err(mixer->chip,
+ "FCP response %08x too short: %d\n",
+ opcode, err);
+ return -EINVAL;
+ }
+
+ if (req->seq != resp->seq) {
+ usb_audio_err(mixer->chip,
+ "FCP response %08x seq mismatch %d/%d\n",
+ opcode,
+ le16_to_cpu(req->seq), le16_to_cpu(resp->seq));
+ return -EINVAL;
+ }
+
+ if (req->opcode != resp->opcode) {
+ usb_audio_err(mixer->chip,
+ "FCP response %08x opcode mismatch %08x\n",
+ opcode, le16_to_cpu(resp->opcode));
+ return -EINVAL;
+ }
+
+ if (resp->error) {
+ usb_audio_err(mixer->chip,
+ "FCP response %08x error %d\n",
+ opcode, le32_to_cpu(resp->error));
+ return -EINVAL;
+ }
+
+ if (err != resp_buf_size) {
+ usb_audio_err(mixer->chip,
+ "FCP response %08x buffer size mismatch %d/%zu\n",
+ opcode, err, resp_buf_size);
+ return -EINVAL;
+ }
+
+ if (resp_size != le16_to_cpu(resp->size)) {
+ usb_audio_err(mixer->chip,
+ "FCP response %08x size mismatch %d/%d\n",
+ opcode, resp_size, le16_to_cpu(resp->size));
+ return -EINVAL;
+ }
+
+ if (resp_data && resp_size > 0)
+ memcpy(resp_data, resp->data, resp_size);
+
+ return 0;
+}
+
+static int fcp_reinit(struct usb_mixer_interface *mixer)
+{
+ struct fcp_data *private = mixer->private_data;
+ void *step0_resp __free(kfree) = NULL;
+ void *step2_resp __free(kfree) = NULL;
+
+ if (mixer->urb)
+ return 0;
+
+ step0_resp = kmalloc(private->step0_resp_size, GFP_KERNEL);
+ if (!step0_resp)
+ return -ENOMEM;
+ step2_resp = kmalloc(private->step2_resp_size, GFP_KERNEL);
+ if (!step2_resp)
+ return -ENOMEM;
+
+ return fcp_init(mixer, step0_resp, step2_resp);
+}
+
+/*** Control Functions ***/
+
+/* helper function to create a new control */
+static int fcp_add_new_ctl(struct usb_mixer_interface *mixer,
+ const struct snd_kcontrol_new *ncontrol,
+ int index, int channels, const char *name,
+ struct snd_kcontrol **kctl_return)
+{
+ struct snd_kcontrol *kctl;
+ struct usb_mixer_elem_info *elem;
+ int err;
+
+ elem = kzalloc(sizeof(*elem), GFP_KERNEL);
+ if (!elem)
+ return -ENOMEM;
+
+ /* We set USB_MIXER_BESPOKEN type, so that the core USB mixer code
+ * ignores them for resume and other operations.
+ * Also, the head.id field is set to 0, as we don't use this field.
+ */
+ elem->head.mixer = mixer;
+ elem->control = index;
+ elem->head.id = 0;
+ elem->channels = channels;
+ elem->val_type = USB_MIXER_BESPOKEN;
+
+ kctl = snd_ctl_new1(ncontrol, elem);
+ if (!kctl) {
+ kfree(elem);
+ return -ENOMEM;
+ }
+ kctl->private_free = snd_usb_mixer_elem_free;
+
+ strscpy(kctl->id.name, name, sizeof(kctl->id.name));
+
+ err = snd_usb_mixer_add_control(&elem->head, kctl);
+ if (err < 0)
+ return err;
+
+ if (kctl_return)
+ *kctl_return = kctl;
+
+ return 0;
+}
+
+/*** Level Meter Control ***/
+
+static int fcp_meter_ctl_info(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct usb_mixer_elem_info *elem = kctl->private_data;
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = elem->channels;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 4095;
+ uinfo->value.integer.step = 1;
+ return 0;
+}
+
+static int fcp_meter_ctl_get(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct usb_mixer_elem_info *elem = kctl->private_data;
+ struct usb_mixer_interface *mixer = elem->head.mixer;
+ struct fcp_data *private = mixer->private_data;
+ int num_meter_slots, resp_size;
+ u32 *resp = private->meter_levels;
+ int i, err = 0;
+
+ struct {
+ __le16 pad;
+ __le16 num_meters;
+ __le32 magic;
+ } __packed req;
+
+ guard(mutex)(&private->mutex);
+
+ err = fcp_reinit(mixer);
+ if (err < 0)
+ return err;
+
+ num_meter_slots = private->num_meter_slots;
+ resp_size = num_meter_slots * sizeof(u32);
+
+ req.pad = 0;
+ req.num_meters = cpu_to_le16(num_meter_slots);
+ req.magic = cpu_to_le32(FCP_USB_METER_LEVELS_GET_MAGIC);
+ err = fcp_usb(mixer, FCP_USB_GET_METER,
+ &req, sizeof(req), resp, resp_size);
+ if (err < 0)
+ return err;
+
+ /* copy & translate from resp[] using meter_level_map[] */
+ for (i = 0; i < elem->channels; i++) {
+ int idx = private->meter_level_map[i];
+ int value = idx < 0 ? 0 : le32_to_cpu(resp[idx]);
+
+ ucontrol->value.integer.value[i] = value;
+ }
+
+ return 0;
+}
+
+static int fcp_meter_tlv_callback(struct snd_kcontrol *kctl,
+ int op_flag, unsigned int size,
+ unsigned int __user *tlv)
+{
+ struct usb_mixer_elem_info *elem = kctl->private_data;
+ struct usb_mixer_interface *mixer = elem->head.mixer;
+ struct fcp_data *private = mixer->private_data;
+
+ guard(mutex)(&private->mutex);
+
+ if (op_flag == SNDRV_CTL_TLV_OP_READ) {
+ if (private->meter_labels_tlv_size == 0)
+ return 0;
+
+ if (size > private->meter_labels_tlv_size)
+ size = private->meter_labels_tlv_size;
+
+ if (copy_to_user(tlv, private->meter_labels_tlv, size))
+ return -EFAULT;
+
+ return size;
+ }
+
+ return -EINVAL;
+}
+
+static const struct snd_kcontrol_new fcp_meter_ctl = {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = fcp_meter_ctl_info,
+ .get = fcp_meter_ctl_get,
+ .tlv = { .c = fcp_meter_tlv_callback },
+};
+
+/*** hwdep interface ***/
+
+/* FCP initialisation */
+static int fcp_ioctl_init(struct usb_mixer_interface *mixer,
+ struct fcp_init __user *arg)
+{
+ struct fcp_init init;
+ struct usb_device *dev = mixer->chip->dev;
+ struct fcp_data *private = mixer->private_data;
+ void *resp __free(kfree) = NULL;
+ void *step2_resp;
+ int err, buf_size;
+
+ if (usb_pipe_type_check(dev, usb_sndctrlpipe(dev, 0)))
+ return -EINVAL;
+
+ /* Get initialisation parameters */
+ if (copy_from_user(&init, arg, sizeof(init)))
+ return -EFAULT;
+
+ /* Validate the response sizes */
+ if (init.step0_resp_size < 1 ||
+ init.step0_resp_size > 255 ||
+ init.step2_resp_size < 1 ||
+ init.step2_resp_size > 255)
+ return -EINVAL;
+
+ /* Allocate response buffer */
+ buf_size = init.step0_resp_size + init.step2_resp_size;
+
+ resp = kmalloc(buf_size, GFP_KERNEL);
+ if (!resp)
+ return -ENOMEM;
+
+ private->step0_resp_size = init.step0_resp_size;
+ private->step2_resp_size = init.step2_resp_size;
+ private->init1_opcode = init.init1_opcode;
+ private->init2_opcode = init.init2_opcode;
+
+ step2_resp = resp + private->step0_resp_size;
+
+ err = fcp_init(mixer, resp, step2_resp);
+ if (err < 0)
+ return err;
+
+ if (copy_to_user(arg->resp, resp, buf_size))
+ return -EFAULT;
+
+ return 0;
+}
+
+/* Check that the command is allowed
+ * Don't permit erasing/writing segment 0 (App_Gold)
+ */
+static int fcp_validate_cmd(u32 opcode, void *data, u16 size)
+{
+ if (opcode == FCP_USB_FLASH_ERASE) {
+ struct {
+ __le32 segment_num;
+ __le32 pad;
+ } __packed *req = data;
+
+ if (size != sizeof(*req))
+ return -EINVAL;
+
+ if (le32_to_cpu(req->segment_num) == FCP_SEGMENT_APP_GOLD)
+ return -EPERM;
+
+ if (req->pad != 0)
+ return -EINVAL;
+
+ } else if (opcode == FCP_USB_FLASH_WRITE) {
+ struct {
+ __le32 segment_num;
+ __le32 offset;
+ __le32 pad;
+ u8 data[];
+ } __packed *req = data;
+
+ if (size < sizeof(*req))
+ return -EINVAL;
+
+ if (le32_to_cpu(req->segment_num) == FCP_SEGMENT_APP_GOLD)
+ return -EPERM;
+
+ if (req->pad != 0)
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/* Execute an FCP command specified by the user */
+static int fcp_ioctl_cmd(struct usb_mixer_interface *mixer,
+ struct fcp_cmd __user *arg)
+{
+ struct fcp_cmd cmd;
+ int err, buf_size;
+ void *data __free(kfree) = NULL;
+
+ /* get opcode and request/response size */
+ if (copy_from_user(&cmd, arg, sizeof(cmd)))
+ return -EFAULT;
+
+ /* validate request and response sizes */
+ if (cmd.req_size > 4096 || cmd.resp_size > 4096)
+ return -EINVAL;
+
+ /* reinit if needed */
+ err = fcp_reinit(mixer);
+ if (err < 0)
+ return err;
+
+ /* allocate request/response buffer */
+ buf_size = max(cmd.req_size, cmd.resp_size);
+
+ if (buf_size > 0) {
+ data = kmalloc(buf_size, GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+ }
+
+ /* copy request from user */
+ if (cmd.req_size > 0)
+ if (copy_from_user(data, arg->data, cmd.req_size))
+ return -EFAULT;
+
+ /* check that the command is allowed */
+ err = fcp_validate_cmd(cmd.opcode, data, cmd.req_size);
+ if (err < 0)
+ return err;
+
+ /* send request, get response */
+ err = fcp_usb(mixer, cmd.opcode,
+ data, cmd.req_size, data, cmd.resp_size);
+ if (err < 0)
+ return err;
+
+ /* copy response to user */
+ if (cmd.resp_size > 0)
+ if (copy_to_user(arg->data, data, cmd.resp_size))
+ return -EFAULT;
+
+ return 0;
+}
+
+/* Validate the Level Meter map passed by the user */
+static int validate_meter_map(const s16 *map, int map_size, int meter_slots)
+{
+ int i;
+
+ for (i = 0; i < map_size; i++)
+ if (map[i] < -1 || map[i] >= meter_slots)
+ return -EINVAL;
+
+ return 0;
+}
+
+/* Set the Level Meter map and add the control */
+static int fcp_ioctl_set_meter_map(struct usb_mixer_interface *mixer,
+ struct fcp_meter_map __user *arg)
+{
+ struct fcp_meter_map map;
+ struct fcp_data *private = mixer->private_data;
+ s16 *tmp_map __free(kfree) = NULL;
+ int err;
+
+ if (copy_from_user(&map, arg, sizeof(map)))
+ return -EFAULT;
+
+ /* Don't allow changing the map size or meter slots once set */
+ if (private->meter_ctl) {
+ struct usb_mixer_elem_info *elem =
+ private->meter_ctl->private_data;
+
+ if (map.map_size != elem->channels ||
+ map.meter_slots != private->num_meter_slots)
+ return -EINVAL;
+ }
+
+ /* Validate the map size */
+ if (map.map_size < 1 || map.map_size > 255 ||
+ map.meter_slots < 1 || map.meter_slots > 255)
+ return -EINVAL;
+
+ /* Allocate and copy the map data */
+ tmp_map = kmalloc_array(map.map_size, sizeof(s16), GFP_KERNEL);
+ if (!tmp_map)
+ return -ENOMEM;
+
+ if (copy_from_user(tmp_map, arg->map, map.map_size * sizeof(s16)))
+ return -EFAULT;
+
+ err = validate_meter_map(tmp_map, map.map_size, map.meter_slots);
+ if (err < 0)
+ return err;
+
+ /* If the control doesn't exist, create it */
+ if (!private->meter_ctl) {
+ s16 *new_map __free(kfree) = NULL;
+ u32 *meter_levels __free(kfree) = NULL;
+
+ /* Allocate buffer for the map */
+ new_map = kmalloc_array(map.map_size, sizeof(s16), GFP_KERNEL);
+ if (!new_map)
+ return -ENOMEM;
+
+ /* Allocate buffer for reading meter levels */
+ meter_levels = kmalloc_array(map.meter_slots, sizeof(u32),
+ GFP_KERNEL);
+ if (!meter_levels)
+ return -ENOMEM;
+
+ /* Create the Level Meter control */
+ err = fcp_add_new_ctl(mixer, &fcp_meter_ctl, 0, map.map_size,
+ "Level Meter", &private->meter_ctl);
+ if (err < 0)
+ return err;
+
+ /* Success; save the pointers in private and don't free them */
+ private->meter_level_map = new_map;
+ private->meter_levels = meter_levels;
+ private->num_meter_slots = map.meter_slots;
+ new_map = NULL;
+ meter_levels = NULL;
+ }
+
+ /* Install the new map */
+ memcpy(private->meter_level_map, tmp_map, map.map_size * sizeof(s16));
+
+ return 0;
+}
+
+/* Set the Level Meter labels */
+static int fcp_ioctl_set_meter_labels(struct usb_mixer_interface *mixer,
+ struct fcp_meter_labels __user *arg)
+{
+ struct fcp_meter_labels labels;
+ struct fcp_data *private = mixer->private_data;
+ unsigned int *tlv_data;
+ unsigned int tlv_size, data_size;
+
+ if (copy_from_user(&labels, arg, sizeof(labels)))
+ return -EFAULT;
+
+ /* Remove existing labels if size is zero */
+ if (!labels.labels_size) {
+
+ /* Clear TLV read/callback bits if labels were present */
+ if (private->meter_labels_tlv) {
+ private->meter_ctl->vd[0].access &=
+ ~(SNDRV_CTL_ELEM_ACCESS_TLV_READ |
+ SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK);
+ snd_ctl_notify(mixer->chip->card,
+ SNDRV_CTL_EVENT_MASK_INFO,
+ &private->meter_ctl->id);
+ }
+
+ kfree(private->meter_labels_tlv);
+ private->meter_labels_tlv = NULL;
+ private->meter_labels_tlv_size = 0;
+
+ return 0;
+ }
+
+ /* Validate size */
+ if (labels.labels_size > 4096)
+ return -EINVAL;
+
+ /* Calculate padded data size */
+ data_size = ALIGN(labels.labels_size, sizeof(unsigned int));
+
+ /* Calculate total TLV size including header */
+ tlv_size = sizeof(unsigned int) * 2 + data_size;
+
+ /* Allocate, set up TLV header, and copy the labels data */
+ tlv_data = kzalloc(tlv_size, GFP_KERNEL);
+ if (!tlv_data)
+ return -ENOMEM;
+ tlv_data[0] = SNDRV_CTL_TLVT_FCP_CHANNEL_LABELS;
+ tlv_data[1] = data_size;
+ if (copy_from_user(&tlv_data[2], arg->labels, labels.labels_size)) {
+ kfree(tlv_data);
+ return -EFAULT;
+ }
+
+ /* Set TLV read/callback bits if labels weren't present */
+ if (!private->meter_labels_tlv) {
+ private->meter_ctl->vd[0].access |=
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ |
+ SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK;
+ snd_ctl_notify(mixer->chip->card,
+ SNDRV_CTL_EVENT_MASK_INFO,
+ &private->meter_ctl->id);
+ }
+
+ /* Swap in the new labels */
+ kfree(private->meter_labels_tlv);
+ private->meter_labels_tlv = tlv_data;
+ private->meter_labels_tlv_size = tlv_size;
+
+ return 0;
+}
+
+static int fcp_hwdep_open(struct snd_hwdep *hw, struct file *file)
+{
+ struct usb_mixer_interface *mixer = hw->private_data;
+ struct fcp_data *private = mixer->private_data;
+
+ if (!capable(CAP_SYS_RAWIO))
+ return -EPERM;
+
+ private->file = file;
+
+ return 0;
+}
+
+static int fcp_hwdep_ioctl(struct snd_hwdep *hw, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct usb_mixer_interface *mixer = hw->private_data;
+ struct fcp_data *private = mixer->private_data;
+ void __user *argp = (void __user *)arg;
+
+ guard(mutex)(&private->mutex);
+
+ switch (cmd) {
+
+ case FCP_IOCTL_PVERSION:
+ return put_user(FCP_HWDEP_VERSION,
+ (int __user *)argp) ? -EFAULT : 0;
+ break;
+
+ case FCP_IOCTL_INIT:
+ return fcp_ioctl_init(mixer, argp);
+
+ case FCP_IOCTL_CMD:
+ if (!private->init)
+ return -EINVAL;
+ return fcp_ioctl_cmd(mixer, argp);
+
+ case FCP_IOCTL_SET_METER_MAP:
+ if (!private->init)
+ return -EINVAL;
+ return fcp_ioctl_set_meter_map(mixer, argp);
+
+ case FCP_IOCTL_SET_METER_LABELS:
+ if (!private->init)
+ return -EINVAL;
+ if (!private->meter_ctl)
+ return -EINVAL;
+ return fcp_ioctl_set_meter_labels(mixer, argp);
+
+ default:
+ return -ENOIOCTLCMD;
+ }
+
+ /* not reached */
+}
+
+static long fcp_hwdep_read(struct snd_hwdep *hw, char __user *buf,
+ long count, loff_t *offset)
+{
+ struct usb_mixer_interface *mixer = hw->private_data;
+ struct fcp_data *private = mixer->private_data;
+ unsigned long flags;
+ long ret = 0;
+ u32 event;
+
+ if (count < sizeof(event))
+ return -EINVAL;
+
+ ret = wait_event_interruptible(private->notify.queue,
+ private->notify.event);
+ if (ret)
+ return ret;
+
+ spin_lock_irqsave(&private->notify.lock, flags);
+ event = private->notify.event;
+ private->notify.event = 0;
+ spin_unlock_irqrestore(&private->notify.lock, flags);
+
+ if (copy_to_user(buf, &event, sizeof(event)))
+ return -EFAULT;
+
+ return sizeof(event);
+}
+
+static unsigned int fcp_hwdep_poll(struct snd_hwdep *hw,
+ struct file *file,
+ poll_table *wait)
+{
+ struct usb_mixer_interface *mixer = hw->private_data;
+ struct fcp_data *private = mixer->private_data;
+ unsigned int mask = 0;
+
+ poll_wait(file, &private->notify.queue, wait);
+
+ if (private->notify.event)
+ mask |= POLLIN | POLLRDNORM;
+
+ return mask;
+}
+
+static int fcp_hwdep_release(struct snd_hwdep *hw, struct file *file)
+{
+ struct usb_mixer_interface *mixer = hw->private_data;
+ struct fcp_data *private = mixer->private_data;
+
+ if (!private)
+ return 0;
+
+ private->file = NULL;
+
+ return 0;
+}
+
+static int fcp_hwdep_init(struct usb_mixer_interface *mixer)
+{
+ struct snd_hwdep *hw;
+ int err;
+
+ err = snd_hwdep_new(mixer->chip->card, "Focusrite Control", 0, &hw);
+ if (err < 0)
+ return err;
+
+ hw->private_data = mixer;
+ hw->exclusive = 1;
+ hw->ops.open = fcp_hwdep_open;
+ hw->ops.ioctl = fcp_hwdep_ioctl;
+ hw->ops.ioctl_compat = fcp_hwdep_ioctl;
+ hw->ops.read = fcp_hwdep_read;
+ hw->ops.poll = fcp_hwdep_poll;
+ hw->ops.release = fcp_hwdep_release;
+
+ return 0;
+}
+
+/*** Cleanup ***/
+
+static void fcp_cleanup_urb(struct usb_mixer_interface *mixer)
+{
+ if (!mixer->urb)
+ return;
+
+ usb_kill_urb(mixer->urb);
+ kfree(mixer->urb->transfer_buffer);
+ usb_free_urb(mixer->urb);
+ mixer->urb = NULL;
+}
+
+static void fcp_private_free(struct usb_mixer_interface *mixer)
+{
+ struct fcp_data *private = mixer->private_data;
+
+ fcp_cleanup_urb(mixer);
+
+ kfree(private->meter_level_map);
+ kfree(private->meter_levels);
+ kfree(private->meter_labels_tlv);
+ kfree(private);
+ mixer->private_data = NULL;
+}
+
+static void fcp_private_suspend(struct usb_mixer_interface *mixer)
+{
+ fcp_cleanup_urb(mixer);
+}
+
+/*** Callbacks ***/
+
+static void fcp_notify(struct urb *urb)
+{
+ struct usb_mixer_interface *mixer = urb->context;
+ struct fcp_data *private = mixer->private_data;
+ int len = urb->actual_length;
+ int ustatus = urb->status;
+ u32 data;
+
+ if (ustatus != 0 || len != 8)
+ goto requeue;
+
+ data = le32_to_cpu(*(__le32 *)urb->transfer_buffer);
+
+ /* Handle command acknowledgement */
+ if (data & FCP_NOTIFY_ACK) {
+ complete(&private->cmd_done);
+ data &= ~FCP_NOTIFY_ACK;
+ }
+
+ if (data) {
+ unsigned long flags;
+
+ spin_lock_irqsave(&private->notify.lock, flags);
+ private->notify.event |= data;
+ spin_unlock_irqrestore(&private->notify.lock, flags);
+
+ wake_up_interruptible(&private->notify.queue);
+ }
+
+requeue:
+ if (ustatus != -ENOENT &&
+ ustatus != -ECONNRESET &&
+ ustatus != -ESHUTDOWN) {
+ urb->dev = mixer->chip->dev;
+ usb_submit_urb(urb, GFP_ATOMIC);
+ } else {
+ complete(&private->cmd_done);
+ }
+}
+
+/* Submit a URB to receive notifications from the device */
+static int fcp_init_notify(struct usb_mixer_interface *mixer)
+{
+ struct usb_device *dev = mixer->chip->dev;
+ struct fcp_data *private = mixer->private_data;
+ unsigned int pipe = usb_rcvintpipe(dev, private->bEndpointAddress);
+ void *transfer_buffer;
+ int err;
+
+ /* Already set up */
+ if (mixer->urb)
+ return 0;
+
+ if (usb_pipe_type_check(dev, pipe))
+ return -EINVAL;
+
+ mixer->urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!mixer->urb)
+ return -ENOMEM;
+
+ transfer_buffer = kmalloc(private->wMaxPacketSize, GFP_KERNEL);
+ if (!transfer_buffer) {
+ usb_free_urb(mixer->urb);
+ mixer->urb = NULL;
+ return -ENOMEM;
+ }
+
+ usb_fill_int_urb(mixer->urb, dev, pipe,
+ transfer_buffer, private->wMaxPacketSize,
+ fcp_notify, mixer, private->bInterval);
+
+ init_completion(&private->cmd_done);
+
+ err = usb_submit_urb(mixer->urb, GFP_KERNEL);
+ if (err) {
+ usb_audio_err(mixer->chip,
+ "%s: usb_submit_urb failed: %d\n",
+ __func__, err);
+ kfree(transfer_buffer);
+ usb_free_urb(mixer->urb);
+ mixer->urb = NULL;
+ }
+
+ return err;
+}
+
+/*** Initialisation ***/
+
+static int fcp_init(struct usb_mixer_interface *mixer,
+ void *step0_resp, void *step2_resp)
+{
+ struct fcp_data *private = mixer->private_data;
+ struct usb_device *dev = mixer->chip->dev;
+ int err;
+
+ err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0),
+ FCP_USB_REQ_STEP0,
+ USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
+ 0, private->bInterfaceNumber,
+ step0_resp, private->step0_resp_size);
+ if (err < 0)
+ return err;
+
+ err = fcp_init_notify(mixer);
+ if (err < 0)
+ return err;
+
+ private->seq = 0;
+ private->init = 1;
+
+ err = fcp_usb(mixer, private->init1_opcode, NULL, 0, NULL, 0);
+ if (err < 0)
+ return err;
+
+ err = fcp_usb(mixer, private->init2_opcode,
+ NULL, 0, step2_resp, private->step2_resp_size);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+static int fcp_init_private(struct usb_mixer_interface *mixer)
+{
+ struct fcp_data *private =
+ kzalloc(sizeof(struct fcp_data), GFP_KERNEL);
+
+ if (!private)
+ return -ENOMEM;
+
+ mutex_init(&private->mutex);
+ init_waitqueue_head(&private->notify.queue);
+ spin_lock_init(&private->notify.lock);
+
+ mixer->private_data = private;
+ mixer->private_free = fcp_private_free;
+ mixer->private_suspend = fcp_private_suspend;
+
+ private->mixer = mixer;
+
+ return 0;
+}
+
+/* Look through the interface descriptors for the Focusrite Control
+ * interface (bInterfaceClass = 255 Vendor Specific Class) and set
+ * bInterfaceNumber, bEndpointAddress, wMaxPacketSize, and bInterval
+ * in private
+ */
+static int fcp_find_fc_interface(struct usb_mixer_interface *mixer)
+{
+ struct snd_usb_audio *chip = mixer->chip;
+ struct fcp_data *private = mixer->private_data;
+ struct usb_host_config *config = chip->dev->actconfig;
+ int i;
+
+ for (i = 0; i < config->desc.bNumInterfaces; i++) {
+ struct usb_interface *intf = config->interface[i];
+ struct usb_interface_descriptor *desc =
+ &intf->altsetting[0].desc;
+ struct usb_endpoint_descriptor *epd;
+
+ if (desc->bInterfaceClass != 255)
+ continue;
+
+ epd = get_endpoint(intf->altsetting, 0);
+ private->bInterfaceNumber = desc->bInterfaceNumber;
+ private->bEndpointAddress = epd->bEndpointAddress &
+ USB_ENDPOINT_NUMBER_MASK;
+ private->wMaxPacketSize = le16_to_cpu(epd->wMaxPacketSize);
+ private->bInterval = epd->bInterval;
+ return 0;
+ }
+
+ usb_audio_err(chip, "Focusrite vendor-specific interface not found\n");
+ return -EINVAL;
+}
+
+int snd_fcp_init(struct usb_mixer_interface *mixer)
+{
+ struct snd_usb_audio *chip = mixer->chip;
+ int err;
+
+ /* only use UAC_VERSION_2 */
+ if (!mixer->protocol)
+ return 0;
+
+ err = fcp_init_private(mixer);
+ if (err < 0)
+ return err;
+
+ err = fcp_find_fc_interface(mixer);
+ if (err < 0)
+ return err;
+
+ err = fcp_hwdep_init(mixer);
+ if (err < 0)
+ return err;
+
+ usb_audio_info(chip,
+ "Focusrite Control Protocol Driver ready (pid=0x%04x); "
+ "report any issues to "
+ "https://github.com/geoffreybennett/fcp-support/issues",
+ USB_ID_PRODUCT(chip->usb_id));
+
+ return err;
+}
diff --git a/sound/usb/fcp.h b/sound/usb/fcp.h
new file mode 100644
index 000000000000..d58cc7db75b1
--- /dev/null
+++ b/sound/usb/fcp.h
@@ -0,0 +1,7 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __USBAUDIO_FCP_H
+#define __USBAUDIO_FCP_H
+
+int snd_fcp_init(struct usb_mixer_interface *mixer);
+
+#endif /* __USBAUDIO_FCP_H */
diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c
index 23fcd680167d..ed6127b0389f 100644
--- a/sound/usb/mixer_quirks.c
+++ b/sound/usb/mixer_quirks.c
@@ -38,6 +38,7 @@
#include "mixer_us16x08.h"
#include "mixer_s1810c.h"
#include "helper.h"
+#include "fcp.h"
struct std_mono_table {
unsigned int unitid, control, cmask;
@@ -4090,6 +4091,12 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer)
err = snd_scarlett2_init(mixer);
break;
+ case USB_ID(0x1235, 0x821b): /* Focusrite Scarlett 16i16 4th Gen */
+ case USB_ID(0x1235, 0x821c): /* Focusrite Scarlett 18i16 4th Gen */
+ case USB_ID(0x1235, 0x821d): /* Focusrite Scarlett 18i20 4th Gen */
+ err = snd_fcp_init(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_scarlett2.c b/sound/usb/mixer_scarlett2.c
index 7f595c1752a5..288d22e6a0b2 100644
--- a/sound/usb/mixer_scarlett2.c
+++ b/sound/usb/mixer_scarlett2.c
@@ -166,6 +166,7 @@
#include "helper.h"
#include "mixer_scarlett2.h"
+#include "fcp.h"
/* device_setup value to allow turning MSD mode back on */
#define SCARLETT2_MSD_ENABLE 0x02
@@ -173,6 +174,9 @@
/* device_setup value to disable this mixer driver */
#define SCARLETT2_DISABLE 0x04
+/* device_setup value to use the FCP driver instead */
+#define SCARLETT2_USE_FCP_DRIVER 0x08
+
/* some gui mixers can't handle negative ctl values */
#define SCARLETT2_VOLUME_BIAS 127
@@ -9702,6 +9706,10 @@ int snd_scarlett2_init(struct usb_mixer_interface *mixer)
if (!mixer->protocol)
return 0;
+ /* check if the user wants to use the FCP driver instead */
+ if (chip->setup & SCARLETT2_USE_FCP_DRIVER)
+ return snd_fcp_init(mixer);
+
/* find entry in scarlett2_devices */
entry = get_scarlett2_device_entry(mixer);
if (!entry) {
diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c
index 8ba0aff8be2e..7968d6a2f592 100644
--- a/sound/usb/quirks.c
+++ b/sound/usb/quirks.c
@@ -2239,6 +2239,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = {
QUIRK_FLAG_CTL_MSG_DELAY_1M),
DEVICE_FLG(0x0c45, 0x6340, /* Sonix HD USB Camera */
QUIRK_FLAG_GET_SAMPLE_RATE),
+ DEVICE_FLG(0x0d8c, 0x0014, /* USB Audio Device */
+ QUIRK_FLAG_CTL_MSG_DELAY_1M),
DEVICE_FLG(0x0ecb, 0x205c, /* JBL Quantum610 Wireless */
QUIRK_FLAG_FIXED_RATE),
DEVICE_FLG(0x0ecb, 0x2069, /* JBL Quantum810 Wireless */