sound updates for 5.5-rc1
There have been some significant changes in the core side, both for ALSA and ASoC, while lots of development have been seen in SOF, as well as many small fixes/improvements for ASoC codecs and platforms. Below is a highlight in this cycle: Core: - The unification of PCM vmalloc buffer allocation helpers into the standard API - Clean up of the default PCM mmap handling for vmalloc & SG-buffer - Fix potential races at ALSA timer open - A few new PCM API extensions; just preliminary core changes, the actual changes in drivers will be merged in 5.6 - Continued ASoC componentization works; now almost everything is a common ASoC component object. A lot of refactoring and simplification have been done along with it. ASoC: - Many fixes to the Sound Open Firmware (SOF) code - Wake on voice support for Chromebooks - SPI support and trigger word detection for RT5677 - New drivers for Analog Devices ADAU7118, Intel Cannonlake systems with RT1011 and RT5682, Texas Instruments TAS2562 and TAS2770 HD-audio: - Improved Intel DSP configuration / probe code for SOF - Plumbing the legacy HD-audio driver with Intel SOF HDMI - DP-MST support for Nvidia HDMI codecs - Realtek quirks cleanups and new additions as usual Others: - Lots of refactoring and cleanups for FireWire; period-size sharing, h/w IRQ interval configuration, clock recovery improvements, etc - USB-audio: Scarlett mixer quirks - Cleanups of PCM calls in various drivers (including media and USB) to adapt the core API changes -----BEGIN PGP SIGNATURE----- iQJCBAABCAAsFiEEIXTw5fNLNI7mMiVaLtJE4w1nLE8FAl3cAmcOHHRpd2FpQHN1 c2UuZGUACgkQLtJE4w1nLE9UXxAAnybNeJRjvq5jCXIdASNjT7L1GdvEpjsYaqis LGibFi8ekJmbs6PH8ALn5aRaaZgM9EONIU/BJ3ItlsX96OZ/o+PT5QBy67osF39K zgS0z37puptznj4wy5+istfn7aYuCCSobN9K/3xSS/yX5ibvneBr2d88gKa/u7mn ubKKR8r+asBXLyKEHYhtif8IXN7lttYQgIiVGSTGe8a8V0C1VU2VQOFbCVAv2guv tg7PYzaQYa/37XLKgdRIovvJGC6r498/aI3aA7dSttUuAlZge6HT9iD/TDhChvmA OGyfnH7SViRtp8zlDfCyiwi2vlXSFHrkFYRQaf7Ov4uhAUPlQhehyEAs5SCl3zOB Z9BSGYyiyzUCwoy6nnxzsjA+6CSaVx7ceW33Zc64wie4CsvmaWT+QssJ3IQkB+WF VQTM0gnzaEKF1yR7jeTFc9ndWFnnHbCRR2WWWsn/U4lxsHczdpt/RoLi+TxTm3YP Qb4atYtydgnwFcMvIlWGh68/MzaP3yK9lh0Ckr0GsRgRgMs/nqK/gZMlvCosDMRP Hc8j7cSACXF1EZ+dGlVa+q/qiYD9rAFQa8f8h4WB4En6yqkZ+qilk/z/A7sdb8bt VaaoOWTK4xEiVeV23RMO74+kPZazkkju636EWTvc2zBTJ6upkaT+geUV/e4g1aSc r/gylz8= =xGUt -----END PGP SIGNATURE----- Merge tag 'sound-5.5-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound Pull sound updates from Takashi Iwai: "There have been some significant changes in the core side, both for ALSA and ASoC, while lots of development have been seen in SOF, as well as many small fixes/improvements for ASoC codecs and platforms. Below is a highlight in this cycle: Core: - The unification of PCM vmalloc buffer allocation helpers into the standard API - Clean up of the default PCM mmap handling for vmalloc & SG-buffer - Fix potential races at ALSA timer open - A few new PCM API extensions; just preliminary core changes, the actual changes in drivers will be merged in 5.6 - Continued ASoC componentization works; now almost everything is a common ASoC component object. A lot of refactoring and simplification have been done along with it. ASoC: - Many fixes to the Sound Open Firmware (SOF) code - Wake on voice support for Chromebooks - SPI support and trigger word detection for RT5677 - New drivers for Analog Devices ADAU7118, Intel Cannonlake systems with RT1011 and RT5682, Texas Instruments TAS2562 and TAS2770 HD-audio: - Improved Intel DSP configuration / probe code for SOF - Plumbing the legacy HD-audio driver with Intel SOF HDMI - DP-MST support for Nvidia HDMI codecs - Realtek quirks cleanups and new additions as usual Others: - Lots of refactoring and cleanups for FireWire; period-size sharing, h/w IRQ interval configuration, clock recovery improvements, etc - USB-audio: Scarlett mixer quirks - Cleanups of PCM calls in various drivers (including media and USB) to adapt the core API changes" * tag 'sound-5.5-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound: (497 commits) ALSA: usb-audio: Fix Focusrite Scarlett 6i6 gen1 - input handling ALSA: hda/realtek - Enable internal speaker of ASUS UX431FLC ALSA: aloop: Fix dependency on timer API ASoC: DMI long name - avoid to add board name if matches with product name ASoC: improve the DMI long card code in asoc-core ASoC: rsnd: fix DALIGN register for SSIU ALSA: aloop: Avoid unexpected timer event callback tasklets ALSA: aloop: Remove redundant locking in timer open function ASoC: component: Add sync_stop PCM ops ASoC: pcm: Make ioctl ops optional ALSA: hda/hdmi - Clear codec->relaxed_resume flag at unbinding ALSA: hda - Disable audio component for legacy Nvidia HDMI codecs ALSA: cs4236: fix error return comparison of an unsigned integer ALSA: usb-audio: Fix NULL dereference at parsing BADD ALSA: usb-audio: Fix Scarlett 6i6 Gen 2 port data ALSA: hda/realtek - Enable the headset-mic on a Xiaomi's laptop ALSA: hda/realtek - Move some alc236 pintbls to fallback table ALSA: hda/realtek - Move some alc256 pintbls to fallback table ALSA: docs: Update about the new PCM sync_stop ops ALSA: pcm: Add card sync_irq field ...
This commit is contained in:
commit
3f1b210a7f
|
@ -0,0 +1,85 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/sound/adi,adau7118.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
|
||||
title: Analog Devices ADAU7118 8 Channel PDM to I2S/TDM Converter
|
||||
|
||||
maintainers:
|
||||
- Nuno Sá <nuno.sa@analog.com>
|
||||
|
||||
description: |
|
||||
Analog Devices ADAU7118 8 Channel PDM to I2S/TDM Converter over I2C or HW
|
||||
standalone mode.
|
||||
https://www.analog.com/media/en/technical-documentation/data-sheets/ADAU7118.pdf
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- adi,adau7118
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
"#sound-dai-cells":
|
||||
const: 0
|
||||
|
||||
iovdd-supply:
|
||||
description: Digital Input/Output Power Supply.
|
||||
|
||||
dvdd-supply:
|
||||
description: Internal Core Digital Power Supply.
|
||||
|
||||
adi,decimation-ratio:
|
||||
description: |
|
||||
This property set's the decimation ratio of PDM to PCM audio data.
|
||||
allOf:
|
||||
- $ref: /schemas/types.yaml#/definitions/uint32
|
||||
- enum: [64, 32, 16]
|
||||
default: 64
|
||||
|
||||
adi,pdm-clk-map:
|
||||
description: |
|
||||
The ADAU7118 has two PDM clocks for the four Inputs. Each input must be
|
||||
assigned to one of these two clocks. This property set's the mapping
|
||||
between the clocks and the inputs.
|
||||
allOf:
|
||||
- $ref: /schemas/types.yaml#/definitions/uint32-array
|
||||
- minItems: 4
|
||||
maxItems: 4
|
||||
items:
|
||||
maximum: 1
|
||||
default: [0, 0, 1, 1]
|
||||
|
||||
required:
|
||||
- "#sound-dai-cells"
|
||||
- compatible
|
||||
- iovdd-supply
|
||||
- dvdd-supply
|
||||
|
||||
examples:
|
||||
- |
|
||||
i2c {
|
||||
/* example with i2c support */
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
adau7118_codec: audio-codec@14 {
|
||||
compatible = "adi,adau7118";
|
||||
reg = <0x14>;
|
||||
#sound-dai-cells = <0>;
|
||||
iovdd-supply = <&supply>;
|
||||
dvdd-supply = <&supply>;
|
||||
adi,pdm-clk-map = <1 1 0 0>;
|
||||
adi,decimation-ratio = <16>;
|
||||
};
|
||||
};
|
||||
|
||||
/* example with hw standalone mode */
|
||||
adau7118_codec_hw: adau7118-codec-hw {
|
||||
compatible = "adi,adau7118";
|
||||
#sound-dai-cells = <0>;
|
||||
iovdd-supply = <&supply>;
|
||||
dvdd-supply = <&supply>;
|
||||
};
|
|
@ -0,0 +1,267 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/sound/allwinner,sun4i-a10-codec.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Allwinner A10 Codec Device Tree Bindings
|
||||
|
||||
maintainers:
|
||||
- Chen-Yu Tsai <wens@csie.org>
|
||||
- Maxime Ripard <maxime.ripard@bootlin.com>
|
||||
|
||||
properties:
|
||||
"#sound-dai-cells":
|
||||
const: 0
|
||||
|
||||
compatible:
|
||||
enum:
|
||||
- allwinner,sun4i-a10-codec
|
||||
- allwinner,sun6i-a31-codec
|
||||
- allwinner,sun7i-a20-codec
|
||||
- allwinner,sun8i-a23-codec
|
||||
- allwinner,sun8i-h3-codec
|
||||
- allwinner,sun8i-v3s-codec
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
clocks:
|
||||
items:
|
||||
- description: Bus Clock
|
||||
- description: Module Clock
|
||||
|
||||
clock-names:
|
||||
items:
|
||||
- const: apb
|
||||
- const: codec
|
||||
|
||||
dmas:
|
||||
items:
|
||||
- description: RX DMA Channel
|
||||
- description: TX DMA Channel
|
||||
|
||||
dma-names:
|
||||
items:
|
||||
- const: rx
|
||||
- const: tx
|
||||
|
||||
resets:
|
||||
maxItems: 1
|
||||
|
||||
allwinner,audio-routing:
|
||||
description: |-
|
||||
A list of the connections between audio components. Each entry
|
||||
is a pair of strings, the first being the connection's sink, the
|
||||
second being the connection's source.
|
||||
allOf:
|
||||
- $ref: /schemas/types.yaml#definitions/non-unique-string-array
|
||||
- minItems: 2
|
||||
maxItems: 18
|
||||
items:
|
||||
enum:
|
||||
# Audio Pins on the SoC
|
||||
- HP
|
||||
- HPCOM
|
||||
- LINEIN
|
||||
- LINEOUT
|
||||
- MIC1
|
||||
- MIC2
|
||||
- MIC3
|
||||
|
||||
# Microphone Biases from the SoC
|
||||
- HBIAS
|
||||
- MBIAS
|
||||
|
||||
# Board Connectors
|
||||
- Headphone
|
||||
- Headset Mic
|
||||
- Line In
|
||||
- Line Out
|
||||
- Mic
|
||||
- Speaker
|
||||
|
||||
allwinner,codec-analog-controls:
|
||||
$ref: /schemas/types.yaml#/definitions/phandle
|
||||
description: Phandle to the codec analog controls in the PRCM
|
||||
|
||||
allwinner,pa-gpios:
|
||||
description: GPIO to enable the external amplifier
|
||||
|
||||
required:
|
||||
- "#sound-dai-cells"
|
||||
- compatible
|
||||
- reg
|
||||
- interrupts
|
||||
- clocks
|
||||
- clock-names
|
||||
- dmas
|
||||
- dma-names
|
||||
|
||||
allOf:
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- allwinner,sun6i-a31-codec
|
||||
- allwinner,sun8i-a23-codec
|
||||
- allwinner,sun8i-h3-codec
|
||||
- allwinner,sun8i-v3s-codec
|
||||
|
||||
then:
|
||||
if:
|
||||
properties:
|
||||
compatible:
|
||||
const: allwinner,sun6i-a31-codec
|
||||
|
||||
then:
|
||||
required:
|
||||
- resets
|
||||
- allwinner,audio-routing
|
||||
|
||||
else:
|
||||
required:
|
||||
- resets
|
||||
- allwinner,audio-routing
|
||||
- allwinner,codec-analog-controls
|
||||
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- allwinner,sun6i-a31-codec
|
||||
|
||||
then:
|
||||
properties:
|
||||
allwinner,audio-routing:
|
||||
items:
|
||||
enum:
|
||||
- HP
|
||||
- HPCOM
|
||||
- LINEIN
|
||||
- LINEOUT
|
||||
- MIC1
|
||||
- MIC2
|
||||
- MIC3
|
||||
- HBIAS
|
||||
- MBIAS
|
||||
- Headphone
|
||||
- Headset Mic
|
||||
- Line In
|
||||
- Line Out
|
||||
- Mic
|
||||
- Speaker
|
||||
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- allwinner,sun8i-a23-codec
|
||||
|
||||
then:
|
||||
properties:
|
||||
allwinner,audio-routing:
|
||||
items:
|
||||
enum:
|
||||
- HP
|
||||
- HPCOM
|
||||
- LINEIN
|
||||
- MIC1
|
||||
- MIC2
|
||||
- HBIAS
|
||||
- MBIAS
|
||||
- Headphone
|
||||
- Headset Mic
|
||||
- Line In
|
||||
- Line Out
|
||||
- Mic
|
||||
- Speaker
|
||||
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- allwinner,sun8i-h3-codec
|
||||
|
||||
then:
|
||||
properties:
|
||||
allwinner,audio-routing:
|
||||
items:
|
||||
enum:
|
||||
- HP
|
||||
- HPCOM
|
||||
- LINEIN
|
||||
- LINEOUT
|
||||
- MIC1
|
||||
- MIC2
|
||||
- HBIAS
|
||||
- MBIAS
|
||||
- Headphone
|
||||
- Headset Mic
|
||||
- Line In
|
||||
- Line Out
|
||||
- Mic
|
||||
- Speaker
|
||||
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- allwinner,sun8i-v3s-codec
|
||||
|
||||
then:
|
||||
properties:
|
||||
allwinner,audio-routing:
|
||||
items:
|
||||
enum:
|
||||
- HP
|
||||
- HPCOM
|
||||
- MIC1
|
||||
- HBIAS
|
||||
- Headphone
|
||||
- Headset Mic
|
||||
- Line In
|
||||
- Line Out
|
||||
- Mic
|
||||
- Speaker
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
codec@1c22c00 {
|
||||
#sound-dai-cells = <0>;
|
||||
compatible = "allwinner,sun7i-a20-codec";
|
||||
reg = <0x01c22c00 0x40>;
|
||||
interrupts = <0 30 4>;
|
||||
clocks = <&apb0_gates 0>, <&codec_clk>;
|
||||
clock-names = "apb", "codec";
|
||||
dmas = <&dma 0 19>, <&dma 0 19>;
|
||||
dma-names = "rx", "tx";
|
||||
};
|
||||
|
||||
- |
|
||||
codec@1c22c00 {
|
||||
#sound-dai-cells = <0>;
|
||||
compatible = "allwinner,sun6i-a31-codec";
|
||||
reg = <0x01c22c00 0x98>;
|
||||
interrupts = <0 29 4>;
|
||||
clocks = <&ccu 61>, <&ccu 135>;
|
||||
clock-names = "apb", "codec";
|
||||
resets = <&ccu 42>;
|
||||
dmas = <&dma 15>, <&dma 15>;
|
||||
dma-names = "rx", "tx";
|
||||
allwinner,audio-routing =
|
||||
"Headphone", "HP",
|
||||
"Speaker", "LINEOUT",
|
||||
"LINEIN", "Line In",
|
||||
"MIC1", "MBIAS",
|
||||
"MIC1", "Mic",
|
||||
"MIC2", "HBIAS",
|
||||
"MIC2", "Headset Mic";
|
||||
};
|
||||
|
||||
...
|
|
@ -0,0 +1,38 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/sound/allwinner,sun8i-a23-codec-analog.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Allwinner A23 Analog Codec Device Tree Bindings
|
||||
|
||||
maintainers:
|
||||
- Chen-Yu Tsai <wens@csie.org>
|
||||
- Maxime Ripard <maxime.ripard@bootlin.com>
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
# FIXME: This is documented in the PRCM binding, but needs to be
|
||||
# migrated here at some point
|
||||
# - allwinner,sun8i-a23-codec-analog
|
||||
- allwinner,sun8i-h3-codec-analog
|
||||
- allwinner,sun8i-v3s-codec-analog
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
codec_analog: codec-analog@1f015c0 {
|
||||
compatible = "allwinner,sun8i-h3-codec-analog";
|
||||
reg = <0x01f015c0 0x4>;
|
||||
};
|
||||
|
||||
...
|
|
@ -1,8 +1,9 @@
|
|||
Audio Binding for Arndale boards
|
||||
|
||||
Required properties:
|
||||
- compatible : Can be the following,
|
||||
"samsung,arndale-rt5631"
|
||||
- compatible : Can be one of the following:
|
||||
"samsung,arndale-rt5631",
|
||||
"samsung,arndale-wm1811"
|
||||
|
||||
- samsung,audio-cpu: The phandle of the Samsung I2S controller
|
||||
- samsung,audio-codec: The phandle of the audio codec
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
fsl,mqs audio CODEC
|
||||
|
||||
Required properties:
|
||||
- compatible : Must contain one of "fsl,imx6sx-mqs", "fsl,codec-mqs"
|
||||
"fsl,imx8qm-mqs", "fsl,imx8qxp-mqs".
|
||||
- clocks : A list of phandles + clock-specifiers, one for each entry in
|
||||
clock-names
|
||||
- clock-names : "mclk" - must required.
|
||||
"core" - required if compatible is "fsl,imx8qm-mqs", it
|
||||
is for register access.
|
||||
- gpr : A phandle of General Purpose Registers in IOMUX Controller.
|
||||
Required if compatible is "fsl,imx6sx-mqs".
|
||||
|
||||
Required if compatible is "fsl,imx8qm-mqs":
|
||||
- power-domains: A phandle of PM domain provider node.
|
||||
- reg: Offset and length of the register set for the device.
|
||||
|
||||
Example:
|
||||
|
||||
mqs: mqs {
|
||||
compatible = "fsl,imx6sx-mqs";
|
||||
gpr = <&gpr>;
|
||||
clocks = <&clks IMX6SX_CLK_SAI1>;
|
||||
clock-names = "mclk";
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
mqs: mqs@59850000 {
|
||||
compatible = "fsl,imx8qm-mqs";
|
||||
reg = <0x59850000 0x10000>;
|
||||
clocks = <&clk IMX8QM_AUD_MQS_IPG>,
|
||||
<&clk IMX8QM_AUD_MQS_HMCLK>;
|
||||
clock-names = "core", "mclk";
|
||||
power-domains = <&pd_mqs0>;
|
||||
status = "disabled";
|
||||
};
|
|
@ -1,4 +1,4 @@
|
|||
* Audio codec controlled by ChromeOS EC
|
||||
Audio codec controlled by ChromeOS EC
|
||||
|
||||
Google's ChromeOS EC codec is a digital mic codec provided by the
|
||||
Embedded Controller (EC) and is controlled via a host-command interface.
|
||||
|
@ -9,10 +9,27 @@ Documentation/devicetree/bindings/mfd/cros-ec.txt).
|
|||
Required properties:
|
||||
- compatible: Must contain "google,cros-ec-codec"
|
||||
- #sound-dai-cells: Should be 1. The cell specifies number of DAIs.
|
||||
- max-dmic-gain: A number for maximum gain in dB on digital microphone.
|
||||
|
||||
Optional properties:
|
||||
- reg: Pysical base address and length of shared memory region from EC.
|
||||
It contains 3 unsigned 32-bit integer. The first 2 integers
|
||||
combine to become an unsigned 64-bit physical address. The last
|
||||
one integer is length of the shared memory.
|
||||
- memory-region: Shared memory region to EC. A "shared-dma-pool". See
|
||||
../reserved-memory/reserved-memory.txt for details.
|
||||
|
||||
Example:
|
||||
|
||||
{
|
||||
...
|
||||
|
||||
reserved_mem: reserved_mem {
|
||||
compatible = "shared-dma-pool";
|
||||
reg = <0 0x52800000 0 0x100000>;
|
||||
no-map;
|
||||
};
|
||||
}
|
||||
|
||||
cros-ec@0 {
|
||||
compatible = "google,cros-ec-spi";
|
||||
|
||||
|
@ -21,6 +38,7 @@ cros-ec@0 {
|
|||
cros_ec_codec: ec-codec {
|
||||
compatible = "google,cros-ec-codec";
|
||||
#sound-dai-cells = <1>;
|
||||
max-dmic-gain = <43>;
|
||||
reg = <0x0 0x10500000 0x80000>;
|
||||
memory-region = <&reserved_mem>;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -4,6 +4,10 @@ Required properties:
|
|||
- compatible = "mediatek,mt68183-audio";
|
||||
- reg: register location and size
|
||||
- interrupts: should contain AFE interrupt
|
||||
- resets: Must contain an entry for each entry in reset-names
|
||||
See ../reset/reset.txt for details.
|
||||
- reset-names: should have these reset names:
|
||||
"audiosys";
|
||||
- power-domains: should define the power domain
|
||||
- clocks: Must contain an entry for each entry in clock-names
|
||||
- clock-names: should have these clock names:
|
||||
|
@ -20,6 +24,8 @@ Example:
|
|||
compatible = "mediatek,mt8183-audio";
|
||||
reg = <0 0x11220000 0 0x1000>;
|
||||
interrupts = <GIC_SPI 161 IRQ_TYPE_LEVEL_LOW>;
|
||||
resets = <&watchdog MT8183_TOPRGU_AUDIO_SW_RST>;
|
||||
reset-names = "audiosys";
|
||||
power-domains = <&scpsys MT8183_POWER_DOMAIN_AUDIO>;
|
||||
clocks = <&infrasys CLK_INFRA_AUDIO>,
|
||||
<&infrasys CLK_INFRA_AUDIO_26M_BCLK>,
|
||||
|
|
|
@ -2,14 +2,19 @@ MT8183 with MT6358, TS3A227 and MAX98357 CODECS
|
|||
|
||||
Required properties:
|
||||
- compatible : "mediatek,mt8183_mt6358_ts3a227_max98357"
|
||||
- mediatek,headset-codec: the phandles of ts3a227 codecs
|
||||
- mediatek,platform: the phandle of MT8183 ASoC platform
|
||||
|
||||
Optional properties:
|
||||
- mediatek,headset-codec: the phandles of ts3a227 codecs
|
||||
- mediatek,ec-codec: the phandle of EC codecs.
|
||||
See google,cros-ec-codec.txt for more details.
|
||||
|
||||
Example:
|
||||
|
||||
sound {
|
||||
compatible = "mediatek,mt8183_mt6358_ts3a227_max98357";
|
||||
mediatek,headset-codec = <&ts3a227>;
|
||||
mediatek,ec-codec = <&ec_codec>;
|
||||
mediatek,platform = <&afe>;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
Renesas FSI
|
||||
|
||||
Required properties:
|
||||
- compatible : "renesas,fsi2-<soctype>",
|
||||
"renesas,sh_fsi2" or "renesas,sh_fsi" as
|
||||
fallback.
|
||||
Examples with soctypes are:
|
||||
- "renesas,fsi2-r8a7740" (R-Mobile A1)
|
||||
- "renesas,fsi2-sh73a0" (SH-Mobile AG5)
|
||||
- reg : Should contain the register physical address and length
|
||||
- interrupts : Should contain FSI interrupt
|
||||
|
||||
- fsia,spdif-connection : FSI is connected by S/PDIF
|
||||
- fsia,stream-mode-support : FSI supports 16bit stream mode.
|
||||
- fsia,use-internal-clock : FSI uses internal clock when master mode.
|
||||
|
||||
- fsib,spdif-connection : same as fsia
|
||||
- fsib,stream-mode-support : same as fsia
|
||||
- fsib,use-internal-clock : same as fsia
|
||||
|
||||
Example:
|
||||
|
||||
sh_fsi2: sh_fsi2@ec230000 {
|
||||
compatible = "renesas,sh_fsi2";
|
||||
reg = <0xec230000 0x400>;
|
||||
interrupts = <0 146 0x4>;
|
||||
|
||||
fsia,spdif-connection;
|
||||
fsia,stream-mode-support;
|
||||
fsia,use-internal-clock;
|
||||
};
|
|
@ -0,0 +1,76 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/sound/renesas,fsi.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Renesas FSI Sound Driver Device Tree Bindings
|
||||
|
||||
maintainers:
|
||||
- Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
|
||||
|
||||
properties:
|
||||
$nodename:
|
||||
pattern: "^sound@.*"
|
||||
|
||||
compatible:
|
||||
oneOf:
|
||||
# for FSI2 SoC
|
||||
- items:
|
||||
- enum:
|
||||
- renesas,fsi2-sh73a0
|
||||
- renesas,fsi2-r8a7740
|
||||
- enum:
|
||||
- renesas,sh_fsi2
|
||||
# for Generic
|
||||
- items:
|
||||
- enum:
|
||||
- renesas,sh_fsi
|
||||
- renesas,sh_fsi2
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
fsia,spdif-connection:
|
||||
$ref: /schemas/types.yaml#/definitions/flag
|
||||
description: FSI is connected by S/PDIF
|
||||
|
||||
fsia,stream-mode-support:
|
||||
$ref: /schemas/types.yaml#/definitions/flag
|
||||
description: FSI supports 16bit stream mode
|
||||
|
||||
fsia,use-internal-clock:
|
||||
$ref: /schemas/types.yaml#/definitions/flag
|
||||
description: FSI uses internal clock when master mode
|
||||
|
||||
fsib,spdif-connection:
|
||||
$ref: /schemas/types.yaml#/definitions/flag
|
||||
description: same as fsia
|
||||
|
||||
fsib,stream-mode-support:
|
||||
$ref: /schemas/types.yaml#/definitions/flag
|
||||
description: same as fsia
|
||||
|
||||
fsib,use-internal-clock:
|
||||
$ref: /schemas/types.yaml#/definitions/flag
|
||||
description: same as fsia
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- interrupts
|
||||
|
||||
examples:
|
||||
- |
|
||||
sh_fsi2: sound@ec230000 {
|
||||
compatible = "renesas,fsi2-r8a7740", "renesas,sh_fsi2";
|
||||
reg = <0xec230000 0x400>;
|
||||
interrupts = <0 146 0x4>;
|
||||
|
||||
fsia,spdif-connection;
|
||||
fsia,stream-mode-support;
|
||||
fsia,use-internal-clock;
|
||||
};
|
|
@ -268,6 +268,7 @@ Required properties:
|
|||
- "renesas,rcar_sound-r8a7745" (RZ/G1E)
|
||||
- "renesas,rcar_sound-r8a77470" (RZ/G1C)
|
||||
- "renesas,rcar_sound-r8a774a1" (RZ/G2M)
|
||||
- "renesas,rcar_sound-r8a774b1" (RZ/G2N)
|
||||
- "renesas,rcar_sound-r8a774c0" (RZ/G2E)
|
||||
- "renesas,rcar_sound-r8a7778" (R-Car M1A)
|
||||
- "renesas,rcar_sound-r8a7779" (R-Car H1)
|
||||
|
|
|
@ -5,11 +5,16 @@ Required properties:
|
|||
- rockchip,model: The user-visible name of this sound complex
|
||||
- rockchip,i2s-controller: The phandle of the Rockchip I2S controller that's
|
||||
connected to the CODEC
|
||||
- rockchip,audio-codec: The phandle of the MAX98090 audio codec
|
||||
- rockchip,headset-codec: The phandle of Ext chip for jack detection
|
||||
|
||||
Optional properties:
|
||||
- rockchip,audio-codec: The phandle of the MAX98090 audio codec.
|
||||
- rockchip,headset-codec: The phandle of Ext chip for jack detection. This is
|
||||
required if there is rockchip,audio-codec.
|
||||
- rockchip,hdmi-codec: The phandle of HDMI device for HDMI codec.
|
||||
|
||||
Example:
|
||||
|
||||
/* For max98090-only board. */
|
||||
sound {
|
||||
compatible = "rockchip,rockchip-audio-max98090";
|
||||
rockchip,model = "ROCKCHIP-I2S";
|
||||
|
@ -17,3 +22,21 @@ sound {
|
|||
rockchip,audio-codec = <&max98090>;
|
||||
rockchip,headset-codec = <&headsetcodec>;
|
||||
};
|
||||
|
||||
/* For HDMI-only board. */
|
||||
sound {
|
||||
compatible = "rockchip,rockchip-audio-max98090";
|
||||
rockchip,model = "ROCKCHIP-I2S";
|
||||
rockchip,i2s-controller = <&i2s>;
|
||||
rockchip,hdmi-codec = <&hdmi>;
|
||||
};
|
||||
|
||||
/* For max98090 plus HDMI board. */
|
||||
sound {
|
||||
compatible = "rockchip,rockchip-audio-max98090";
|
||||
rockchip,model = "ROCKCHIP-I2S";
|
||||
rockchip,i2s-controller = <&i2s>;
|
||||
rockchip,audio-codec = <&max98090>;
|
||||
rockchip,headset-codec = <&headsetcodec>;
|
||||
rockchip,hdmi-codec = <&hdmi>;
|
||||
};
|
||||
|
|
|
@ -20,6 +20,14 @@ Required properties:
|
|||
| 1 | 1 | 0x3b |
|
||||
-------------------------------------
|
||||
|
||||
Optional properties:
|
||||
|
||||
- realtek,temperature_calib
|
||||
u32. The temperature was measured while doing the calibration. Units: Celsius degree
|
||||
|
||||
- realtek,r0_calib
|
||||
u32. This is r0 calibration data which was measured in factory mode.
|
||||
|
||||
Pins on the device (for linking into audio routes) for RT1011:
|
||||
|
||||
* SPO
|
||||
|
@ -29,4 +37,6 @@ Example:
|
|||
rt1011: codec@38 {
|
||||
compatible = "realtek,rt1011";
|
||||
reg = <0x38>;
|
||||
realtek,temperature_calib = <25>;
|
||||
realtek,r0_calib = <0x224050>;
|
||||
};
|
||||
|
|
|
@ -27,6 +27,11 @@ Optional properties:
|
|||
|
||||
- realtek,ldo1-en-gpios : The GPIO that controls the CODEC's LDO1_EN pin.
|
||||
|
||||
- realtek,btndet-delay
|
||||
The debounce delay for push button.
|
||||
The delay time is realtek,btndet-delay value multiple of 8.192 ms.
|
||||
If absent, the default is 16.
|
||||
|
||||
Pins on the device (for linking into audio routes) for RT5682:
|
||||
|
||||
* DMIC L1
|
||||
|
@ -47,4 +52,5 @@ rt5682 {
|
|||
realtek,dmic1-data-pin = <1>;
|
||||
realtek,dmic1-clk-pin = <1>;
|
||||
realtek,jd-src = <1>;
|
||||
realtek,btndet-delay = <16>;
|
||||
};
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
Samsung Exynos Odroid XU3/XU4 audio complex with MAX98090 codec
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible - "hardkernel,odroid-xu3-audio" - for Odroid XU3 board,
|
||||
"hardkernel,odroid-xu4-audio" - for Odroid XU4 board (deprecated),
|
||||
"samsung,odroid-xu3-audio" - for Odroid XU3 board (deprecated),
|
||||
"samsung,odroid-xu4-audio" - for Odroid XU4 board (deprecated)
|
||||
- model - the user-visible name of this sound complex
|
||||
- clocks - should contain entries matching clock names in the clock-names
|
||||
property
|
||||
- samsung,audio-widgets - this property specifies off-codec audio elements
|
||||
like headphones or speakers, for details see widgets.txt
|
||||
- samsung,audio-routing - a list of the connections between audio
|
||||
components; each entry is a pair of strings, the first being the
|
||||
connection's sink, the second being the connection's source;
|
||||
valid names for sources and sinks are the MAX98090's pins (as
|
||||
documented in its binding), and the jacks on the board
|
||||
|
||||
For Odroid X2:
|
||||
"Headphone Jack", "Mic Jack", "DMIC"
|
||||
|
||||
For Odroid U3, XU3:
|
||||
"Headphone Jack", "Speakers"
|
||||
|
||||
For Odroid XU4:
|
||||
no entries
|
||||
|
||||
Required sub-nodes:
|
||||
|
||||
- 'cpu' subnode with a 'sound-dai' property containing the phandle of the I2S
|
||||
controller
|
||||
- 'codec' subnode with a 'sound-dai' property containing list of phandles
|
||||
to the CODEC nodes, first entry must be corresponding to the MAX98090
|
||||
CODEC and the second entry must be the phandle of the HDMI IP block node
|
||||
|
||||
Example:
|
||||
|
||||
sound {
|
||||
compatible = "hardkernel,odroid-xu3-audio";
|
||||
model = "Odroid-XU3";
|
||||
samsung,audio-routing =
|
||||
"Headphone Jack", "HPL",
|
||||
"Headphone Jack", "HPR",
|
||||
"IN1", "Mic Jack",
|
||||
"Mic Jack", "MICBIAS";
|
||||
|
||||
cpu {
|
||||
sound-dai = <&i2s0 0>;
|
||||
};
|
||||
codec {
|
||||
sound-dai = <&hdmi>, <&max98090>;
|
||||
};
|
||||
};
|
|
@ -0,0 +1,91 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/sound/samsung,odroid.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Samsung Exynos Odroid XU3/XU4 audio complex with MAX98090 codec
|
||||
|
||||
maintainers:
|
||||
- Krzysztof Kozlowski <krzk@kernel.org>
|
||||
- Sylwester Nawrocki <s.nawrocki@samsung.com>
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
oneOf:
|
||||
- const: hardkernel,odroid-xu3-audio
|
||||
|
||||
- const: hardkernel,odroid-xu4-audio
|
||||
deprecated: true
|
||||
|
||||
- const: samsung,odroid-xu3-audio
|
||||
deprecated: true
|
||||
|
||||
- const: samsung,odroid-xu4-audio
|
||||
deprecated: true
|
||||
|
||||
model:
|
||||
$ref: /schemas/types.yaml#/definitions/string
|
||||
description: The user-visible name of this sound complex.
|
||||
|
||||
cpu:
|
||||
type: object
|
||||
properties:
|
||||
sound-dai:
|
||||
$ref: /schemas/types.yaml#/definitions/phandle-array
|
||||
description: phandles to the I2S controllers
|
||||
|
||||
codec:
|
||||
type: object
|
||||
properties:
|
||||
sound-dai:
|
||||
$ref: /schemas/types.yaml#/definitions/phandle-array
|
||||
description: |
|
||||
List of phandles to the CODEC nodes,
|
||||
first entry must be corresponding to the MAX98090 CODEC and
|
||||
the second entry must be the phandle of the HDMI IP block node.
|
||||
|
||||
samsung,audio-routing:
|
||||
$ref: /schemas/types.yaml#/definitions/non-unique-string-array
|
||||
description: |
|
||||
List of the connections between audio
|
||||
components; each entry is a pair of strings, the first being the
|
||||
connection's sink, the second being the connection's source;
|
||||
valid names for sources and sinks are the MAX98090's pins (as
|
||||
documented in its binding), and the jacks on the board.
|
||||
For Odroid X2: "Headphone Jack", "Mic Jack", "DMIC"
|
||||
For Odroid U3, XU3: "Headphone Jack", "Speakers"
|
||||
For Odroid XU4: no entries
|
||||
|
||||
samsung,audio-widgets:
|
||||
$ref: /schemas/types.yaml#/definitions/non-unique-string-array
|
||||
description: |
|
||||
This property specifies off-codec audio elements
|
||||
like headphones or speakers, for details see widgets.txt
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- model
|
||||
- cpu
|
||||
- codec
|
||||
|
||||
examples:
|
||||
- |
|
||||
sound {
|
||||
compatible = "hardkernel,odroid-xu3-audio";
|
||||
model = "Odroid-XU3";
|
||||
samsung,audio-routing =
|
||||
"Headphone Jack", "HPL",
|
||||
"Headphone Jack", "HPR",
|
||||
"IN1", "Mic Jack",
|
||||
"Mic Jack", "MICBIAS";
|
||||
|
||||
cpu {
|
||||
sound-dai = <&i2s0 0>;
|
||||
};
|
||||
|
||||
codec {
|
||||
sound-dai = <&hdmi>, <&max98090>;
|
||||
};
|
||||
};
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
* Samsung I2S controller
|
||||
|
||||
Required SoC Specific Properties:
|
||||
|
||||
- compatible : should be one of the following.
|
||||
- samsung,s3c6410-i2s: for 8/16/24bit stereo I2S.
|
||||
- samsung,s5pv210-i2s: for 8/16/24bit multichannel(5.1) I2S with
|
||||
secondary fifo, s/w reset control and internal mux for root clk src.
|
||||
- samsung,exynos5420-i2s: for 8/16/24bit multichannel(5.1) I2S for
|
||||
playback, stereo channel capture, secondary fifo using internal
|
||||
or external dma, s/w reset control, internal mux for root clk src
|
||||
and 7.1 channel TDM support for playback. TDM (Time division multiplexing)
|
||||
is to allow transfer of multiple channel audio data on single data line.
|
||||
- samsung,exynos7-i2s: with all the available features of exynos5 i2s,
|
||||
exynos7 I2S has 7.1 channel TDM support for capture, secondary fifo
|
||||
with only external dma and more no.of root clk sampling frequencies.
|
||||
- samsung,exynos7-i2s1: I2S1 on previous samsung platforms supports
|
||||
stereo channels. exynos7 i2s1 upgraded to 5.1 multichannel with
|
||||
slightly modified bit offsets.
|
||||
|
||||
- reg: physical base address of the controller and length of memory mapped
|
||||
region.
|
||||
- dmas: list of DMA controller phandle and DMA request line ordered pairs.
|
||||
- dma-names: identifier string for each DMA request line in the dmas property.
|
||||
These strings correspond 1:1 with the ordered pairs in dmas.
|
||||
- clocks: Handle to iis clock and RCLK source clk.
|
||||
- clock-names:
|
||||
i2s0 uses some base clocks from CMU and some are from audio subsystem internal
|
||||
clock controller. The clock names for i2s0 should be "iis", "i2s_opclk0" and
|
||||
"i2s_opclk1" as shown in the example below.
|
||||
i2s1 and i2s2 uses clocks from CMU. The clock names for i2s1 and i2s2 should
|
||||
be "iis" and "i2s_opclk0".
|
||||
"iis" is the i2s bus clock and i2s_opclk0, i2s_opclk1 are sources of the root
|
||||
clk. i2s0 has internal mux to select the source of root clk and i2s1 and i2s2
|
||||
doesn't have any such mux.
|
||||
- #clock-cells: should be 1, this property must be present if the I2S device
|
||||
is a clock provider in terms of the common clock bindings, described in
|
||||
../clock/clock-bindings.txt.
|
||||
- clock-output-names (deprecated): from the common clock bindings, names of
|
||||
the CDCLK I2S output clocks, suggested values are "i2s_cdclk0", "i2s_cdclk1",
|
||||
"i2s_cdclk3" for the I2S0, I2S1, I2S2 devices respectively.
|
||||
|
||||
There are following clocks available at the I2S device nodes:
|
||||
CLK_I2S_CDCLK - the CDCLK (CODECLKO) gate clock,
|
||||
CLK_I2S_RCLK_PSR - the RCLK prescaler divider clock (corresponding to the
|
||||
IISPSR register),
|
||||
CLK_I2S_RCLK_SRC - the RCLKSRC mux clock (corresponding to RCLKSRC bit in
|
||||
IISMOD register).
|
||||
|
||||
Refer to the SoC datasheet for availability of the above clocks.
|
||||
The CLK_I2S_RCLK_PSR and CLK_I2S_RCLK_SRC clocks are usually only available
|
||||
in the IIS Multi Audio Interface.
|
||||
|
||||
Note: Old DTs may not have the #clock-cells property and then not use the I2S
|
||||
node as a clock supplier.
|
||||
|
||||
Optional SoC Specific Properties:
|
||||
|
||||
- samsung,idma-addr: Internal DMA register base address of the audio
|
||||
sub system(used in secondary sound source).
|
||||
- pinctrl-0: Should specify pin control groups used for this controller.
|
||||
- pinctrl-names: Should contain only one value - "default".
|
||||
- #sound-dai-cells: should be 1.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
i2s0: i2s@3830000 {
|
||||
compatible = "samsung,s5pv210-i2s";
|
||||
reg = <0x03830000 0x100>;
|
||||
dmas = <&pdma0 10
|
||||
&pdma0 9
|
||||
&pdma0 8>;
|
||||
dma-names = "tx", "rx", "tx-sec";
|
||||
clocks = <&clock_audss EXYNOS_I2S_BUS>,
|
||||
<&clock_audss EXYNOS_I2S_BUS>,
|
||||
<&clock_audss EXYNOS_SCLK_I2S>;
|
||||
clock-names = "iis", "i2s_opclk0", "i2s_opclk1";
|
||||
#clock-cells = <1>;
|
||||
samsung,idma-addr = <0x03000000>;
|
||||
pinctrl-names = "default";
|
||||
pinctrl-0 = <&i2s0_bus>;
|
||||
#sound-dai-cells = <1>;
|
||||
};
|
|
@ -0,0 +1,138 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/sound/samsung-i2s.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Samsung SoC I2S controller
|
||||
|
||||
maintainers:
|
||||
- Krzysztof Kozlowski <krzk@kernel.org>
|
||||
- Sylwester Nawrocki <s.nawrocki@samsung.com>
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
description: |
|
||||
samsung,s3c6410-i2s: for 8/16/24bit stereo I2S.
|
||||
|
||||
samsung,s5pv210-i2s: for 8/16/24bit multichannel (5.1) I2S with
|
||||
secondary FIFO, s/w reset control and internal mux for root clock
|
||||
source.
|
||||
|
||||
samsung,exynos5420-i2s: for 8/16/24bit multichannel (5.1) I2S for
|
||||
playback, stereo channel capture, secondary FIFO using internal
|
||||
or external DMA, s/w reset control, internal mux for root clock
|
||||
source and 7.1 channel TDM support for playback; TDM (Time division
|
||||
multiplexing) is to allow transfer of multiple channel audio data on
|
||||
single data line.
|
||||
|
||||
samsung,exynos7-i2s: with all the available features of Exynos5 I2S.
|
||||
Exynos7 I2S has 7.1 channel TDM support for capture, secondary FIFO
|
||||
with only external DMA and more number of root clock sampling
|
||||
frequencies.
|
||||
|
||||
samsung,exynos7-i2s1: I2S1 on previous samsung platforms supports
|
||||
stereo channels. Exynos7 I2S1 upgraded to 5.1 multichannel with
|
||||
slightly modified bit offsets.
|
||||
enum:
|
||||
- samsung,s3c6410-i2s
|
||||
- samsung,s5pv210-i2s
|
||||
- samsung,exynos5420-i2s
|
||||
- samsung,exynos7-i2s
|
||||
- samsung,exynos7-i2s1
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
dmas:
|
||||
minItems: 2
|
||||
maxItems: 3
|
||||
|
||||
dma-names:
|
||||
oneOf:
|
||||
- items:
|
||||
- const: tx
|
||||
- const: rx
|
||||
- items:
|
||||
- const: tx
|
||||
- const: rx
|
||||
- const: tx-sec
|
||||
|
||||
clocks:
|
||||
minItems: 1
|
||||
maxItems: 3
|
||||
|
||||
clock-names:
|
||||
oneOf:
|
||||
- items:
|
||||
- const: iis
|
||||
- items: # for I2S0
|
||||
- const: iis
|
||||
- const: i2s_opclk0
|
||||
- const: i2s_opclk1
|
||||
- items: # for I2S1 and I2S2
|
||||
- const: iis
|
||||
- const: i2s_opclk0
|
||||
description: |
|
||||
"iis" is the I2S bus clock and i2s_opclk0, i2s_opclk1 are sources
|
||||
of the root clock. I2S0 has internal mux to select the source
|
||||
of root clock and I2S1 and I2S2 doesn't have any such mux.
|
||||
|
||||
"#clock-cells":
|
||||
const: 1
|
||||
|
||||
clock-output-names:
|
||||
deprecated: true
|
||||
oneOf:
|
||||
- items: # for I2S0
|
||||
- const: i2s_cdclk0
|
||||
- items: # for I2S1
|
||||
- const: i2s_cdclk1
|
||||
- items: # for I2S2
|
||||
- const: i2s_cdclk2
|
||||
description: Names of the CDCLK I2S output clocks.
|
||||
|
||||
samsung,idma-addr:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
description: |
|
||||
Internal DMA register base address of the audio
|
||||
subsystem (used in secondary sound source).
|
||||
|
||||
pinctrl-0:
|
||||
description: Should specify pin control groups used for this controller.
|
||||
|
||||
pinctrl-names:
|
||||
const: default
|
||||
|
||||
"#sound-dai-cells":
|
||||
const: 1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- dmas
|
||||
- dma-names
|
||||
- clocks
|
||||
- clock-names
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/clock/exynos-audss-clk.h>
|
||||
|
||||
i2s0: i2s@3830000 {
|
||||
compatible = "samsung,s5pv210-i2s";
|
||||
reg = <0x03830000 0x100>;
|
||||
dmas = <&pdma0 10>,
|
||||
<&pdma0 9>,
|
||||
<&pdma0 8>;
|
||||
dma-names = "tx", "rx", "tx-sec";
|
||||
clocks = <&clock_audss EXYNOS_I2S_BUS>,
|
||||
<&clock_audss EXYNOS_I2S_BUS>,
|
||||
<&clock_audss EXYNOS_SCLK_I2S>;
|
||||
clock-names = "iis", "i2s_opclk0", "i2s_opclk1";
|
||||
#clock-cells = <1>;
|
||||
samsung,idma-addr = <0x03000000>;
|
||||
pinctrl-names = "default";
|
||||
pinctrl-0 = <&i2s0_bus>;
|
||||
#sound-dai-cells = <1>;
|
||||
};
|
|
@ -1,94 +0,0 @@
|
|||
* Allwinner A10 Codec
|
||||
|
||||
Required properties:
|
||||
- compatible: must be one of the following compatibles:
|
||||
- "allwinner,sun4i-a10-codec"
|
||||
- "allwinner,sun6i-a31-codec"
|
||||
- "allwinner,sun7i-a20-codec"
|
||||
- "allwinner,sun8i-a23-codec"
|
||||
- "allwinner,sun8i-h3-codec"
|
||||
- "allwinner,sun8i-v3s-codec"
|
||||
- reg: must contain the registers location and length
|
||||
- interrupts: must contain the codec interrupt
|
||||
- dmas: DMA channels for tx and rx dma. See the DMA client binding,
|
||||
Documentation/devicetree/bindings/dma/dma.txt
|
||||
- dma-names: should include "tx" and "rx".
|
||||
- clocks: a list of phandle + clock-specifer pairs, one for each entry
|
||||
in clock-names.
|
||||
- clock-names: should contain the following:
|
||||
- "apb": the parent APB clock for this controller
|
||||
- "codec": the parent module clock
|
||||
|
||||
Optional properties:
|
||||
- allwinner,pa-gpios: gpio to enable external amplifier
|
||||
|
||||
Required properties for the following compatibles:
|
||||
- "allwinner,sun6i-a31-codec"
|
||||
- "allwinner,sun8i-a23-codec"
|
||||
- "allwinner,sun8i-h3-codec"
|
||||
- "allwinner,sun8i-v3s-codec"
|
||||
- resets: phandle to the reset control for this device
|
||||
- allwinner,audio-routing: A list of the connections between audio components.
|
||||
Each entry is a pair of strings, the first being the
|
||||
connection's sink, the second being the connection's
|
||||
source. Valid names include:
|
||||
|
||||
Audio pins on the SoC:
|
||||
"HP"
|
||||
"HPCOM"
|
||||
"LINEIN" (not on sun8i-v3s)
|
||||
"LINEOUT" (not on sun8i-a23 or sun8i-v3s)
|
||||
"MIC1"
|
||||
"MIC2" (not on sun8i-v3s)
|
||||
"MIC3" (sun6i-a31 only)
|
||||
|
||||
Microphone biases from the SoC:
|
||||
"HBIAS"
|
||||
"MBIAS" (not on sun8i-v3s)
|
||||
|
||||
Board connectors:
|
||||
"Headphone"
|
||||
"Headset Mic"
|
||||
"Line In"
|
||||
"Line Out"
|
||||
"Mic"
|
||||
"Speaker"
|
||||
|
||||
Required properties for the following compatibles:
|
||||
- "allwinner,sun8i-a23-codec"
|
||||
- "allwinner,sun8i-h3-codec"
|
||||
- "allwinner,sun8i-v3s-codec"
|
||||
- allwinner,codec-analog-controls: A phandle to the codec analog controls
|
||||
block in the PRCM.
|
||||
|
||||
Example:
|
||||
codec: codec@1c22c00 {
|
||||
#sound-dai-cells = <0>;
|
||||
compatible = "allwinner,sun7i-a20-codec";
|
||||
reg = <0x01c22c00 0x40>;
|
||||
interrupts = <0 30 4>;
|
||||
clocks = <&apb0_gates 0>, <&codec_clk>;
|
||||
clock-names = "apb", "codec";
|
||||
dmas = <&dma 0 19>, <&dma 0 19>;
|
||||
dma-names = "rx", "tx";
|
||||
};
|
||||
|
||||
codec: codec@1c22c00 {
|
||||
#sound-dai-cells = <0>;
|
||||
compatible = "allwinner,sun6i-a31-codec";
|
||||
reg = <0x01c22c00 0x98>;
|
||||
interrupts = <GIC_SPI 29 IRQ_TYPE_LEVEL_HIGH>;
|
||||
clocks = <&ccu CLK_APB1_CODEC>, <&ccu CLK_CODEC>;
|
||||
clock-names = "apb", "codec";
|
||||
resets = <&ccu RST_APB1_CODEC>;
|
||||
dmas = <&dma 15>, <&dma 15>;
|
||||
dma-names = "rx", "tx";
|
||||
allwinner,audio-routing =
|
||||
"Headphone", "HP",
|
||||
"Speaker", "LINEOUT",
|
||||
"LINEIN", "Line In",
|
||||
"MIC1", "MBIAS",
|
||||
"MIC1", "Mic",
|
||||
"MIC2", "HBIAS",
|
||||
"MIC2", "Headset Mic";
|
||||
};
|
|
@ -1,17 +0,0 @@
|
|||
* Allwinner Codec Analog Controls
|
||||
|
||||
Required properties:
|
||||
- compatible: must be one of the following compatibles:
|
||||
- "allwinner,sun8i-a23-codec-analog"
|
||||
- "allwinner,sun8i-h3-codec-analog"
|
||||
- "allwinner,sun8i-v3s-codec-analog"
|
||||
|
||||
Required properties if not a sub-node of the PRCM node:
|
||||
- reg: must contain the registers location and length
|
||||
|
||||
Example:
|
||||
prcm: prcm@1f01400 {
|
||||
codec_analog: codec-analog {
|
||||
compatible = "allwinner,sun8i-a23-codec-analog";
|
||||
};
|
||||
};
|
|
@ -0,0 +1,34 @@
|
|||
Texas Instruments TAS2562 Smart PA
|
||||
|
||||
The TAS2562 is a mono, digital input Class-D audio amplifier optimized for
|
||||
efficiently driving high peak power into small loudspeakers.
|
||||
Integrated speaker voltage and current sense provides for
|
||||
real time monitoring of loudspeaker behavior.
|
||||
|
||||
Required properties:
|
||||
- #address-cells - Should be <1>.
|
||||
- #size-cells - Should be <0>.
|
||||
- compatible: - Should contain "ti,tas2562".
|
||||
- reg: - The i2c address. Should be 0x4c, 0x4d, 0x4e or 0x4f.
|
||||
- ti,imon-slot-no:- TDM TX current sense time slot.
|
||||
|
||||
Optional properties:
|
||||
- interrupt-parent: phandle to the interrupt controller which provides
|
||||
the interrupt.
|
||||
- interrupts: (GPIO) interrupt to which the chip is connected.
|
||||
- shut-down: GPIO used to control the state of the device.
|
||||
|
||||
Examples:
|
||||
tas2562@4c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
compatible = "ti,tas2562";
|
||||
reg = <0x4c>;
|
||||
|
||||
interrupt-parent = <&gpio1>;
|
||||
interrupts = <14>;
|
||||
|
||||
shut-down = <&gpio1 15 0>;
|
||||
ti,imon-slot-no = <0>;
|
||||
};
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
Texas Instruments TAS2770 Smart PA
|
||||
|
||||
The TAS2770 is a mono, digital input Class-D audio amplifier optimized for
|
||||
efficiently driving high peak power into small loudspeakers.
|
||||
Integrated speaker voltage and current sense provides for
|
||||
real time monitoring of loudspeaker behavior.
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible: - Should contain "ti,tas2770".
|
||||
- reg: - The i2c address. Should contain <0x4c>, <0x4d>,<0x4e>, or <0x4f>.
|
||||
- #address-cells - Should be <1>.
|
||||
- #size-cells - Should be <0>.
|
||||
- ti,asi-format: - Sets TDM RX capture edge. 0->Rising; 1->Falling.
|
||||
- ti,imon-slot-no:- TDM TX current sense time slot.
|
||||
- ti,vmon-slot-no:- TDM TX voltage sense time slot.
|
||||
|
||||
Optional properties:
|
||||
|
||||
- interrupt-parent: the phandle to the interrupt controller which provides
|
||||
the interrupt.
|
||||
- interrupts: interrupt specification for data-ready.
|
||||
|
||||
Examples:
|
||||
|
||||
tas2770@4c {
|
||||
compatible = "ti,tas2770";
|
||||
reg = <0x4c>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
interrupt-parent = <&msm_gpio>;
|
||||
interrupts = <97 0>;
|
||||
ti,asi-format = <0>;
|
||||
ti,imon-slot-no = <0>;
|
||||
ti,vmon-slot-no = <2>;
|
||||
};
|
||||
|
|
@ -25,6 +25,13 @@ Required properties:
|
|||
|
||||
For required properties on SPI/I2C, consult SPI/I2C device tree documentation
|
||||
|
||||
Optional properties:
|
||||
|
||||
- reset-gpios : Optional reset gpio line connected to RST pin of the codec.
|
||||
The RST line is low active:
|
||||
RST = low: device power-down
|
||||
RST = high: device is enabled
|
||||
|
||||
Examples:
|
||||
|
||||
i2c0: i2c0@0 {
|
||||
|
@ -34,6 +41,7 @@ i2c0: i2c0@0 {
|
|||
pcm3168a: audio-codec@44 {
|
||||
compatible = "ti,pcm3168a";
|
||||
reg = <0x44>;
|
||||
reset-gpios = <&gpio0 4 GPIO_ACTIVE_LOW>;
|
||||
clocks = <&clk_core CLK_AUDIO>;
|
||||
clock-names = "scki";
|
||||
VDD1-supply = <&supply3v3>;
|
||||
|
|
|
@ -29,6 +29,11 @@ Optional properties:
|
|||
3 or MICBIAS_AVDD - MICBIAS output is connected to AVDD
|
||||
If this node is not mentioned or if the value is unknown, then
|
||||
micbias is set to 2.0V.
|
||||
- ai31xx-ocmv - output common-mode voltage setting
|
||||
0 - 1.35V,
|
||||
1 - 1.5V,
|
||||
2 - 1.65V,
|
||||
3 - 1.8V
|
||||
|
||||
Deprecated properties:
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ properties: {}
|
|||
patternProperties:
|
||||
# Prefixes which are not vendors, but followed the pattern
|
||||
# DO NOT ADD NEW PROPERTIES TO THIS LIST
|
||||
"^(at25|devbus|dmacap|dsa|exynos|gpio-fan|gpio|gpmc|hdmi|i2c-gpio),.*": true
|
||||
"^(at25|devbus|dmacap|dsa|exynos|fsi[ab]|gpio-fan|gpio|gpmc|hdmi|i2c-gpio),.*": true
|
||||
"^(keypad|m25p|max8952|max8997|max8998|mpmc),.*": true
|
||||
"^(pinctrl-single|#pinctrl-single|PowerPC),.*": true
|
||||
"^(pl022|pxa-mmc|rcar_sound|rotary-encoder|s5m8767|sdhci),.*": true
|
||||
|
|
|
@ -805,6 +805,7 @@ destructor and PCI entries. Example code is shown first, below.
|
|||
return -EBUSY;
|
||||
}
|
||||
chip->irq = pci->irq;
|
||||
card->sync_irq = chip->irq;
|
||||
|
||||
/* (2) initialization of the chip hardware */
|
||||
.... /* (not implemented in this document) */
|
||||
|
@ -965,6 +966,15 @@ usually like the following:
|
|||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
After requesting the IRQ, you can passed it to ``card->sync_irq``
|
||||
field:
|
||||
::
|
||||
|
||||
card->irq = chip->irq;
|
||||
|
||||
This allows PCM core automatically performing
|
||||
:c:func:`synchronize_irq()` at the necessary timing like ``hw_free``.
|
||||
See the later section `sync_stop callback`_ for details.
|
||||
|
||||
Now let's write the corresponding destructor for the resources above.
|
||||
The role of destructor is simple: disable the hardware (if already
|
||||
|
@ -1270,21 +1280,23 @@ shows only the skeleton, how to build up the PCM interfaces.
|
|||
/* the hardware-specific codes will be here */
|
||||
....
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
/* hw_params callback */
|
||||
static int snd_mychip_pcm_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *hw_params)
|
||||
{
|
||||
return snd_pcm_lib_malloc_pages(substream,
|
||||
params_buffer_bytes(hw_params));
|
||||
/* the hardware-specific codes will be here */
|
||||
....
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* hw_free callback */
|
||||
static int snd_mychip_pcm_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
return snd_pcm_lib_free_pages(substream);
|
||||
/* the hardware-specific codes will be here */
|
||||
....
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* prepare callback */
|
||||
|
@ -1339,7 +1351,6 @@ shows only the skeleton, how to build up the PCM interfaces.
|
|||
static struct snd_pcm_ops snd_mychip_playback_ops = {
|
||||
.open = snd_mychip_playback_open,
|
||||
.close = snd_mychip_playback_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = snd_mychip_pcm_hw_params,
|
||||
.hw_free = snd_mychip_pcm_hw_free,
|
||||
.prepare = snd_mychip_pcm_prepare,
|
||||
|
@ -1351,7 +1362,6 @@ shows only the skeleton, how to build up the PCM interfaces.
|
|||
static struct snd_pcm_ops snd_mychip_capture_ops = {
|
||||
.open = snd_mychip_capture_open,
|
||||
.close = snd_mychip_capture_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = snd_mychip_pcm_hw_params,
|
||||
.hw_free = snd_mychip_pcm_hw_free,
|
||||
.prepare = snd_mychip_pcm_prepare,
|
||||
|
@ -1382,9 +1392,9 @@ shows only the skeleton, how to build up the PCM interfaces.
|
|||
&snd_mychip_capture_ops);
|
||||
/* pre-allocation of buffers */
|
||||
/* NOTE: this may fail */
|
||||
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
|
||||
snd_dma_pci_data(chip->pci),
|
||||
64*1024, 64*1024);
|
||||
snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
|
||||
&chip->pci->dev,
|
||||
64*1024, 64*1024);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -1454,7 +1464,6 @@ The operators are defined typically like this:
|
|||
static struct snd_pcm_ops snd_mychip_playback_ops = {
|
||||
.open = snd_mychip_pcm_open,
|
||||
.close = snd_mychip_pcm_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = snd_mychip_pcm_hw_params,
|
||||
.hw_free = snd_mychip_pcm_hw_free,
|
||||
.prepare = snd_mychip_pcm_prepare,
|
||||
|
@ -1465,13 +1474,14 @@ The operators are defined typically like this:
|
|||
All the callbacks are described in the Operators_ subsection.
|
||||
|
||||
After setting the operators, you probably will want to pre-allocate the
|
||||
buffer. For the pre-allocation, simply call the following:
|
||||
buffer and set up the managed allocation mode.
|
||||
For that, simply call the following:
|
||||
|
||||
::
|
||||
|
||||
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
|
||||
snd_dma_pci_data(chip->pci),
|
||||
64*1024, 64*1024);
|
||||
snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
|
||||
&chip->pci->dev,
|
||||
64*1024, 64*1024);
|
||||
|
||||
It will allocate a buffer up to 64kB as default. Buffer management
|
||||
details will be described in the later section `Buffer and Memory
|
||||
|
@ -1621,8 +1631,7 @@ For the operators (callbacks) of each sound driver, most of these
|
|||
records are supposed to be read-only. Only the PCM middle-layer changes
|
||||
/ updates them. The exceptions are the hardware description (hw) DMA
|
||||
buffer information and the private data. Besides, if you use the
|
||||
standard buffer allocation method via
|
||||
:c:func:`snd_pcm_lib_malloc_pages()`, you don't need to set the
|
||||
standard managed buffer allocation mode, you don't need to set the
|
||||
DMA buffer information by yourself.
|
||||
|
||||
In the sections below, important records are explained.
|
||||
|
@ -1776,8 +1785,8 @@ the physical address of the buffer. This field is specified only when
|
|||
the buffer is a linear buffer. ``dma_bytes`` holds the size of buffer
|
||||
in bytes. ``dma_private`` is used for the ALSA DMA allocator.
|
||||
|
||||
If you use a standard ALSA function,
|
||||
:c:func:`snd_pcm_lib_malloc_pages()`, for allocating the buffer,
|
||||
If you use either the managed buffer allocation mode or the standard
|
||||
API function :c:func:`snd_pcm_lib_malloc_pages()` for allocating the buffer,
|
||||
these fields are set by the ALSA middle layer, and you should *not*
|
||||
change them by yourself. You can read them but not write them. On the
|
||||
other hand, if you want to allocate the buffer by yourself, you'll
|
||||
|
@ -1911,7 +1920,10 @@ ioctl callback
|
|||
~~~~~~~~~~~~~~
|
||||
|
||||
This is used for any special call to pcm ioctls. But usually you can
|
||||
pass a generic ioctl callback, :c:func:`snd_pcm_lib_ioctl()`.
|
||||
leave it as NULL, then PCM core calls the generic ioctl callback
|
||||
function :c:func:`snd_pcm_lib_ioctl()`. If you need to deal with the
|
||||
unique setup of channel info or reset procedure, you can pass your own
|
||||
callback function here.
|
||||
|
||||
hw_params callback
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -1929,8 +1941,12 @@ Many hardware setups should be done in this callback, including the
|
|||
allocation of buffers.
|
||||
|
||||
Parameters to be initialized are retrieved by
|
||||
:c:func:`params_xxx()` macros. To allocate buffer, you can call a
|
||||
helper function,
|
||||
:c:func:`params_xxx()` macros.
|
||||
|
||||
When you set up the managed buffer allocation mode for the substream,
|
||||
a buffer is already allocated before this callback gets
|
||||
called. Alternatively, you can call a helper function below for
|
||||
allocating the buffer, too.
|
||||
|
||||
::
|
||||
|
||||
|
@ -1964,18 +1980,23 @@ hw_free callback
|
|||
static int snd_xxx_hw_free(struct snd_pcm_substream *substream);
|
||||
|
||||
This is called to release the resources allocated via
|
||||
``hw_params``. For example, releasing the buffer via
|
||||
:c:func:`snd_pcm_lib_malloc_pages()` is done by calling the
|
||||
following:
|
||||
|
||||
::
|
||||
|
||||
snd_pcm_lib_free_pages(substream);
|
||||
``hw_params``.
|
||||
|
||||
This function is always called before the close callback is called.
|
||||
Also, the callback may be called multiple times, too. Keep track
|
||||
whether the resource was already released.
|
||||
|
||||
When you have set up the managed buffer allocation mode for the PCM
|
||||
substream, the allocated PCM buffer will be automatically released
|
||||
after this callback gets called. Otherwise you'll have to release the
|
||||
buffer manually. Typically, when the buffer was allocated from the
|
||||
pre-allocated pool, you can use the standard API function
|
||||
:c:func:`snd_pcm_lib_malloc_pages()` like:
|
||||
|
||||
::
|
||||
|
||||
snd_pcm_lib_free_pages(substream);
|
||||
|
||||
prepare callback
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -2048,6 +2069,37 @@ flag set, and you cannot call functions which may sleep. The
|
|||
triggering the DMA. The other stuff should be initialized
|
||||
``hw_params`` and ``prepare`` callbacks properly beforehand.
|
||||
|
||||
sync_stop callback
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
static int snd_xxx_sync_stop(struct snd_pcm_substream *substream);
|
||||
|
||||
This callback is optional, and NULL can be passed. It's called after
|
||||
the PCM core stops the stream and changes the stream state
|
||||
``prepare``, ``hw_params`` or ``hw_free``.
|
||||
Since the IRQ handler might be still pending, we need to wait until
|
||||
the pending task finishes before moving to the next step; otherwise it
|
||||
might lead to a crash due to resource conflicts or access to the freed
|
||||
resources. A typical behavior is to call a synchronization function
|
||||
like :c:func:`synchronize_irq()` here.
|
||||
|
||||
For majority of drivers that need only a call of
|
||||
:c:func:`synchronize_irq()`, there is a simpler setup, too.
|
||||
While keeping NULL to ``sync_stop`` PCM callback, the driver can set
|
||||
``card->sync_irq`` field to store the valid interrupt number after
|
||||
requesting an IRQ, instead. Then PCM core will look call
|
||||
:c:func:`synchronize_irq()` with the given IRQ appropriately.
|
||||
|
||||
If the IRQ handler is released at the card destructor, you don't need
|
||||
to clear ``card->sync_irq``, as the card itself is being released.
|
||||
So, usually you'll need to add just a single line for assigning
|
||||
``card->sync_irq`` in the driver code unless the driver re-acquires
|
||||
the IRQ. When the driver frees and re-acquires the IRQ dynamically
|
||||
(e.g. for suspend/resume), it needs to clear and re-set
|
||||
``card->sync_irq`` again appropriately.
|
||||
|
||||
pointer callback
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -2095,10 +2147,12 @@ This callback is atomic as default.
|
|||
page callback
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
This callback is optional too. This callback is used mainly for
|
||||
non-contiguous buffers. The mmap calls this callback to get the page
|
||||
address. Some examples will be explained in the later section `Buffer
|
||||
and Memory Management`_, too.
|
||||
This callback is optional too. The mmap calls this callback to get the
|
||||
page fault address.
|
||||
|
||||
Since the recent changes, you need no special callback any longer for
|
||||
the standard SG-buffer or vmalloc-buffer. Hence this callback should
|
||||
be rarely used.
|
||||
|
||||
mmap calllback
|
||||
~~~~~~~~~~~~~~
|
||||
|
@ -3512,7 +3566,7 @@ bus).
|
|||
::
|
||||
|
||||
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
|
||||
snd_dma_pci_data(pci), size, max);
|
||||
&pci->dev, size, max);
|
||||
|
||||
where ``size`` is the byte size to be pre-allocated and the ``max`` is
|
||||
the maximum size to be changed via the ``prealloc`` proc file. The
|
||||
|
@ -3523,12 +3577,14 @@ The second argument (type) and the third argument (device pointer) are
|
|||
dependent on the bus. For normal devices, pass the device pointer
|
||||
(typically identical as ``card->dev``) to the third argument with
|
||||
``SNDRV_DMA_TYPE_DEV`` type. For the continuous buffer unrelated to the
|
||||
bus can be pre-allocated with ``SNDRV_DMA_TYPE_CONTINUOUS`` type and the
|
||||
``snd_dma_continuous_data(GFP_KERNEL)`` device pointer, where
|
||||
``GFP_KERNEL`` is the kernel allocation flag to use. For the
|
||||
scatter-gather buffers, use ``SNDRV_DMA_TYPE_DEV_SG`` with the device
|
||||
pointer (see the `Non-Contiguous Buffers`_
|
||||
section).
|
||||
bus can be pre-allocated with ``SNDRV_DMA_TYPE_CONTINUOUS`` type.
|
||||
You can pass NULL to the device pointer in that case, which is the
|
||||
default mode implying to allocate with ``GFP_KRENEL`` flag.
|
||||
If you need a different GFP flag, you can pass it by encoding the flag
|
||||
into the device pointer via a special macro
|
||||
:c:func:`snd_dma_continuous_data()`.
|
||||
For the scatter-gather buffers, use ``SNDRV_DMA_TYPE_DEV_SG`` with the
|
||||
device pointer (see the `Non-Contiguous Buffers`_ section).
|
||||
|
||||
Once the buffer is pre-allocated, you can use the allocator in the
|
||||
``hw_params`` callback:
|
||||
|
@ -3539,6 +3595,25 @@ Once the buffer is pre-allocated, you can use the allocator in the
|
|||
|
||||
Note that you have to pre-allocate to use this function.
|
||||
|
||||
Most of drivers use, though, rather the newly introduced "managed
|
||||
buffer allocation mode" instead of the manual allocation or release.
|
||||
This is done by calling :c:func:`snd_pcm_set_managed_buffer_all()`
|
||||
instead of :c:func:`snd_pcm_lib_preallocate_pages_for_all()`.
|
||||
|
||||
::
|
||||
|
||||
snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
|
||||
&pci->dev, size, max);
|
||||
|
||||
where passed arguments are identical in both functions.
|
||||
The difference in the managed mode is that PCM core will call
|
||||
:c:func:`snd_pcm_lib_malloc_pages()` internally already before calling
|
||||
the PCM ``hw_params`` callback, and call :c:func:`snd_pcm_lib_free_pages()`
|
||||
after the PCM ``hw_free`` callback automatically. So the driver
|
||||
doesn't have to call these functions explicitly in its callback any
|
||||
longer. This made many driver code having NULL ``hw_params`` and
|
||||
``hw_free`` entries.
|
||||
|
||||
External Hardware Buffers
|
||||
-------------------------
|
||||
|
||||
|
@ -3693,20 +3768,26 @@ provides an interface for handling SG-buffers. The API is provided in
|
|||
``<sound/pcm.h>``.
|
||||
|
||||
For creating the SG-buffer handler, call
|
||||
:c:func:`snd_pcm_lib_preallocate_pages()` or
|
||||
:c:func:`snd_pcm_lib_preallocate_pages_for_all()` with
|
||||
:c:func:`snd_pcm_set_managed_buffer()` or
|
||||
:c:func:`snd_pcm_set_managed_buffer_all()` with
|
||||
``SNDRV_DMA_TYPE_DEV_SG`` in the PCM constructor like other PCI
|
||||
pre-allocator. You need to pass ``snd_dma_pci_data(pci)``, where pci is
|
||||
pre-allocator. You need to pass ``&pci->dev``, where pci is
|
||||
the :c:type:`struct pci_dev <pci_dev>` pointer of the chip as
|
||||
well. The ``struct snd_sg_buf`` instance is created as
|
||||
``substream->dma_private``. You can cast the pointer like:
|
||||
well.
|
||||
|
||||
::
|
||||
|
||||
snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV_SG,
|
||||
&pci->dev, size, max);
|
||||
|
||||
The ``struct snd_sg_buf`` instance is created as
|
||||
``substream->dma_private`` in turn. You can cast the pointer like:
|
||||
|
||||
::
|
||||
|
||||
struct snd_sg_buf *sgbuf = (struct snd_sg_buf *)substream->dma_private;
|
||||
|
||||
Then call :c:func:`snd_pcm_lib_malloc_pages()` in the ``hw_params``
|
||||
callback as well as in the case of normal PCI buffer. The SG-buffer
|
||||
Then in :c:func:`snd_pcm_lib_malloc_pages()` call, the common SG-buffer
|
||||
handler will allocate the non-contiguous kernel pages of the given size
|
||||
and map them onto the virtually contiguous memory. The virtual pointer
|
||||
is addressed in runtime->dma_area. The physical address
|
||||
|
@ -3715,41 +3796,40 @@ physically non-contiguous. The physical address table is set up in
|
|||
``sgbuf->table``. You can get the physical address at a certain offset
|
||||
via :c:func:`snd_pcm_sgbuf_get_addr()`.
|
||||
|
||||
When a SG-handler is used, you need to set
|
||||
:c:func:`snd_pcm_sgbuf_ops_page()` as the ``page`` callback. (See
|
||||
`page callback`_ section.)
|
||||
|
||||
To release the data, call :c:func:`snd_pcm_lib_free_pages()` in
|
||||
the ``hw_free`` callback as usual.
|
||||
If you need to release the SG-buffer data explicitly, call the
|
||||
standard API function :c:func:`snd_pcm_lib_free_pages()` as usual.
|
||||
|
||||
Vmalloc'ed Buffers
|
||||
------------------
|
||||
|
||||
It's possible to use a buffer allocated via :c:func:`vmalloc()`, for
|
||||
example, for an intermediate buffer. Since the allocated pages are not
|
||||
contiguous, you need to set the ``page`` callback to obtain the physical
|
||||
address at every offset.
|
||||
|
||||
The easiest way to achieve it would be to use
|
||||
:c:func:`snd_pcm_lib_alloc_vmalloc_buffer()` for allocating the buffer
|
||||
via :c:func:`vmalloc()`, and set :c:func:`snd_pcm_sgbuf_ops_page()` to
|
||||
the ``page`` callback. At release, you need to call
|
||||
:c:func:`snd_pcm_lib_free_vmalloc_buffer()`.
|
||||
|
||||
If you want to implementation the ``page`` manually, it would be like
|
||||
this:
|
||||
example, for an intermediate buffer. In the recent version of kernel,
|
||||
you can simply allocate it via standard
|
||||
:c:func:`snd_pcm_lib_malloc_pages()` and co after setting up the
|
||||
buffer preallocation with ``SNDRV_DMA_TYPE_VMALLOC`` type.
|
||||
|
||||
::
|
||||
|
||||
#include <linux/vmalloc.h>
|
||||
snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC,
|
||||
NULL, 0, 0);
|
||||
|
||||
/* get the physical page pointer on the given offset */
|
||||
static struct page *mychip_page(struct snd_pcm_substream *substream,
|
||||
unsigned long offset)
|
||||
{
|
||||
void *pageptr = substream->runtime->dma_area + offset;
|
||||
return vmalloc_to_page(pageptr);
|
||||
}
|
||||
The NULL is passed to the device pointer argument, which indicates
|
||||
that the default pages (GFP_KERNEL and GFP_HIGHMEM) will be
|
||||
allocated.
|
||||
|
||||
Also, note that zero is passed to both the size and the max size
|
||||
arguments here. Since each vmalloc call should succeed at any time,
|
||||
we don't need to pre-allocate the buffers like other continuous
|
||||
pages.
|
||||
|
||||
If you need the 32bit DMA allocation, pass the device pointer encoded
|
||||
by :c:func:`snd_dma_continuous_data()` with ``GFP_KERNEL|__GFP_DMA32``
|
||||
argument.
|
||||
|
||||
::
|
||||
|
||||
snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC,
|
||||
snd_dma_continuous_data(GFP_KERNEL | __GFP_DMA32), 0, 0);
|
||||
|
||||
Proc Interface
|
||||
==============
|
||||
|
|
|
@ -1002,6 +1002,7 @@ F: drivers/media/i2c/adv7842*
|
|||
|
||||
ANALOG DEVICES INC ASOC CODEC DRIVERS
|
||||
M: Lars-Peter Clausen <lars@metafoo.de>
|
||||
M: Nuno Sá <nuno.sa@analog.com>
|
||||
L: alsa-devel@alsa-project.org (moderated for non-subscribers)
|
||||
W: http://wiki.analog.com/
|
||||
W: http://ez.analog.com/community/linux-device-drivers
|
||||
|
|
|
@ -151,11 +151,22 @@ static int dw_hdmi_i2s_get_dai_id(struct snd_soc_component *component,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int dw_hdmi_i2s_hook_plugged_cb(struct device *dev, void *data,
|
||||
hdmi_codec_plugged_cb fn,
|
||||
struct device *codec_dev)
|
||||
{
|
||||
struct dw_hdmi_i2s_audio_data *audio = data;
|
||||
struct dw_hdmi *hdmi = audio->hdmi;
|
||||
|
||||
return dw_hdmi_set_plugged_cb(hdmi, fn, codec_dev);
|
||||
}
|
||||
|
||||
static struct hdmi_codec_ops dw_hdmi_i2s_ops = {
|
||||
.hw_params = dw_hdmi_i2s_hw_params,
|
||||
.audio_shutdown = dw_hdmi_i2s_audio_shutdown,
|
||||
.get_eld = dw_hdmi_i2s_get_eld,
|
||||
.get_dai_id = dw_hdmi_i2s_get_dai_id,
|
||||
.hook_plugged_cb = dw_hdmi_i2s_hook_plugged_cb,
|
||||
};
|
||||
|
||||
static int snd_dw_hdmi_probe(struct platform_device *pdev)
|
||||
|
|
|
@ -191,6 +191,10 @@ struct dw_hdmi {
|
|||
|
||||
struct mutex cec_notifier_mutex;
|
||||
struct cec_notifier *cec_notifier;
|
||||
|
||||
hdmi_codec_plugged_cb plugged_cb;
|
||||
struct device *codec_dev;
|
||||
enum drm_connector_status last_connector_result;
|
||||
};
|
||||
|
||||
#define HDMI_IH_PHY_STAT0_RX_SENSE \
|
||||
|
@ -215,6 +219,28 @@ static inline u8 hdmi_readb(struct dw_hdmi *hdmi, int offset)
|
|||
return val;
|
||||
}
|
||||
|
||||
static void handle_plugged_change(struct dw_hdmi *hdmi, bool plugged)
|
||||
{
|
||||
if (hdmi->plugged_cb && hdmi->codec_dev)
|
||||
hdmi->plugged_cb(hdmi->codec_dev, plugged);
|
||||
}
|
||||
|
||||
int dw_hdmi_set_plugged_cb(struct dw_hdmi *hdmi, hdmi_codec_plugged_cb fn,
|
||||
struct device *codec_dev)
|
||||
{
|
||||
bool plugged;
|
||||
|
||||
mutex_lock(&hdmi->mutex);
|
||||
hdmi->plugged_cb = fn;
|
||||
hdmi->codec_dev = codec_dev;
|
||||
plugged = hdmi->last_connector_result == connector_status_connected;
|
||||
handle_plugged_change(hdmi, plugged);
|
||||
mutex_unlock(&hdmi->mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dw_hdmi_set_plugged_cb);
|
||||
|
||||
static void hdmi_modb(struct dw_hdmi *hdmi, u8 data, u8 mask, unsigned reg)
|
||||
{
|
||||
regmap_update_bits(hdmi->regm, reg << hdmi->reg_shift, mask, data);
|
||||
|
@ -2161,6 +2187,7 @@ dw_hdmi_connector_detect(struct drm_connector *connector, bool force)
|
|||
{
|
||||
struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi,
|
||||
connector);
|
||||
enum drm_connector_status result;
|
||||
|
||||
mutex_lock(&hdmi->mutex);
|
||||
hdmi->force = DRM_FORCE_UNSPECIFIED;
|
||||
|
@ -2168,7 +2195,18 @@ dw_hdmi_connector_detect(struct drm_connector *connector, bool force)
|
|||
dw_hdmi_update_phy_mask(hdmi);
|
||||
mutex_unlock(&hdmi->mutex);
|
||||
|
||||
return hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data);
|
||||
result = hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data);
|
||||
|
||||
mutex_lock(&hdmi->mutex);
|
||||
if (result != hdmi->last_connector_result) {
|
||||
dev_dbg(hdmi->dev, "read_hpd result: %d", result);
|
||||
handle_plugged_change(hdmi,
|
||||
result == connector_status_connected);
|
||||
hdmi->last_connector_result = result;
|
||||
}
|
||||
mutex_unlock(&hdmi->mutex);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static int dw_hdmi_connector_get_modes(struct drm_connector *connector)
|
||||
|
@ -2619,6 +2657,7 @@ __dw_hdmi_probe(struct platform_device *pdev,
|
|||
hdmi->rxsense = true;
|
||||
hdmi->phy_mask = (u8)~(HDMI_PHY_HPD | HDMI_PHY_RX_SENSE);
|
||||
hdmi->mc_clkdis = 0x7f;
|
||||
hdmi->last_connector_result = connector_status_disconnected;
|
||||
|
||||
mutex_init(&hdmi->mutex);
|
||||
mutex_init(&hdmi->audio_mutex);
|
||||
|
|
|
@ -353,7 +353,7 @@ static int solo_snd_pcm_init(struct solo_dev *solo_dev)
|
|||
|
||||
snd_pcm_lib_preallocate_pages_for_all(pcm,
|
||||
SNDRV_DMA_TYPE_CONTINUOUS,
|
||||
snd_dma_continuous_data(GFP_KERNEL),
|
||||
NULL,
|
||||
G723_PERIOD_BYTES * PERIODS,
|
||||
G723_PERIOD_BYTES * PERIODS);
|
||||
|
||||
|
|
|
@ -300,7 +300,7 @@ static int tw686x_snd_pcm_init(struct tw686x_dev *dev)
|
|||
|
||||
snd_pcm_lib_preallocate_pages_for_all(pcm,
|
||||
SNDRV_DMA_TYPE_DEV,
|
||||
snd_dma_pci_data(dev->pci_dev),
|
||||
&dev->pci_dev->dev,
|
||||
TW686X_AUDIO_PAGE_MAX * AUDIO_DMA_SIZE_MAX,
|
||||
TW686X_AUDIO_PAGE_MAX * AUDIO_DMA_SIZE_MAX);
|
||||
return 0;
|
||||
|
|
|
@ -378,8 +378,7 @@ int usbtv_audio_init(struct usbtv *usbtv)
|
|||
|
||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_usbtv_pcm_ops);
|
||||
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
|
||||
snd_dma_continuous_data(GFP_KERNEL), USBTV_AUDIO_BUFFER,
|
||||
USBTV_AUDIO_BUFFER);
|
||||
NULL, USBTV_AUDIO_BUFFER, USBTV_AUDIO_BUFFER);
|
||||
|
||||
rv = snd_card_register(card);
|
||||
if (rv)
|
||||
|
|
|
@ -5854,6 +5854,24 @@ int pci_set_vga_state(struct pci_dev *dev, bool decode,
|
|||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ACPI
|
||||
bool pci_pr3_present(struct pci_dev *pdev)
|
||||
{
|
||||
struct acpi_device *adev;
|
||||
|
||||
if (acpi_disabled)
|
||||
return false;
|
||||
|
||||
adev = ACPI_COMPANION(&pdev->dev);
|
||||
if (!adev)
|
||||
return false;
|
||||
|
||||
return adev->power.flags.power_resources &&
|
||||
acpi_has_method(adev->handle, "_PR3");
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_pr3_present);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* pci_add_dma_alias - Add a DMA devfn alias for a device
|
||||
* @dev: the PCI device for which alias is added
|
||||
|
|
|
@ -98,7 +98,10 @@
|
|||
TRACE_SYMBOL(EC_CMD_SB_READ_BLOCK), \
|
||||
TRACE_SYMBOL(EC_CMD_SB_WRITE_BLOCK), \
|
||||
TRACE_SYMBOL(EC_CMD_BATTERY_VENDOR_PARAM), \
|
||||
TRACE_SYMBOL(EC_CMD_CODEC_I2S), \
|
||||
TRACE_SYMBOL(EC_CMD_EC_CODEC), \
|
||||
TRACE_SYMBOL(EC_CMD_EC_CODEC_DMIC), \
|
||||
TRACE_SYMBOL(EC_CMD_EC_CODEC_I2S_RX), \
|
||||
TRACE_SYMBOL(EC_CMD_EC_CODEC_WOV), \
|
||||
TRACE_SYMBOL(EC_CMD_REBOOT_EC), \
|
||||
TRACE_SYMBOL(EC_CMD_GET_PANIC_INFO), \
|
||||
TRACE_SYMBOL(EC_CMD_ACPI_READ), \
|
||||
|
|
|
@ -344,8 +344,7 @@ static int pcm_hw_params(struct snd_pcm_substream *substream,
|
|||
pr_err("Requested number of channels not supported.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
return snd_pcm_lib_alloc_vmalloc_buffer(substream,
|
||||
params_buffer_bytes(hw_params));
|
||||
return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -359,7 +358,7 @@ static int pcm_hw_params(struct snd_pcm_substream *substream,
|
|||
*/
|
||||
static int pcm_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
return snd_pcm_lib_free_vmalloc_buffer(substream);
|
||||
return snd_pcm_lib_free_pages(substream);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -469,7 +468,6 @@ static const struct snd_pcm_ops pcm_ops = {
|
|||
.prepare = pcm_prepare,
|
||||
.trigger = pcm_trigger,
|
||||
.pointer = pcm_pointer,
|
||||
.page = snd_pcm_lib_get_vmalloc_page,
|
||||
};
|
||||
|
||||
static int split_arg_list(char *buf, u16 *ch_num, char **sample_res)
|
||||
|
@ -663,6 +661,8 @@ skip_adpt_alloc:
|
|||
pcm->private_data = channel;
|
||||
strscpy(pcm->name, device_name, sizeof(pcm->name));
|
||||
snd_pcm_set_ops(pcm, direction, &pcm_ops);
|
||||
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_VMALLOC,
|
||||
NULL, 0, 0);
|
||||
|
||||
return 0;
|
||||
|
||||
|
|
|
@ -585,7 +585,7 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
|
|||
sprintf(card->longname, "%s %i", card_name, card->dev->id);
|
||||
|
||||
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
|
||||
snd_dma_continuous_data(GFP_KERNEL), 0, BUFF_SIZE_MAX);
|
||||
NULL, 0, BUFF_SIZE_MAX);
|
||||
|
||||
err = snd_card_register(card);
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
#ifndef __DW_HDMI__
|
||||
#define __DW_HDMI__
|
||||
|
||||
#include <sound/hdmi-codec.h>
|
||||
|
||||
struct drm_connector;
|
||||
struct drm_display_mode;
|
||||
struct drm_encoder;
|
||||
|
@ -154,6 +156,8 @@ void dw_hdmi_resume(struct dw_hdmi *hdmi);
|
|||
|
||||
void dw_hdmi_setup_rx_sense(struct dw_hdmi *hdmi, bool hpd, bool rx_sense);
|
||||
|
||||
int dw_hdmi_set_plugged_cb(struct dw_hdmi *hdmi, hdmi_codec_plugged_cb fn,
|
||||
struct device *codec_dev);
|
||||
void dw_hdmi_set_sample_rate(struct dw_hdmi *hdmi, unsigned int rate);
|
||||
void dw_hdmi_set_channel_count(struct dw_hdmi *hdmi, unsigned int cnt);
|
||||
void dw_hdmi_set_channel_allocation(struct dw_hdmi *hdmi, unsigned int ca);
|
||||
|
|
|
@ -2,8 +2,14 @@
|
|||
#ifndef _DT_BINDINGS_SAMSUNG_I2S_H
|
||||
#define _DT_BINDINGS_SAMSUNG_I2S_H
|
||||
|
||||
#define CLK_I2S_CDCLK 0
|
||||
#define CLK_I2S_RCLK_SRC 1
|
||||
#define CLK_I2S_RCLK_PSR 2
|
||||
#define CLK_I2S_CDCLK 0 /* the CDCLK (CODECLKO) gate clock */
|
||||
|
||||
#define CLK_I2S_RCLK_SRC 1 /* the RCLKSRC mux clock (corresponding to
|
||||
* RCLKSRC bit in IISMOD register)
|
||||
*/
|
||||
|
||||
#define CLK_I2S_RCLK_PSR 2 /* the RCLK prescaler divider clock
|
||||
* (corresponding to the IISPSR register)
|
||||
*/
|
||||
|
||||
#endif /* _DT_BINDINGS_SAMSUNG_I2S_H */
|
||||
|
|
|
@ -2311,9 +2311,11 @@ struct irq_domain *pci_host_bridge_acpi_msi_domain(struct pci_bus *bus);
|
|||
|
||||
void
|
||||
pci_msi_register_fwnode_provider(struct fwnode_handle *(*fn)(struct device *));
|
||||
bool pci_pr3_present(struct pci_dev *pdev);
|
||||
#else
|
||||
static inline struct irq_domain *
|
||||
pci_host_bridge_acpi_msi_domain(struct pci_bus *bus) { return NULL; }
|
||||
static inline bool pci_pr3_present(struct pci_dev *pdev) { return false; }
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_EEH
|
||||
|
|
|
@ -556,6 +556,9 @@ enum host_event_code {
|
|||
/* Keyboard recovery combo with hardware reinitialization */
|
||||
EC_HOST_EVENT_KEYBOARD_RECOVERY_HW_REINIT = 30,
|
||||
|
||||
/* WoV */
|
||||
EC_HOST_EVENT_WOV = 31,
|
||||
|
||||
/*
|
||||
* The high bit of the event mask is not used as a host event code. If
|
||||
* it reads back as set, then the entire event mask should be
|
||||
|
@ -1277,8 +1280,6 @@ enum ec_feature_code {
|
|||
* MOTIONSENSE_CMD_TABLET_MODE_LID_ANGLE.
|
||||
*/
|
||||
EC_FEATURE_REFINED_TABLET_MODE_HYSTERESIS = 37,
|
||||
/* EC supports audio codec. */
|
||||
EC_FEATURE_AUDIO_CODEC = 38,
|
||||
/* The MCU is a System Companion Processor (SCP). */
|
||||
EC_FEATURE_SCP = 39,
|
||||
/* The MCU is an Integrated Sensor Hub */
|
||||
|
@ -4468,92 +4469,246 @@ enum mkbp_cec_event {
|
|||
|
||||
/*****************************************************************************/
|
||||
|
||||
/* Commands for I2S recording on audio codec. */
|
||||
/* Commands for audio codec. */
|
||||
#define EC_CMD_EC_CODEC 0x00BC
|
||||
|
||||
#define EC_CMD_CODEC_I2S 0x00BC
|
||||
#define EC_WOV_I2S_SAMPLE_RATE 48000
|
||||
|
||||
enum ec_codec_i2s_subcmd {
|
||||
EC_CODEC_SET_SAMPLE_DEPTH = 0x0,
|
||||
EC_CODEC_SET_GAIN = 0x1,
|
||||
EC_CODEC_GET_GAIN = 0x2,
|
||||
EC_CODEC_I2S_ENABLE = 0x3,
|
||||
EC_CODEC_I2S_SET_CONFIG = 0x4,
|
||||
EC_CODEC_I2S_SET_TDM_CONFIG = 0x5,
|
||||
EC_CODEC_I2S_SET_BCLK = 0x6,
|
||||
EC_CODEC_I2S_SUBCMD_COUNT = 0x7,
|
||||
enum ec_codec_subcmd {
|
||||
EC_CODEC_GET_CAPABILITIES = 0x0,
|
||||
EC_CODEC_GET_SHM_ADDR = 0x1,
|
||||
EC_CODEC_SET_SHM_ADDR = 0x2,
|
||||
EC_CODEC_SUBCMD_COUNT,
|
||||
};
|
||||
|
||||
enum ec_sample_depth_value {
|
||||
EC_CODEC_SAMPLE_DEPTH_16 = 0,
|
||||
EC_CODEC_SAMPLE_DEPTH_24 = 1,
|
||||
enum ec_codec_cap {
|
||||
EC_CODEC_CAP_WOV_AUDIO_SHM = 0,
|
||||
EC_CODEC_CAP_WOV_LANG_SHM = 1,
|
||||
EC_CODEC_CAP_LAST = 32,
|
||||
};
|
||||
|
||||
enum ec_i2s_config {
|
||||
EC_DAI_FMT_I2S = 0,
|
||||
EC_DAI_FMT_RIGHT_J = 1,
|
||||
EC_DAI_FMT_LEFT_J = 2,
|
||||
EC_DAI_FMT_PCM_A = 3,
|
||||
EC_DAI_FMT_PCM_B = 4,
|
||||
EC_DAI_FMT_PCM_TDM = 5,
|
||||
enum ec_codec_shm_id {
|
||||
EC_CODEC_SHM_ID_WOV_AUDIO = 0x0,
|
||||
EC_CODEC_SHM_ID_WOV_LANG = 0x1,
|
||||
EC_CODEC_SHM_ID_LAST,
|
||||
};
|
||||
|
||||
/*
|
||||
* For subcommand EC_CODEC_GET_GAIN.
|
||||
*/
|
||||
struct __ec_align1 ec_codec_i2s_gain {
|
||||
uint8_t left;
|
||||
uint8_t right;
|
||||
enum ec_codec_shm_type {
|
||||
EC_CODEC_SHM_TYPE_EC_RAM = 0x0,
|
||||
EC_CODEC_SHM_TYPE_SYSTEM_RAM = 0x1,
|
||||
};
|
||||
|
||||
struct __ec_todo_unpacked ec_param_codec_i2s_tdm {
|
||||
int16_t ch0_delay; /* 0 to 496 */
|
||||
int16_t ch1_delay; /* -1 to 496 */
|
||||
uint8_t adjacent_to_ch0;
|
||||
uint8_t adjacent_to_ch1;
|
||||
struct __ec_align1 ec_param_ec_codec_get_shm_addr {
|
||||
uint8_t shm_id;
|
||||
uint8_t reserved[3];
|
||||
};
|
||||
|
||||
struct __ec_todo_packed ec_param_codec_i2s {
|
||||
/* enum ec_codec_i2s_subcmd */
|
||||
uint8_t cmd;
|
||||
struct __ec_align4 ec_param_ec_codec_set_shm_addr {
|
||||
uint64_t phys_addr;
|
||||
uint32_t len;
|
||||
uint8_t shm_id;
|
||||
uint8_t reserved[3];
|
||||
};
|
||||
|
||||
struct __ec_align4 ec_param_ec_codec {
|
||||
uint8_t cmd; /* enum ec_codec_subcmd */
|
||||
uint8_t reserved[3];
|
||||
|
||||
union {
|
||||
/*
|
||||
* EC_CODEC_SET_SAMPLE_DEPTH
|
||||
* Value should be one of ec_sample_depth_value.
|
||||
*/
|
||||
uint8_t depth;
|
||||
|
||||
/*
|
||||
* EC_CODEC_SET_GAIN
|
||||
* Value should be 0~43 for both channels.
|
||||
*/
|
||||
struct ec_codec_i2s_gain gain;
|
||||
|
||||
/*
|
||||
* EC_CODEC_I2S_ENABLE
|
||||
* 1 to enable, 0 to disable.
|
||||
*/
|
||||
uint8_t i2s_enable;
|
||||
|
||||
/*
|
||||
* EC_CODEC_I2S_SET_CONFIG
|
||||
* Value should be one of ec_i2s_config.
|
||||
*/
|
||||
uint8_t i2s_config;
|
||||
|
||||
/*
|
||||
* EC_CODEC_I2S_SET_TDM_CONFIG
|
||||
* Value should be one of ec_i2s_config.
|
||||
*/
|
||||
struct ec_param_codec_i2s_tdm tdm_param;
|
||||
|
||||
/*
|
||||
* EC_CODEC_I2S_SET_BCLK
|
||||
*/
|
||||
uint32_t bclk;
|
||||
struct ec_param_ec_codec_get_shm_addr
|
||||
get_shm_addr_param;
|
||||
struct ec_param_ec_codec_set_shm_addr
|
||||
set_shm_addr_param;
|
||||
};
|
||||
};
|
||||
|
||||
struct __ec_align4 ec_response_ec_codec_get_capabilities {
|
||||
uint32_t capabilities;
|
||||
};
|
||||
|
||||
struct __ec_align4 ec_response_ec_codec_get_shm_addr {
|
||||
uint64_t phys_addr;
|
||||
uint32_t len;
|
||||
uint8_t type;
|
||||
uint8_t reserved[3];
|
||||
};
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
/* Commands for DMIC on audio codec. */
|
||||
#define EC_CMD_EC_CODEC_DMIC 0x00BD
|
||||
|
||||
enum ec_codec_dmic_subcmd {
|
||||
EC_CODEC_DMIC_GET_MAX_GAIN = 0x0,
|
||||
EC_CODEC_DMIC_SET_GAIN_IDX = 0x1,
|
||||
EC_CODEC_DMIC_GET_GAIN_IDX = 0x2,
|
||||
EC_CODEC_DMIC_SUBCMD_COUNT,
|
||||
};
|
||||
|
||||
enum ec_codec_dmic_channel {
|
||||
EC_CODEC_DMIC_CHANNEL_0 = 0x0,
|
||||
EC_CODEC_DMIC_CHANNEL_1 = 0x1,
|
||||
EC_CODEC_DMIC_CHANNEL_2 = 0x2,
|
||||
EC_CODEC_DMIC_CHANNEL_3 = 0x3,
|
||||
EC_CODEC_DMIC_CHANNEL_4 = 0x4,
|
||||
EC_CODEC_DMIC_CHANNEL_5 = 0x5,
|
||||
EC_CODEC_DMIC_CHANNEL_6 = 0x6,
|
||||
EC_CODEC_DMIC_CHANNEL_7 = 0x7,
|
||||
EC_CODEC_DMIC_CHANNEL_COUNT,
|
||||
};
|
||||
|
||||
struct __ec_align1 ec_param_ec_codec_dmic_set_gain_idx {
|
||||
uint8_t channel; /* enum ec_codec_dmic_channel */
|
||||
uint8_t gain;
|
||||
uint8_t reserved[2];
|
||||
};
|
||||
|
||||
struct __ec_align1 ec_param_ec_codec_dmic_get_gain_idx {
|
||||
uint8_t channel; /* enum ec_codec_dmic_channel */
|
||||
uint8_t reserved[3];
|
||||
};
|
||||
|
||||
struct __ec_align4 ec_param_ec_codec_dmic {
|
||||
uint8_t cmd; /* enum ec_codec_dmic_subcmd */
|
||||
uint8_t reserved[3];
|
||||
|
||||
union {
|
||||
struct ec_param_ec_codec_dmic_set_gain_idx
|
||||
set_gain_idx_param;
|
||||
struct ec_param_ec_codec_dmic_get_gain_idx
|
||||
get_gain_idx_param;
|
||||
};
|
||||
};
|
||||
|
||||
struct __ec_align1 ec_response_ec_codec_dmic_get_max_gain {
|
||||
uint8_t max_gain;
|
||||
};
|
||||
|
||||
struct __ec_align1 ec_response_ec_codec_dmic_get_gain_idx {
|
||||
uint8_t gain;
|
||||
};
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
/* Commands for I2S RX on audio codec. */
|
||||
|
||||
#define EC_CMD_EC_CODEC_I2S_RX 0x00BE
|
||||
|
||||
enum ec_codec_i2s_rx_subcmd {
|
||||
EC_CODEC_I2S_RX_ENABLE = 0x0,
|
||||
EC_CODEC_I2S_RX_DISABLE = 0x1,
|
||||
EC_CODEC_I2S_RX_SET_SAMPLE_DEPTH = 0x2,
|
||||
EC_CODEC_I2S_RX_SET_DAIFMT = 0x3,
|
||||
EC_CODEC_I2S_RX_SET_BCLK = 0x4,
|
||||
EC_CODEC_I2S_RX_SUBCMD_COUNT,
|
||||
};
|
||||
|
||||
enum ec_codec_i2s_rx_sample_depth {
|
||||
EC_CODEC_I2S_RX_SAMPLE_DEPTH_16 = 0x0,
|
||||
EC_CODEC_I2S_RX_SAMPLE_DEPTH_24 = 0x1,
|
||||
EC_CODEC_I2S_RX_SAMPLE_DEPTH_COUNT,
|
||||
};
|
||||
|
||||
enum ec_codec_i2s_rx_daifmt {
|
||||
EC_CODEC_I2S_RX_DAIFMT_I2S = 0x0,
|
||||
EC_CODEC_I2S_RX_DAIFMT_RIGHT_J = 0x1,
|
||||
EC_CODEC_I2S_RX_DAIFMT_LEFT_J = 0x2,
|
||||
EC_CODEC_I2S_RX_DAIFMT_COUNT,
|
||||
};
|
||||
|
||||
struct __ec_align1 ec_param_ec_codec_i2s_rx_set_sample_depth {
|
||||
uint8_t depth;
|
||||
uint8_t reserved[3];
|
||||
};
|
||||
|
||||
struct __ec_align1 ec_param_ec_codec_i2s_rx_set_gain {
|
||||
uint8_t left;
|
||||
uint8_t right;
|
||||
uint8_t reserved[2];
|
||||
};
|
||||
|
||||
struct __ec_align1 ec_param_ec_codec_i2s_rx_set_daifmt {
|
||||
uint8_t daifmt;
|
||||
uint8_t reserved[3];
|
||||
};
|
||||
|
||||
struct __ec_align4 ec_param_ec_codec_i2s_rx_set_bclk {
|
||||
uint32_t bclk;
|
||||
};
|
||||
|
||||
struct __ec_align4 ec_param_ec_codec_i2s_rx {
|
||||
uint8_t cmd; /* enum ec_codec_i2s_rx_subcmd */
|
||||
uint8_t reserved[3];
|
||||
|
||||
union {
|
||||
struct ec_param_ec_codec_i2s_rx_set_sample_depth
|
||||
set_sample_depth_param;
|
||||
struct ec_param_ec_codec_i2s_rx_set_daifmt
|
||||
set_daifmt_param;
|
||||
struct ec_param_ec_codec_i2s_rx_set_bclk
|
||||
set_bclk_param;
|
||||
};
|
||||
};
|
||||
|
||||
/*****************************************************************************/
|
||||
/* Commands for WoV on audio codec. */
|
||||
|
||||
#define EC_CMD_EC_CODEC_WOV 0x00BF
|
||||
|
||||
enum ec_codec_wov_subcmd {
|
||||
EC_CODEC_WOV_SET_LANG = 0x0,
|
||||
EC_CODEC_WOV_SET_LANG_SHM = 0x1,
|
||||
EC_CODEC_WOV_GET_LANG = 0x2,
|
||||
EC_CODEC_WOV_ENABLE = 0x3,
|
||||
EC_CODEC_WOV_DISABLE = 0x4,
|
||||
EC_CODEC_WOV_READ_AUDIO = 0x5,
|
||||
EC_CODEC_WOV_READ_AUDIO_SHM = 0x6,
|
||||
EC_CODEC_WOV_SUBCMD_COUNT,
|
||||
};
|
||||
|
||||
/*
|
||||
* @hash is SHA256 of the whole language model.
|
||||
* @total_len indicates the length of whole language model.
|
||||
* @offset is the cursor from the beginning of the model.
|
||||
* @buf is the packet buffer.
|
||||
* @len denotes how many bytes in the buf.
|
||||
*/
|
||||
struct __ec_align4 ec_param_ec_codec_wov_set_lang {
|
||||
uint8_t hash[32];
|
||||
uint32_t total_len;
|
||||
uint32_t offset;
|
||||
uint8_t buf[128];
|
||||
uint32_t len;
|
||||
};
|
||||
|
||||
struct __ec_align4 ec_param_ec_codec_wov_set_lang_shm {
|
||||
uint8_t hash[32];
|
||||
uint32_t total_len;
|
||||
};
|
||||
|
||||
struct __ec_align4 ec_param_ec_codec_wov {
|
||||
uint8_t cmd; /* enum ec_codec_wov_subcmd */
|
||||
uint8_t reserved[3];
|
||||
|
||||
union {
|
||||
struct ec_param_ec_codec_wov_set_lang
|
||||
set_lang_param;
|
||||
struct ec_param_ec_codec_wov_set_lang_shm
|
||||
set_lang_shm_param;
|
||||
};
|
||||
};
|
||||
|
||||
struct __ec_align4 ec_response_ec_codec_wov_get_lang {
|
||||
uint8_t hash[32];
|
||||
};
|
||||
|
||||
struct __ec_align4 ec_response_ec_codec_wov_read_audio {
|
||||
uint8_t buf[128];
|
||||
uint32_t len;
|
||||
};
|
||||
|
||||
struct __ec_align4 ec_response_ec_codec_wov_read_audio_shm {
|
||||
uint32_t offset;
|
||||
uint32_t len;
|
||||
};
|
||||
|
||||
/*****************************************************************************/
|
||||
/* System commands */
|
||||
|
|
|
@ -117,6 +117,7 @@ struct snd_card {
|
|||
struct device card_dev; /* cardX object for sysfs */
|
||||
const struct attribute_group *dev_groups[4]; /* assigned sysfs attr */
|
||||
bool registered; /* card_dev is registered? */
|
||||
int sync_irq; /* assigned irq, used for PCM sync */
|
||||
wait_queue_head_t remove_sleep;
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
|
|
|
@ -83,6 +83,11 @@ void snd_dmaengine_pcm_set_config_from_dai_data(
|
|||
const struct snd_dmaengine_dai_dma_data *dma_data,
|
||||
struct dma_slave_config *config);
|
||||
|
||||
int snd_dmaengine_pcm_refine_runtime_hwparams(
|
||||
struct snd_pcm_substream *substream,
|
||||
struct snd_dmaengine_dai_dma_data *dma_data,
|
||||
struct snd_pcm_hardware *hw,
|
||||
struct dma_chan *chan);
|
||||
|
||||
/*
|
||||
* Try to request the DMA channel using compat_request_channel or
|
||||
|
|
|
@ -254,6 +254,7 @@ struct hda_codec {
|
|||
unsigned int force_pin_prefix:1; /* Add location prefix */
|
||||
unsigned int link_down_at_suspend:1; /* link down at runtime suspend */
|
||||
unsigned int relaxed_resume:1; /* don't resume forcibly for jack */
|
||||
unsigned int mst_no_extra_pcms:1; /* no backup PCMs for DP-MST */
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
unsigned long power_on_acct;
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* intel-dsp-config.h - Intel DSP config
|
||||
*
|
||||
* Copyright (c) 2019 Jaroslav Kysela <perex@perex.cz>
|
||||
*/
|
||||
|
||||
#ifndef __INTEL_DSP_CONFIG_H__
|
||||
#define __INTEL_DSP_CONFIG_H__
|
||||
|
||||
struct pci_dev;
|
||||
|
||||
enum {
|
||||
SND_INTEL_DSP_DRIVER_ANY = 0,
|
||||
SND_INTEL_DSP_DRIVER_LEGACY,
|
||||
SND_INTEL_DSP_DRIVER_SST,
|
||||
SND_INTEL_DSP_DRIVER_SOF,
|
||||
SND_INTEL_DSP_DRIVER_LAST = SND_INTEL_DSP_DRIVER_SOF
|
||||
};
|
||||
|
||||
#if IS_ENABLED(CONFIG_SND_INTEL_DSP_CONFIG)
|
||||
|
||||
int snd_intel_dsp_driver_probe(struct pci_dev *pci);
|
||||
|
||||
#else
|
||||
|
||||
static inline int snd_intel_dsp_driver_probe(struct pci_dev *pci)
|
||||
{
|
||||
return SND_INTEL_DSP_DRIVER_ANY;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -21,7 +21,6 @@ struct snd_dma_device {
|
|||
struct device *dev; /* generic device */
|
||||
};
|
||||
|
||||
#define snd_dma_pci_data(pci) (&(pci)->dev)
|
||||
#define snd_dma_continuous_data(x) ((struct device *)(__force unsigned long)(x))
|
||||
|
||||
|
||||
|
@ -44,6 +43,7 @@ struct snd_dma_device {
|
|||
#else
|
||||
#define SNDRV_DMA_TYPE_DEV_IRAM SNDRV_DMA_TYPE_DEV
|
||||
#endif
|
||||
#define SNDRV_DMA_TYPE_VMALLOC 7 /* vmalloc'ed buffer */
|
||||
|
||||
/*
|
||||
* info for buffer allocation
|
||||
|
|
|
@ -59,6 +59,7 @@ struct snd_pcm_ops {
|
|||
int (*hw_free)(struct snd_pcm_substream *substream);
|
||||
int (*prepare)(struct snd_pcm_substream *substream);
|
||||
int (*trigger)(struct snd_pcm_substream *substream, int cmd);
|
||||
int (*sync_stop)(struct snd_pcm_substream *substream);
|
||||
snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream);
|
||||
int (*get_time_info)(struct snd_pcm_substream *substream,
|
||||
struct timespec *system_ts, struct timespec *audio_ts,
|
||||
|
@ -395,6 +396,7 @@ struct snd_pcm_runtime {
|
|||
wait_queue_head_t sleep; /* poll sleep */
|
||||
wait_queue_head_t tsleep; /* transfer sleep */
|
||||
struct fasync_struct *fasync;
|
||||
bool stop_operating; /* sync_stop will be called */
|
||||
|
||||
/* -- private section -- */
|
||||
void *private_data;
|
||||
|
@ -414,6 +416,7 @@ struct snd_pcm_runtime {
|
|||
size_t dma_bytes; /* size of DMA area */
|
||||
|
||||
struct snd_dma_buffer *dma_buffer_p; /* allocated buffer */
|
||||
unsigned int buffer_changed:1; /* buffer allocation changed; set only in managed mode */
|
||||
|
||||
/* -- audio timestamp config -- */
|
||||
struct snd_pcm_audio_tstamp_config audio_tstamp_config;
|
||||
|
@ -475,6 +478,7 @@ struct snd_pcm_substream {
|
|||
#endif /* CONFIG_SND_VERBOSE_PROCFS */
|
||||
/* misc flags */
|
||||
unsigned int hw_opened: 1;
|
||||
unsigned int managed_buffer_alloc:1;
|
||||
};
|
||||
|
||||
#define SUBSTREAM_BUSY(substream) ((substream)->ref_count > 0)
|
||||
|
@ -1186,6 +1190,12 @@ void snd_pcm_lib_preallocate_pages_for_all(struct snd_pcm *pcm,
|
|||
int snd_pcm_lib_malloc_pages(struct snd_pcm_substream *substream, size_t size);
|
||||
int snd_pcm_lib_free_pages(struct snd_pcm_substream *substream);
|
||||
|
||||
void snd_pcm_set_managed_buffer(struct snd_pcm_substream *substream, int type,
|
||||
struct device *data, size_t size, size_t max);
|
||||
void snd_pcm_set_managed_buffer_all(struct snd_pcm *pcm, int type,
|
||||
struct device *data,
|
||||
size_t size, size_t max);
|
||||
|
||||
int _snd_pcm_lib_alloc_vmalloc_buffer(struct snd_pcm_substream *substream,
|
||||
size_t size, gfp_t gfp_flags);
|
||||
int snd_pcm_lib_free_vmalloc_buffer(struct snd_pcm_substream *substream);
|
||||
|
@ -1236,14 +1246,6 @@ static inline int snd_pcm_lib_alloc_vmalloc_32_buffer
|
|||
*/
|
||||
#define snd_pcm_substream_sgbuf(substream) \
|
||||
snd_pcm_get_dma_buf(substream)->private_data
|
||||
|
||||
struct page *snd_pcm_sgbuf_ops_page(struct snd_pcm_substream *substream,
|
||||
unsigned long offset);
|
||||
#else /* !SND_DMA_SGBUF */
|
||||
/*
|
||||
* fake using a continuous buffer
|
||||
*/
|
||||
#define snd_pcm_sgbuf_ops_page NULL
|
||||
#endif /* SND_DMA_SGBUF */
|
||||
|
||||
/**
|
||||
|
@ -1336,8 +1338,6 @@ static inline void snd_pcm_limit_isa_dma_size(int dma, size_t *max)
|
|||
(IEC958_AES1_CON_PCM_CODER<<8)|\
|
||||
(IEC958_AES3_CON_FS_48000<<24))
|
||||
|
||||
#define PCM_RUNTIME_CHECK(sub) snd_BUG_ON(!(sub) || !(sub)->runtime)
|
||||
|
||||
const char *snd_pcm_format_name(snd_pcm_format_t format);
|
||||
|
||||
/**
|
||||
|
|
|
@ -10,6 +10,7 @@ struct snd_pcm_substream;
|
|||
struct snd_pcm_hw_params;
|
||||
struct snd_soc_pcm_runtime;
|
||||
struct snd_pcm;
|
||||
struct snd_soc_component;
|
||||
|
||||
extern int pxa2xx_pcm_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params);
|
||||
|
@ -23,8 +24,29 @@ extern int pxa2xx_pcm_mmap(struct snd_pcm_substream *substream,
|
|||
struct vm_area_struct *vma);
|
||||
extern int pxa2xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream);
|
||||
extern void pxa2xx_pcm_free_dma_buffers(struct snd_pcm *pcm);
|
||||
extern int pxa2xx_soc_pcm_new(struct snd_soc_pcm_runtime *rtd);
|
||||
extern const struct snd_pcm_ops pxa2xx_pcm_ops;
|
||||
extern void pxa2xx_soc_pcm_free(struct snd_soc_component *component,
|
||||
struct snd_pcm *pcm);
|
||||
extern int pxa2xx_soc_pcm_new(struct snd_soc_component *component,
|
||||
struct snd_soc_pcm_runtime *rtd);
|
||||
extern int pxa2xx_soc_pcm_open(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream);
|
||||
extern int pxa2xx_soc_pcm_close(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream);
|
||||
extern int pxa2xx_soc_pcm_hw_params(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params);
|
||||
extern int pxa2xx_soc_pcm_hw_free(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream);
|
||||
extern int pxa2xx_soc_pcm_prepare(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream);
|
||||
extern int pxa2xx_soc_pcm_trigger(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream, int cmd);
|
||||
extern snd_pcm_uframes_t
|
||||
pxa2xx_soc_pcm_pointer(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream);
|
||||
extern int pxa2xx_soc_pcm_mmap(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream,
|
||||
struct vm_area_struct *vma);
|
||||
|
||||
/* AC97 */
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ struct rt5682_platform_data {
|
|||
enum rt5682_dmic1_data_pin dmic1_data_pin;
|
||||
enum rt5682_dmic1_clk_pin dmic1_clk_pin;
|
||||
enum rt5682_jd_src jd_src;
|
||||
unsigned int btndet_delay;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#ifndef __SIMPLE_CARD_UTILS_H
|
||||
#define __SIMPLE_CARD_UTILS_H
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#define asoc_simple_init_hp(card, sjack, prefix) \
|
||||
|
|
|
@ -24,9 +24,12 @@ extern struct snd_soc_acpi_mach snd_soc_acpi_intel_kbl_machines[];
|
|||
extern struct snd_soc_acpi_mach snd_soc_acpi_intel_bxt_machines[];
|
||||
extern struct snd_soc_acpi_mach snd_soc_acpi_intel_glk_machines[];
|
||||
extern struct snd_soc_acpi_mach snd_soc_acpi_intel_cnl_machines[];
|
||||
extern struct snd_soc_acpi_mach snd_soc_acpi_intel_cfl_machines[];
|
||||
extern struct snd_soc_acpi_mach snd_soc_acpi_intel_cml_machines[];
|
||||
extern struct snd_soc_acpi_mach snd_soc_acpi_intel_icl_machines[];
|
||||
extern struct snd_soc_acpi_mach snd_soc_acpi_intel_tgl_machines[];
|
||||
extern struct snd_soc_acpi_mach snd_soc_acpi_intel_ehl_machines[];
|
||||
extern struct snd_soc_acpi_mach snd_soc_acpi_intel_jsl_machines[];
|
||||
|
||||
/*
|
||||
* generic table used for HDA codec-based platforms, possibly with
|
||||
|
|
|
@ -60,12 +60,14 @@ static inline struct snd_soc_acpi_mach *snd_soc_acpi_codec_list(void *arg)
|
|||
* @acpi_ipc_irq_index: used for BYT-CR detection
|
||||
* @platform: string used for HDaudio codec support
|
||||
* @codec_mask: used for HDAudio support
|
||||
* @common_hdmi_codec_drv: use commom HDAudio HDMI codec driver
|
||||
*/
|
||||
struct snd_soc_acpi_mach_params {
|
||||
u32 acpi_ipc_irq_index;
|
||||
const char *platform;
|
||||
u32 codec_mask;
|
||||
u32 dmic_num;
|
||||
bool common_hdmi_codec_drv;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -75,6 +77,7 @@ struct snd_soc_acpi_mach_params {
|
|||
* all firmware/topology related fields.
|
||||
*
|
||||
* @id: ACPI ID (usually the codec's) used to find a matching machine driver.
|
||||
* @link_mask: describes required board layout, e.g. for SoundWire.
|
||||
* @drv_name: machine driver name
|
||||
* @fw_filename: firmware file name. Used when SOF is not enabled.
|
||||
* @board: board name
|
||||
|
@ -90,6 +93,7 @@ struct snd_soc_acpi_mach_params {
|
|||
/* Descriptor for SST ASoC machine driver */
|
||||
struct snd_soc_acpi_mach {
|
||||
const u8 id[ACPI_ID_LEN];
|
||||
const u32 link_mask;
|
||||
const char *drv_name;
|
||||
const char *fw_filename;
|
||||
const char *board;
|
||||
|
|
|
@ -3,10 +3,6 @@
|
|||
* soc-component.h
|
||||
*
|
||||
* Copyright (c) 2019 Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
#ifndef __SOC_COMPONENT_H
|
||||
#define __SOC_COMPONENT_H
|
||||
|
@ -51,8 +47,10 @@ struct snd_soc_component_driver {
|
|||
unsigned int reg, unsigned int val);
|
||||
|
||||
/* pcm creation and destruction */
|
||||
int (*pcm_new)(struct snd_soc_pcm_runtime *rtd);
|
||||
void (*pcm_free)(struct snd_pcm *pcm);
|
||||
int (*pcm_construct)(struct snd_soc_component *component,
|
||||
struct snd_soc_pcm_runtime *rtd);
|
||||
void (*pcm_destruct)(struct snd_soc_component *component,
|
||||
struct snd_pcm *pcm);
|
||||
|
||||
/* component wide operations */
|
||||
int (*set_sysclk)(struct snd_soc_component *component,
|
||||
|
@ -74,7 +72,42 @@ struct snd_soc_component_driver {
|
|||
int (*set_bias_level)(struct snd_soc_component *component,
|
||||
enum snd_soc_bias_level level);
|
||||
|
||||
const struct snd_pcm_ops *ops;
|
||||
int (*open)(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream);
|
||||
int (*close)(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream);
|
||||
int (*ioctl)(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream,
|
||||
unsigned int cmd, void *arg);
|
||||
int (*hw_params)(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params);
|
||||
int (*hw_free)(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream);
|
||||
int (*prepare)(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream);
|
||||
int (*trigger)(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream, int cmd);
|
||||
int (*sync_stop)(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream);
|
||||
snd_pcm_uframes_t (*pointer)(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream);
|
||||
int (*get_time_info)(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream, struct timespec *system_ts,
|
||||
struct timespec *audio_ts,
|
||||
struct snd_pcm_audio_tstamp_config *audio_tstamp_config,
|
||||
struct snd_pcm_audio_tstamp_report *audio_tstamp_report);
|
||||
int (*copy_user)(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream, int channel,
|
||||
unsigned long pos, void __user *buf,
|
||||
unsigned long bytes);
|
||||
struct page *(*page)(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream,
|
||||
unsigned long offset);
|
||||
int (*mmap)(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream,
|
||||
struct vm_area_struct *vma);
|
||||
|
||||
const struct snd_compr_ops *compr_ops;
|
||||
|
||||
/* probe ordering - for components with runtime dependencies */
|
||||
|
@ -374,6 +407,7 @@ int snd_soc_component_of_xlate_dai_name(struct snd_soc_component *component,
|
|||
int snd_soc_pcm_component_pointer(struct snd_pcm_substream *substream);
|
||||
int snd_soc_pcm_component_ioctl(struct snd_pcm_substream *substream,
|
||||
unsigned int cmd, void *arg);
|
||||
int snd_soc_pcm_component_sync_stop(struct snd_pcm_substream *substream);
|
||||
int snd_soc_pcm_component_copy_user(struct snd_pcm_substream *substream,
|
||||
int channel, unsigned long pos,
|
||||
void __user *buf, unsigned long bytes);
|
||||
|
@ -381,7 +415,7 @@ struct page *snd_soc_pcm_component_page(struct snd_pcm_substream *substream,
|
|||
unsigned long offset);
|
||||
int snd_soc_pcm_component_mmap(struct snd_pcm_substream *substream,
|
||||
struct vm_area_struct *vma);
|
||||
int snd_soc_pcm_component_new(struct snd_pcm *pcm);
|
||||
void snd_soc_pcm_component_free(struct snd_pcm *pcm);
|
||||
int snd_soc_pcm_component_new(struct snd_soc_pcm_runtime *rtd);
|
||||
void snd_soc_pcm_component_free(struct snd_soc_pcm_runtime *rtd);
|
||||
|
||||
#endif /* __SOC_COMPONENT_H */
|
||||
|
|
|
@ -103,15 +103,15 @@ struct snd_soc_dpcm_runtime {
|
|||
int trigger_pending; /* trigger cmd + 1 if pending, 0 if not */
|
||||
};
|
||||
|
||||
#define for_each_dpcm_fe(be, stream, dpcm) \
|
||||
list_for_each_entry(dpcm, &(be)->dpcm[stream].fe_clients, list_fe)
|
||||
#define for_each_dpcm_fe(be, stream, _dpcm) \
|
||||
list_for_each_entry(_dpcm, &(be)->dpcm[stream].fe_clients, list_fe)
|
||||
|
||||
#define for_each_dpcm_be(fe, stream, dpcm) \
|
||||
list_for_each_entry(dpcm, &(fe)->dpcm[stream].be_clients, list_be)
|
||||
#define for_each_dpcm_be_safe(fe, stream, dpcm, _dpcm) \
|
||||
list_for_each_entry_safe(dpcm, _dpcm, &(fe)->dpcm[stream].be_clients, list_be)
|
||||
#define for_each_dpcm_be_rollback(fe, stream, dpcm) \
|
||||
list_for_each_entry_continue_reverse(dpcm, &(fe)->dpcm[stream].be_clients, list_be)
|
||||
#define for_each_dpcm_be(fe, stream, _dpcm) \
|
||||
list_for_each_entry(_dpcm, &(fe)->dpcm[stream].be_clients, list_be)
|
||||
#define for_each_dpcm_be_safe(fe, stream, _dpcm, __dpcm) \
|
||||
list_for_each_entry_safe(_dpcm, __dpcm, &(fe)->dpcm[stream].be_clients, list_be)
|
||||
#define for_each_dpcm_be_rollback(fe, stream, _dpcm) \
|
||||
list_for_each_entry_continue_reverse(_dpcm, &(fe)->dpcm[stream].be_clients, list_be)
|
||||
|
||||
/* can this BE stop and free */
|
||||
int snd_soc_dpcm_can_be_free_stop(struct snd_soc_pcm_runtime *fe,
|
||||
|
|
|
@ -299,6 +299,12 @@
|
|||
.put = snd_soc_bytes_put, .private_value = \
|
||||
((unsigned long)&(struct soc_bytes) \
|
||||
{.base = xbase, .num_regs = xregs }) }
|
||||
#define SND_SOC_BYTES_E(xname, xbase, xregs, xhandler_get, xhandler_put) \
|
||||
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
|
||||
.info = snd_soc_bytes_info, .get = xhandler_get, \
|
||||
.put = xhandler_put, .private_value = \
|
||||
((unsigned long)&(struct soc_bytes) \
|
||||
{.base = xbase, .num_regs = xregs }) }
|
||||
|
||||
#define SND_SOC_BYTES_MASK(xname, xbase, xregs, xmask) \
|
||||
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
|
||||
|
@ -739,10 +745,12 @@ struct snd_soc_rtdcom_list {
|
|||
struct snd_soc_component*
|
||||
snd_soc_rtdcom_lookup(struct snd_soc_pcm_runtime *rtd,
|
||||
const char *driver_name);
|
||||
#define for_each_rtdcom(rtd, rtdcom) \
|
||||
list_for_each_entry(rtdcom, &(rtd)->component_list, list)
|
||||
#define for_each_rtdcom_safe(rtd, rtdcom1, rtdcom2) \
|
||||
list_for_each_entry_safe(rtdcom1, rtdcom2, &(rtd)->component_list, list)
|
||||
#define for_each_rtd_components(rtd, rtdcom, _component) \
|
||||
for (rtdcom = list_first_entry(&(rtd)->component_list, \
|
||||
typeof(*rtdcom), list); \
|
||||
(&rtdcom->list != &(rtd)->component_list) && \
|
||||
(_component = rtdcom->component); \
|
||||
rtdcom = list_next_entry(rtdcom, list))
|
||||
|
||||
struct snd_soc_dai_link_component {
|
||||
const char *name;
|
||||
|
@ -845,7 +853,9 @@ struct snd_soc_dai_link {
|
|||
unsigned int ignore:1;
|
||||
|
||||
struct list_head list; /* DAI link list of the soc card */
|
||||
#ifdef CONFIG_SND_SOC_TOPOLOGY
|
||||
struct snd_soc_dobj dobj; /* For topology */
|
||||
#endif
|
||||
};
|
||||
#define for_each_link_codecs(link, i, codec) \
|
||||
for ((i) = 0; \
|
||||
|
@ -978,6 +988,7 @@ struct snd_soc_card {
|
|||
const char *name;
|
||||
const char *long_name;
|
||||
const char *driver_name;
|
||||
const char *components;
|
||||
char dmi_longname[80];
|
||||
char topology_shortname[32];
|
||||
|
||||
|
@ -1148,7 +1159,6 @@ struct snd_soc_pcm_runtime {
|
|||
struct list_head component_list; /* list of connected components */
|
||||
|
||||
/* bit field */
|
||||
unsigned int dev_registered:1;
|
||||
unsigned int pop_wait:1;
|
||||
unsigned int fe_compr:1; /* for Dynamic PCM */
|
||||
};
|
||||
|
@ -1168,7 +1178,9 @@ struct soc_mixer_control {
|
|||
unsigned int sign_bit;
|
||||
unsigned int invert:1;
|
||||
unsigned int autodisable:1;
|
||||
#ifdef CONFIG_SND_SOC_TOPOLOGY
|
||||
struct snd_soc_dobj dobj;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct soc_bytes {
|
||||
|
@ -1179,8 +1191,9 @@ struct soc_bytes {
|
|||
|
||||
struct soc_bytes_ext {
|
||||
int max;
|
||||
#ifdef CONFIG_SND_SOC_TOPOLOGY
|
||||
struct snd_soc_dobj dobj;
|
||||
|
||||
#endif
|
||||
/* used for TLV byte control */
|
||||
int (*get)(struct snd_kcontrol *kcontrol, unsigned int __user *bytes,
|
||||
unsigned int size);
|
||||
|
@ -1204,7 +1217,9 @@ struct soc_enum {
|
|||
const char * const *texts;
|
||||
const unsigned int *values;
|
||||
unsigned int autodisable:1;
|
||||
#ifdef CONFIG_SND_SOC_TOPOLOGY
|
||||
struct snd_soc_dobj dobj;
|
||||
#endif
|
||||
};
|
||||
|
||||
/* device driver data */
|
||||
|
@ -1325,8 +1340,10 @@ struct snd_soc_dai_link *snd_soc_find_dai_link(struct snd_soc_card *card,
|
|||
int id, const char *name,
|
||||
const char *stream_name);
|
||||
|
||||
int snd_soc_register_dai(struct snd_soc_component *component,
|
||||
struct snd_soc_dai_driver *dai_drv);
|
||||
struct snd_soc_dai *snd_soc_register_dai(struct snd_soc_component *component,
|
||||
struct snd_soc_dai_driver *dai_drv,
|
||||
bool legacy_dai_naming);
|
||||
void snd_soc_unregister_dai(struct snd_soc_dai *dai);
|
||||
|
||||
struct snd_soc_dai *snd_soc_find_dai(
|
||||
const struct snd_soc_dai_link_component *dlc);
|
||||
|
@ -1391,6 +1408,11 @@ static inline void snd_soc_dapm_mutex_unlock(struct snd_soc_dapm_context *dapm)
|
|||
mutex_unlock(&dapm->card->dapm_mutex);
|
||||
}
|
||||
|
||||
/* bypass */
|
||||
int snd_soc_pcm_lib_ioctl(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream,
|
||||
unsigned int cmd, void *arg);
|
||||
|
||||
#include <sound/soc-component.h>
|
||||
|
||||
#endif
|
||||
|
|
|
@ -61,6 +61,9 @@ struct sof_dev_desc {
|
|||
/* list of machines using this configuration */
|
||||
struct snd_soc_acpi_mach *machines;
|
||||
|
||||
/* alternate list of machines using this configuration */
|
||||
struct snd_soc_acpi_mach *alt_machines;
|
||||
|
||||
/* Platform resource indexes in BAR / ACPI resources. */
|
||||
/* Must set to -1 if not used - add new items to end */
|
||||
int resindex_lpe_base;
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
|
||||
/*
|
||||
* Copyright 2019 NXP
|
||||
*
|
||||
* Author: Daniel Baluta <daniel.baluta@nxp.com>
|
||||
*/
|
||||
|
||||
#ifndef __INCLUDE_SOUND_SOF_DAI_IMX_H__
|
||||
#define __INCLUDE_SOUND_SOF_DAI_IMX_H__
|
||||
|
||||
#include <sound/sof/header.h>
|
||||
|
||||
/* ESAI Configuration Request - SOF_IPC_DAI_ESAI_CONFIG */
|
||||
struct sof_ipc_dai_esai_params {
|
||||
struct sof_ipc_hdr hdr;
|
||||
|
||||
/* MCLK */
|
||||
uint16_t reserved1;
|
||||
uint16_t mclk_id;
|
||||
uint32_t mclk_direction;
|
||||
|
||||
uint32_t mclk_rate; /* MCLK frequency in Hz */
|
||||
uint32_t fsync_rate; /* FSYNC frequency in Hz */
|
||||
uint32_t bclk_rate; /* BCLK frequency in Hz */
|
||||
|
||||
/* TDM */
|
||||
uint32_t tdm_slots;
|
||||
uint32_t rx_slots;
|
||||
uint32_t tx_slots;
|
||||
uint16_t tdm_slot_width;
|
||||
uint16_t reserved2; /* alignment */
|
||||
} __packed;
|
||||
|
||||
#endif
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include <sound/sof/header.h>
|
||||
#include <sound/sof/dai-intel.h>
|
||||
#include <sound/sof/dai-imx.h>
|
||||
|
||||
/*
|
||||
* DAI Configuration.
|
||||
|
@ -73,6 +74,7 @@ struct sof_ipc_dai_config {
|
|||
struct sof_ipc_dai_dmic_params dmic;
|
||||
struct sof_ipc_dai_hda_params hda;
|
||||
struct sof_ipc_dai_alh_params alh;
|
||||
struct sof_ipc_dai_esai_params esai;
|
||||
};
|
||||
} __packed;
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#ifndef __INCLUDE_SOUND_SOF_HEADER_H__
|
||||
#define __INCLUDE_SOUND_SOF_HEADER_H__
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <uapi/sound/sof/abi.h>
|
||||
|
||||
/** \addtogroup sof_uapi uAPI
|
||||
|
@ -74,6 +75,7 @@
|
|||
#define SOF_IPC_PM_CLK_GET SOF_CMD_TYPE(0x005)
|
||||
#define SOF_IPC_PM_CLK_REQ SOF_CMD_TYPE(0x006)
|
||||
#define SOF_IPC_PM_CORE_ENABLE SOF_CMD_TYPE(0x007)
|
||||
#define SOF_IPC_PM_GATE SOF_CMD_TYPE(0x008)
|
||||
|
||||
/* component runtime config - multiple different types */
|
||||
#define SOF_IPC_COMP_SET_VALUE SOF_CMD_TYPE(0x001)
|
||||
|
|
|
@ -45,4 +45,12 @@ struct sof_ipc_pm_core_config {
|
|||
uint32_t enable_mask;
|
||||
} __packed;
|
||||
|
||||
struct sof_ipc_pm_gate {
|
||||
struct sof_ipc_cmd_hdr hdr;
|
||||
uint32_t flags; /* platform specific */
|
||||
|
||||
/* reserved for future use */
|
||||
uint32_t reserved[5];
|
||||
} __packed;
|
||||
|
||||
#endif
|
||||
|
|
|
@ -83,10 +83,10 @@ struct sof_ipc_stream_params {
|
|||
uint16_t sample_valid_bytes;
|
||||
uint16_t sample_container_bytes;
|
||||
|
||||
/* for notifying host period has completed - 0 means no period IRQ */
|
||||
uint32_t host_period_bytes;
|
||||
uint16_t no_stream_position; /**< 1 means don't send stream position */
|
||||
|
||||
uint32_t reserved[2];
|
||||
uint16_t reserved[3];
|
||||
uint16_t chmap[SOF_IPC_MAX_CHANNELS]; /**< channel map - SOF_CHMAP_ */
|
||||
} __packed;
|
||||
|
||||
|
|
|
@ -118,8 +118,10 @@ int snd_timer_global_new(char *id, int device, struct snd_timer **rtimer);
|
|||
int snd_timer_global_free(struct snd_timer *timer);
|
||||
int snd_timer_global_register(struct snd_timer *timer);
|
||||
|
||||
int snd_timer_open(struct snd_timer_instance **ti, char *owner, struct snd_timer_id *tid, unsigned int slave_id);
|
||||
int snd_timer_close(struct snd_timer_instance *timeri);
|
||||
struct snd_timer_instance *snd_timer_instance_new(const char *owner);
|
||||
void snd_timer_instance_free(struct snd_timer_instance *timeri);
|
||||
int snd_timer_open(struct snd_timer_instance *timeri, struct snd_timer_id *tid, unsigned int slave_id);
|
||||
void snd_timer_close(struct snd_timer_instance *timeri);
|
||||
unsigned long snd_timer_resolution(struct snd_timer_instance *timeri);
|
||||
int snd_timer_start(struct snd_timer_instance *timeri, unsigned int ticks);
|
||||
int snd_timer_stop(struct snd_timer_instance *timeri);
|
||||
|
|
|
@ -120,7 +120,7 @@
|
|||
* DRC configurations are specified with a label and a set of register
|
||||
* values to write (the enable bits will be ignored). At runtime an
|
||||
* enumerated control will be presented for each DRC block allowing
|
||||
* the user to choose the configration to use.
|
||||
* the user to choose the configuration to use.
|
||||
*
|
||||
* Configurations may be generated by hand or by using the DRC control
|
||||
* panel provided by the WISCE - see http://www.wolfsonmicro.com/wisce/
|
||||
|
|
|
@ -317,12 +317,22 @@ struct snd_enc_generic {
|
|||
__s32 reserved[15]; /* Can be used for SND_AUDIOCODEC_BESPOKE */
|
||||
} __attribute__((packed, aligned(4)));
|
||||
|
||||
struct snd_dec_flac {
|
||||
__u16 sample_size;
|
||||
__u16 min_blk_size;
|
||||
__u16 max_blk_size;
|
||||
__u16 min_frame_size;
|
||||
__u16 max_frame_size;
|
||||
__u16 reserved;
|
||||
} __attribute__((packed, aligned(4)));
|
||||
|
||||
union snd_codec_options {
|
||||
struct snd_enc_wma wma;
|
||||
struct snd_enc_vorbis vorbis;
|
||||
struct snd_enc_real real;
|
||||
struct snd_enc_flac flac;
|
||||
struct snd_enc_generic generic;
|
||||
struct snd_dec_flac flac_d;
|
||||
} __attribute__((packed, aligned(4)));
|
||||
|
||||
/** struct snd_codec_desc - description of codec capabilities
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
/* SOF ABI version major, minor and patch numbers */
|
||||
#define SOF_ABI_MAJOR 3
|
||||
#define SOF_ABI_MINOR 10
|
||||
#define SOF_ABI_MINOR 11
|
||||
#define SOF_ABI_PATCH 0
|
||||
|
||||
/* SOF ABI version number. Format within 32bit word is MMmmmppp */
|
||||
|
|
|
@ -111,7 +111,14 @@
|
|||
/* TODO: Add SAI tokens */
|
||||
|
||||
/* ESAI */
|
||||
#define SOF_TKN_IMX_ESAI_FIRST_TOKEN 1100
|
||||
/* TODO: Add ESAI tokens */
|
||||
#define SOF_TKN_IMX_ESAI_MCLK_ID 1100
|
||||
|
||||
/* Stream */
|
||||
#define SOF_TKN_STREAM_PLAYBACK_COMPATIBLE_D0I3 1200
|
||||
#define SOF_TKN_STREAM_CAPTURE_COMPATIBLE_D0I3 1201
|
||||
|
||||
/* Led control for mute switches */
|
||||
#define SOF_TKN_MUTE_LED_USE 1300
|
||||
#define SOF_TKN_MUTE_LED_DIRECTION 1301
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1028,7 +1028,7 @@ i2sbus_attach_codec(struct soundbus_dev *dev, struct snd_card *card,
|
|||
/* well, we really should support scatter/gather DMA */
|
||||
snd_pcm_lib_preallocate_pages_for_all(
|
||||
dev->pcm, SNDRV_DMA_TYPE_DEV,
|
||||
snd_dma_pci_data(macio_get_pci_dev(i2sdev->macio)),
|
||||
&macio_get_pci_dev(i2sdev->macio)->dev,
|
||||
64 * 1024, 64 * 1024);
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -175,7 +175,15 @@ void pxa2xx_pcm_free_dma_buffers(struct snd_pcm *pcm)
|
|||
}
|
||||
EXPORT_SYMBOL(pxa2xx_pcm_free_dma_buffers);
|
||||
|
||||
int pxa2xx_soc_pcm_new(struct snd_soc_pcm_runtime *rtd)
|
||||
void pxa2xx_soc_pcm_free(struct snd_soc_component *component,
|
||||
struct snd_pcm *pcm)
|
||||
{
|
||||
pxa2xx_pcm_free_dma_buffers(pcm);
|
||||
}
|
||||
EXPORT_SYMBOL(pxa2xx_soc_pcm_free);
|
||||
|
||||
int pxa2xx_soc_pcm_new(struct snd_soc_component *component,
|
||||
struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_card *card = rtd->card->snd_card;
|
||||
struct snd_pcm *pcm = rtd->pcm;
|
||||
|
@ -203,18 +211,64 @@ int pxa2xx_soc_pcm_new(struct snd_soc_pcm_runtime *rtd)
|
|||
}
|
||||
EXPORT_SYMBOL(pxa2xx_soc_pcm_new);
|
||||
|
||||
const struct snd_pcm_ops pxa2xx_pcm_ops = {
|
||||
.open = pxa2xx_pcm_open,
|
||||
.close = pxa2xx_pcm_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = pxa2xx_pcm_hw_params,
|
||||
.hw_free = pxa2xx_pcm_hw_free,
|
||||
.prepare = pxa2xx_pcm_prepare,
|
||||
.trigger = pxa2xx_pcm_trigger,
|
||||
.pointer = pxa2xx_pcm_pointer,
|
||||
.mmap = pxa2xx_pcm_mmap,
|
||||
};
|
||||
EXPORT_SYMBOL(pxa2xx_pcm_ops);
|
||||
int pxa2xx_soc_pcm_open(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
return pxa2xx_pcm_open(substream);
|
||||
}
|
||||
EXPORT_SYMBOL(pxa2xx_soc_pcm_open);
|
||||
|
||||
int pxa2xx_soc_pcm_close(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
return pxa2xx_pcm_close(substream);
|
||||
}
|
||||
EXPORT_SYMBOL(pxa2xx_soc_pcm_close);
|
||||
|
||||
int pxa2xx_soc_pcm_hw_params(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
return pxa2xx_pcm_hw_params(substream, params);
|
||||
}
|
||||
EXPORT_SYMBOL(pxa2xx_soc_pcm_hw_params);
|
||||
|
||||
int pxa2xx_soc_pcm_hw_free(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
return pxa2xx_pcm_hw_free(substream);
|
||||
}
|
||||
EXPORT_SYMBOL(pxa2xx_soc_pcm_hw_free);
|
||||
|
||||
int pxa2xx_soc_pcm_prepare(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
return pxa2xx_pcm_prepare(substream);
|
||||
}
|
||||
EXPORT_SYMBOL(pxa2xx_soc_pcm_prepare);
|
||||
|
||||
int pxa2xx_soc_pcm_trigger(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream, int cmd)
|
||||
{
|
||||
return pxa2xx_pcm_trigger(substream, cmd);
|
||||
}
|
||||
EXPORT_SYMBOL(pxa2xx_soc_pcm_trigger);
|
||||
|
||||
snd_pcm_uframes_t
|
||||
pxa2xx_soc_pcm_pointer(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
return pxa2xx_pcm_pointer(substream);
|
||||
}
|
||||
EXPORT_SYMBOL(pxa2xx_soc_pcm_pointer);
|
||||
|
||||
int pxa2xx_soc_pcm_mmap(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream,
|
||||
struct vm_area_struct *vma)
|
||||
{
|
||||
return pxa2xx_pcm_mmap(substream, vma);
|
||||
}
|
||||
EXPORT_SYMBOL(pxa2xx_soc_pcm_mmap);
|
||||
|
||||
MODULE_AUTHOR("Nicolas Pitre");
|
||||
MODULE_DESCRIPTION("Intel PXA2xx sound library");
|
||||
|
|
|
@ -72,11 +72,11 @@ config SND_PCM_OSS
|
|||
config SND_PCM_OSS_PLUGINS
|
||||
bool "OSS PCM (digital audio) API - Include plugin system"
|
||||
depends on SND_PCM_OSS
|
||||
default y
|
||||
default y
|
||||
help
|
||||
If you disable this option, the ALSA's OSS PCM API will not
|
||||
support conversion of channels, formats and rates. It will
|
||||
behave like most of new OSS/Free drivers in 2.4/2.6 kernels.
|
||||
If you disable this option, the ALSA's OSS PCM API will not
|
||||
support conversion of channels, formats and rates. It will
|
||||
behave like most of new OSS/Free drivers in 2.4/2.6 kernels.
|
||||
|
||||
config SND_PCM_TIMER
|
||||
bool "PCM timer interface" if EXPERT
|
||||
|
@ -128,13 +128,13 @@ config SND_SUPPORT_OLD_API
|
|||
or older).
|
||||
|
||||
config SND_PROC_FS
|
||||
bool "Sound Proc FS Support" if EXPERT
|
||||
depends on PROC_FS
|
||||
default y
|
||||
help
|
||||
Say 'N' to disable Sound proc FS, which may reduce code size about
|
||||
9KB on x86_64 platform.
|
||||
If unsure say Y.
|
||||
bool "Sound Proc FS Support" if EXPERT
|
||||
depends on PROC_FS
|
||||
default y
|
||||
help
|
||||
Say 'N' to disable Sound proc FS, which may reduce code size about
|
||||
9KB on x86_64 platform.
|
||||
If unsure say Y.
|
||||
|
||||
config SND_VERBOSE_PROCFS
|
||||
bool "Verbose procfs contents"
|
||||
|
@ -142,8 +142,8 @@ config SND_VERBOSE_PROCFS
|
|||
default y
|
||||
help
|
||||
Say Y here to include code for verbose procfs contents (provides
|
||||
useful information to developers when a problem occurs). On the
|
||||
other side, it makes the ALSA subsystem larger.
|
||||
useful information to developers when a problem occurs). On the
|
||||
other side, it makes the ALSA subsystem larger.
|
||||
|
||||
config SND_VERBOSE_PRINTK
|
||||
bool "Verbose printk"
|
||||
|
@ -164,7 +164,7 @@ config SND_DEBUG_VERBOSE
|
|||
depends on SND_DEBUG
|
||||
help
|
||||
Say Y here to enable extra-verbose debugging messages.
|
||||
|
||||
|
||||
Let me repeat: it enables EXTRA-VERBOSE DEBUGGING messages.
|
||||
So, say Y only if you are ready to be annoyed.
|
||||
|
||||
|
|
|
@ -215,6 +215,7 @@ int snd_card_new(struct device *parent, int idx, const char *xid,
|
|||
init_waitqueue_head(&card->power_sleep);
|
||||
#endif
|
||||
init_waitqueue_head(&card->remove_sleep);
|
||||
card->sync_irq = -1;
|
||||
|
||||
device_initialize(&card->card_dev);
|
||||
card->card_dev.parent = parent;
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <linux/mm.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/genalloc.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#ifdef CONFIG_X86
|
||||
#include <asm/set_memory.h>
|
||||
#endif
|
||||
|
@ -99,6 +100,14 @@ static void snd_free_dev_iram(struct snd_dma_buffer *dmab)
|
|||
*
|
||||
*/
|
||||
|
||||
static inline gfp_t snd_mem_get_gfp_flags(const struct device *dev,
|
||||
gfp_t default_gfp)
|
||||
{
|
||||
if (!dev)
|
||||
return default_gfp;
|
||||
else
|
||||
return (__force gfp_t)(unsigned long)dev;
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_dma_alloc_pages - allocate the buffer area according to the given type
|
||||
|
@ -116,20 +125,25 @@ static void snd_free_dev_iram(struct snd_dma_buffer *dmab)
|
|||
int snd_dma_alloc_pages(int type, struct device *device, size_t size,
|
||||
struct snd_dma_buffer *dmab)
|
||||
{
|
||||
gfp_t gfp;
|
||||
|
||||
if (WARN_ON(!size))
|
||||
return -ENXIO;
|
||||
if (WARN_ON(!dmab))
|
||||
return -ENXIO;
|
||||
if (WARN_ON(!device))
|
||||
return -EINVAL;
|
||||
|
||||
dmab->dev.type = type;
|
||||
dmab->dev.dev = device;
|
||||
dmab->bytes = 0;
|
||||
switch (type) {
|
||||
case SNDRV_DMA_TYPE_CONTINUOUS:
|
||||
dmab->area = alloc_pages_exact(size,
|
||||
(__force gfp_t)(unsigned long)device);
|
||||
gfp = snd_mem_get_gfp_flags(device, GFP_KERNEL);
|
||||
dmab->area = alloc_pages_exact(size, gfp);
|
||||
dmab->addr = 0;
|
||||
break;
|
||||
case SNDRV_DMA_TYPE_VMALLOC:
|
||||
gfp = snd_mem_get_gfp_flags(device, GFP_KERNEL | __GFP_HIGHMEM);
|
||||
dmab->area = __vmalloc(size, gfp, PAGE_KERNEL);
|
||||
dmab->addr = 0;
|
||||
break;
|
||||
#ifdef CONFIG_HAS_DMA
|
||||
|
@ -215,6 +229,9 @@ void snd_dma_free_pages(struct snd_dma_buffer *dmab)
|
|||
case SNDRV_DMA_TYPE_CONTINUOUS:
|
||||
free_pages_exact(dmab->area, dmab->bytes);
|
||||
break;
|
||||
case SNDRV_DMA_TYPE_VMALLOC:
|
||||
vfree(dmab->area);
|
||||
break;
|
||||
#ifdef CONFIG_HAS_DMA
|
||||
#ifdef CONFIG_GENERIC_ALLOCATOR
|
||||
case SNDRV_DMA_TYPE_DEV_IRAM:
|
||||
|
|
|
@ -369,4 +369,87 @@ int snd_dmaengine_pcm_close_release_chan(struct snd_pcm_substream *substream)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_close_release_chan);
|
||||
|
||||
/**
|
||||
* snd_dmaengine_pcm_refine_runtime_hwparams - Refine runtime hw params
|
||||
* @substream: PCM substream
|
||||
* @dma_data: DAI DMA data
|
||||
* @hw: PCM hw params
|
||||
* @chan: DMA channel to use for data transfers
|
||||
*
|
||||
* Returns 0 on success, a negative error code otherwise.
|
||||
*
|
||||
* This function will query DMA capability, then refine the pcm hardware
|
||||
* parameters.
|
||||
*/
|
||||
int snd_dmaengine_pcm_refine_runtime_hwparams(
|
||||
struct snd_pcm_substream *substream,
|
||||
struct snd_dmaengine_dai_dma_data *dma_data,
|
||||
struct snd_pcm_hardware *hw,
|
||||
struct dma_chan *chan)
|
||||
{
|
||||
struct dma_slave_caps dma_caps;
|
||||
u32 addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) |
|
||||
BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) |
|
||||
BIT(DMA_SLAVE_BUSWIDTH_4_BYTES);
|
||||
snd_pcm_format_t i;
|
||||
int ret = 0;
|
||||
|
||||
if (!hw || !chan || !dma_data)
|
||||
return -EINVAL;
|
||||
|
||||
ret = dma_get_slave_caps(chan, &dma_caps);
|
||||
if (ret == 0) {
|
||||
if (dma_caps.cmd_pause && dma_caps.cmd_resume)
|
||||
hw->info |= SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME;
|
||||
if (dma_caps.residue_granularity <= DMA_RESIDUE_GRANULARITY_SEGMENT)
|
||||
hw->info |= SNDRV_PCM_INFO_BATCH;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
addr_widths = dma_caps.dst_addr_widths;
|
||||
else
|
||||
addr_widths = dma_caps.src_addr_widths;
|
||||
}
|
||||
|
||||
/*
|
||||
* If SND_DMAENGINE_PCM_DAI_FLAG_PACK is set keep
|
||||
* hw.formats set to 0, meaning no restrictions are in place.
|
||||
* In this case it's the responsibility of the DAI driver to
|
||||
* provide the supported format information.
|
||||
*/
|
||||
if (!(dma_data->flags & SND_DMAENGINE_PCM_DAI_FLAG_PACK))
|
||||
/*
|
||||
* Prepare formats mask for valid/allowed sample types. If the
|
||||
* dma does not have support for the given physical word size,
|
||||
* it needs to be masked out so user space can not use the
|
||||
* format which produces corrupted audio.
|
||||
* In case the dma driver does not implement the slave_caps the
|
||||
* default assumption is that it supports 1, 2 and 4 bytes
|
||||
* widths.
|
||||
*/
|
||||
for (i = SNDRV_PCM_FORMAT_FIRST; i <= SNDRV_PCM_FORMAT_LAST; i++) {
|
||||
int bits = snd_pcm_format_physical_width(i);
|
||||
|
||||
/*
|
||||
* Enable only samples with DMA supported physical
|
||||
* widths
|
||||
*/
|
||||
switch (bits) {
|
||||
case 8:
|
||||
case 16:
|
||||
case 24:
|
||||
case 32:
|
||||
case 64:
|
||||
if (addr_widths & (1 << (bits / 8)))
|
||||
hw->formats |= pcm_format_to_bits(i);
|
||||
break;
|
||||
default:
|
||||
/* Unsupported types */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_refine_runtime_hwparams);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
|
@ -67,4 +67,11 @@ static inline void snd_pcm_timer_done(struct snd_pcm_substream *substream) {}
|
|||
void __snd_pcm_xrun(struct snd_pcm_substream *substream);
|
||||
void snd_pcm_group_init(struct snd_pcm_group *group);
|
||||
|
||||
#ifdef CONFIG_SND_DMA_SGBUF
|
||||
struct page *snd_pcm_sgbuf_ops_page(struct snd_pcm_substream *substream,
|
||||
unsigned long offset);
|
||||
#endif
|
||||
|
||||
#define PCM_RUNTIME_CHECK(sub) snd_BUG_ON(!(sub) || !(sub)->runtime)
|
||||
|
||||
#endif /* __SOUND_CORE_PCM_LOCAL_H */
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include <sound/pcm.h>
|
||||
#include <sound/info.h>
|
||||
#include <sound/initval.h>
|
||||
#include "pcm_local.h"
|
||||
|
||||
static int preallocate_dma = 1;
|
||||
module_param(preallocate_dma, int, 0444);
|
||||
|
@ -193,9 +194,15 @@ static inline void preallocate_info_init(struct snd_pcm_substream *substream)
|
|||
/*
|
||||
* pre-allocate the buffer and create a proc file for the substream
|
||||
*/
|
||||
static void snd_pcm_lib_preallocate_pages1(struct snd_pcm_substream *substream,
|
||||
size_t size, size_t max)
|
||||
static void preallocate_pages(struct snd_pcm_substream *substream,
|
||||
int type, struct device *data,
|
||||
size_t size, size_t max, bool managed)
|
||||
{
|
||||
if (snd_BUG_ON(substream->dma_buffer.dev.type))
|
||||
return;
|
||||
|
||||
substream->dma_buffer.dev.type = type;
|
||||
substream->dma_buffer.dev.dev = data;
|
||||
|
||||
if (size > 0 && preallocate_dma && substream->number < maximum_substreams)
|
||||
preallocate_pcm_pages(substream, size);
|
||||
|
@ -203,9 +210,25 @@ static void snd_pcm_lib_preallocate_pages1(struct snd_pcm_substream *substream,
|
|||
if (substream->dma_buffer.bytes > 0)
|
||||
substream->buffer_bytes_max = substream->dma_buffer.bytes;
|
||||
substream->dma_max = max;
|
||||
preallocate_info_init(substream);
|
||||
if (max > 0)
|
||||
preallocate_info_init(substream);
|
||||
if (managed)
|
||||
substream->managed_buffer_alloc = 1;
|
||||
}
|
||||
|
||||
static void preallocate_pages_for_all(struct snd_pcm *pcm, int type,
|
||||
void *data, size_t size, size_t max,
|
||||
bool managed)
|
||||
{
|
||||
struct snd_pcm_substream *substream;
|
||||
int stream;
|
||||
|
||||
for (stream = 0; stream < 2; stream++)
|
||||
for (substream = pcm->streams[stream].substream; substream;
|
||||
substream = substream->next)
|
||||
preallocate_pages(substream, type, data, size, max,
|
||||
managed);
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_pcm_lib_preallocate_pages - pre-allocation for the given DMA type
|
||||
|
@ -221,9 +244,7 @@ void snd_pcm_lib_preallocate_pages(struct snd_pcm_substream *substream,
|
|||
int type, struct device *data,
|
||||
size_t size, size_t max)
|
||||
{
|
||||
substream->dma_buffer.dev.type = type;
|
||||
substream->dma_buffer.dev.dev = data;
|
||||
snd_pcm_lib_preallocate_pages1(substream, size, max);
|
||||
preallocate_pages(substream, type, data, size, max, false);
|
||||
}
|
||||
EXPORT_SYMBOL(snd_pcm_lib_preallocate_pages);
|
||||
|
||||
|
@ -242,17 +263,57 @@ void snd_pcm_lib_preallocate_pages_for_all(struct snd_pcm *pcm,
|
|||
int type, void *data,
|
||||
size_t size, size_t max)
|
||||
{
|
||||
struct snd_pcm_substream *substream;
|
||||
int stream;
|
||||
|
||||
for (stream = 0; stream < 2; stream++)
|
||||
for (substream = pcm->streams[stream].substream; substream; substream = substream->next)
|
||||
snd_pcm_lib_preallocate_pages(substream, type, data, size, max);
|
||||
preallocate_pages_for_all(pcm, type, data, size, max, false);
|
||||
}
|
||||
EXPORT_SYMBOL(snd_pcm_lib_preallocate_pages_for_all);
|
||||
|
||||
#ifdef CONFIG_SND_DMA_SGBUF
|
||||
/**
|
||||
* snd_pcm_set_managed_buffer - set up buffer management for a substream
|
||||
* @substream: the pcm substream instance
|
||||
* @type: DMA type (SNDRV_DMA_TYPE_*)
|
||||
* @data: DMA type dependent data
|
||||
* @size: the requested pre-allocation size in bytes
|
||||
* @max: the max. allowed pre-allocation size
|
||||
*
|
||||
* Do pre-allocation for the given DMA buffer type, and set the managed
|
||||
* buffer allocation mode to the given substream.
|
||||
* In this mode, PCM core will allocate a buffer automatically before PCM
|
||||
* hw_params ops call, and release the buffer after PCM hw_free ops call
|
||||
* as well, so that the driver doesn't need to invoke the allocation and
|
||||
* the release explicitly in its callback.
|
||||
* When a buffer is actually allocated before the PCM hw_params call, it
|
||||
* turns on the runtime buffer_changed flag for drivers changing their h/w
|
||||
* parameters accordingly.
|
||||
*/
|
||||
void snd_pcm_set_managed_buffer(struct snd_pcm_substream *substream, int type,
|
||||
struct device *data, size_t size, size_t max)
|
||||
{
|
||||
preallocate_pages(substream, type, data, size, max, true);
|
||||
}
|
||||
EXPORT_SYMBOL(snd_pcm_set_managed_buffer);
|
||||
|
||||
/**
|
||||
* snd_pcm_set_managed_buffer_all - set up buffer management for all substreams
|
||||
* for all substreams
|
||||
* @pcm: the pcm instance
|
||||
* @type: DMA type (SNDRV_DMA_TYPE_*)
|
||||
* @data: DMA type dependent data
|
||||
* @size: the requested pre-allocation size in bytes
|
||||
* @max: the max. allowed pre-allocation size
|
||||
*
|
||||
* Do pre-allocation to all substreams of the given pcm for the specified DMA
|
||||
* type and size, and set the managed_buffer_alloc flag to each substream.
|
||||
*/
|
||||
void snd_pcm_set_managed_buffer_all(struct snd_pcm *pcm, int type,
|
||||
struct device *data,
|
||||
size_t size, size_t max)
|
||||
{
|
||||
preallocate_pages_for_all(pcm, type, data, size, max, true);
|
||||
}
|
||||
EXPORT_SYMBOL(snd_pcm_set_managed_buffer_all);
|
||||
|
||||
#ifdef CONFIG_SND_DMA_SGBUF
|
||||
/*
|
||||
* snd_pcm_sgbuf_ops_page - get the page struct at the given offset
|
||||
* @substream: the pcm substream instance
|
||||
* @offset: the buffer offset
|
||||
|
@ -270,7 +331,6 @@ struct page *snd_pcm_sgbuf_ops_page(struct snd_pcm_substream *substream, unsigne
|
|||
return NULL;
|
||||
return sgbuf->page_table[idx];
|
||||
}
|
||||
EXPORT_SYMBOL(snd_pcm_sgbuf_ops_page);
|
||||
#endif /* CONFIG_SND_DMA_SGBUF */
|
||||
|
||||
/**
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <linux/pm_qos.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/control.h>
|
||||
#include <sound/info.h>
|
||||
|
@ -177,6 +178,16 @@ void snd_pcm_stream_unlock_irqrestore(struct snd_pcm_substream *substream,
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(snd_pcm_stream_unlock_irqrestore);
|
||||
|
||||
/* Run PCM ioctl ops */
|
||||
static int snd_pcm_ops_ioctl(struct snd_pcm_substream *substream,
|
||||
unsigned cmd, void *arg)
|
||||
{
|
||||
if (substream->ops->ioctl)
|
||||
return substream->ops->ioctl(substream, cmd, arg);
|
||||
else
|
||||
return snd_pcm_lib_ioctl(substream, cmd, arg);
|
||||
}
|
||||
|
||||
int snd_pcm_info(struct snd_pcm_substream *substream, struct snd_pcm_info *info)
|
||||
{
|
||||
struct snd_pcm *pcm = substream->pcm;
|
||||
|
@ -222,7 +233,8 @@ static bool hw_support_mmap(struct snd_pcm_substream *substream)
|
|||
return false;
|
||||
|
||||
if (substream->ops->mmap ||
|
||||
substream->dma_buffer.dev.type != SNDRV_DMA_TYPE_DEV)
|
||||
(substream->dma_buffer.dev.type != SNDRV_DMA_TYPE_DEV &&
|
||||
substream->dma_buffer.dev.type != SNDRV_DMA_TYPE_DEV_UC))
|
||||
return true;
|
||||
|
||||
return dma_can_mmap(substream->dma_buffer.dev.dev);
|
||||
|
@ -446,8 +458,9 @@ static int fixup_unreferenced_params(struct snd_pcm_substream *substream,
|
|||
m = hw_param_mask_c(params, SNDRV_PCM_HW_PARAM_FORMAT);
|
||||
i = hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS);
|
||||
if (snd_mask_single(m) && snd_interval_single(i)) {
|
||||
err = substream->ops->ioctl(substream,
|
||||
SNDRV_PCM_IOCTL1_FIFO_SIZE, params);
|
||||
err = snd_pcm_ops_ioctl(substream,
|
||||
SNDRV_PCM_IOCTL1_FIFO_SIZE,
|
||||
params);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
@ -555,6 +568,17 @@ static inline void snd_pcm_timer_notify(struct snd_pcm_substream *substream,
|
|||
#endif
|
||||
}
|
||||
|
||||
static void snd_pcm_sync_stop(struct snd_pcm_substream *substream)
|
||||
{
|
||||
if (substream->runtime->stop_operating) {
|
||||
substream->runtime->stop_operating = false;
|
||||
if (substream->ops->sync_stop)
|
||||
substream->ops->sync_stop(substream);
|
||||
else if (substream->pcm->card->sync_irq > 0)
|
||||
synchronize_irq(substream->pcm->card->sync_irq);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_pcm_hw_param_choose - choose a configuration defined by @params
|
||||
* @pcm: PCM instance
|
||||
|
@ -647,6 +671,8 @@ static int snd_pcm_hw_params(struct snd_pcm_substream *substream,
|
|||
if (atomic_read(&substream->mmap_count))
|
||||
return -EBADFD;
|
||||
|
||||
snd_pcm_sync_stop(substream);
|
||||
|
||||
params->rmask = ~0U;
|
||||
err = snd_pcm_hw_refine(substream, params);
|
||||
if (err < 0)
|
||||
|
@ -660,6 +686,14 @@ static int snd_pcm_hw_params(struct snd_pcm_substream *substream,
|
|||
if (err < 0)
|
||||
goto _error;
|
||||
|
||||
if (substream->managed_buffer_alloc) {
|
||||
err = snd_pcm_lib_malloc_pages(substream,
|
||||
params_buffer_bytes(params));
|
||||
if (err < 0)
|
||||
goto _error;
|
||||
runtime->buffer_changed = err > 0;
|
||||
}
|
||||
|
||||
if (substream->ops->hw_params != NULL) {
|
||||
err = substream->ops->hw_params(substream, params);
|
||||
if (err < 0)
|
||||
|
@ -721,6 +755,8 @@ static int snd_pcm_hw_params(struct snd_pcm_substream *substream,
|
|||
snd_pcm_set_state(substream, SNDRV_PCM_STATE_OPEN);
|
||||
if (substream->ops->hw_free != NULL)
|
||||
substream->ops->hw_free(substream);
|
||||
if (substream->managed_buffer_alloc)
|
||||
snd_pcm_lib_free_pages(substream);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
@ -765,8 +801,11 @@ static int snd_pcm_hw_free(struct snd_pcm_substream *substream)
|
|||
snd_pcm_stream_unlock_irq(substream);
|
||||
if (atomic_read(&substream->mmap_count))
|
||||
return -EBADFD;
|
||||
snd_pcm_sync_stop(substream);
|
||||
if (substream->ops->hw_free)
|
||||
result = substream->ops->hw_free(substream);
|
||||
if (substream->managed_buffer_alloc)
|
||||
snd_pcm_lib_free_pages(substream);
|
||||
snd_pcm_set_state(substream, SNDRV_PCM_STATE_OPEN);
|
||||
pm_qos_remove_request(&substream->latency_pm_qos_req);
|
||||
return result;
|
||||
|
@ -957,7 +996,7 @@ static int snd_pcm_channel_info(struct snd_pcm_substream *substream,
|
|||
return -EINVAL;
|
||||
memset(info, 0, sizeof(*info));
|
||||
info->channel = channel;
|
||||
return substream->ops->ioctl(substream, SNDRV_PCM_IOCTL1_CHANNEL_INFO, info);
|
||||
return snd_pcm_ops_ioctl(substream, SNDRV_PCM_IOCTL1_CHANNEL_INFO, info);
|
||||
}
|
||||
|
||||
static int snd_pcm_channel_info_user(struct snd_pcm_substream *substream,
|
||||
|
@ -1288,6 +1327,7 @@ static void snd_pcm_post_stop(struct snd_pcm_substream *substream, int state)
|
|||
runtime->status->state = state;
|
||||
snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MSTOP);
|
||||
}
|
||||
runtime->stop_operating = true;
|
||||
wake_up(&runtime->sleep);
|
||||
wake_up(&runtime->tsleep);
|
||||
}
|
||||
|
@ -1564,6 +1604,7 @@ static void snd_pcm_post_resume(struct snd_pcm_substream *substream, int state)
|
|||
snd_pcm_trigger_tstamp(substream);
|
||||
runtime->status->state = runtime->status->suspended_state;
|
||||
snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MRESUME);
|
||||
snd_pcm_sync_stop(substream);
|
||||
}
|
||||
|
||||
static const struct action_ops snd_pcm_action_resume = {
|
||||
|
@ -1633,7 +1674,7 @@ static int snd_pcm_pre_reset(struct snd_pcm_substream *substream, int state)
|
|||
static int snd_pcm_do_reset(struct snd_pcm_substream *substream, int state)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
int err = substream->ops->ioctl(substream, SNDRV_PCM_IOCTL1_RESET, NULL);
|
||||
int err = snd_pcm_ops_ioctl(substream, SNDRV_PCM_IOCTL1_RESET, NULL);
|
||||
if (err < 0)
|
||||
return err;
|
||||
runtime->hw_ptr_base = 0;
|
||||
|
@ -1684,6 +1725,7 @@ static int snd_pcm_pre_prepare(struct snd_pcm_substream *substream,
|
|||
static int snd_pcm_do_prepare(struct snd_pcm_substream *substream, int state)
|
||||
{
|
||||
int err;
|
||||
snd_pcm_sync_stop(substream);
|
||||
err = substream->ops->prepare(substream);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
@ -3334,7 +3376,18 @@ static inline struct page *
|
|||
snd_pcm_default_page_ops(struct snd_pcm_substream *substream, unsigned long ofs)
|
||||
{
|
||||
void *vaddr = substream->runtime->dma_area + ofs;
|
||||
return virt_to_page(vaddr);
|
||||
|
||||
switch (substream->dma_buffer.dev.type) {
|
||||
#ifdef CONFIG_SND_DMA_SGBUF
|
||||
case SNDRV_DMA_TYPE_DEV_SG:
|
||||
case SNDRV_DMA_TYPE_DEV_UC_SG:
|
||||
return snd_pcm_sgbuf_ops_page(substream, ofs);
|
||||
#endif /* CONFIG_SND_DMA_SGBUF */
|
||||
case SNDRV_DMA_TYPE_VMALLOC:
|
||||
return vmalloc_to_page(vaddr);
|
||||
default:
|
||||
return virt_to_page(vaddr);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -3403,7 +3456,8 @@ int snd_pcm_lib_default_mmap(struct snd_pcm_substream *substream,
|
|||
#endif /* CONFIG_GENERIC_ALLOCATOR */
|
||||
#ifndef CONFIG_X86 /* for avoiding warnings arch/x86/mm/pat.c */
|
||||
if (IS_ENABLED(CONFIG_HAS_DMA) && !substream->ops->page &&
|
||||
substream->dma_buffer.dev.type == SNDRV_DMA_TYPE_DEV)
|
||||
(substream->dma_buffer.dev.type == SNDRV_DMA_TYPE_DEV ||
|
||||
substream->dma_buffer.dev.type == SNDRV_DMA_TYPE_DEV_UC))
|
||||
return dma_mmap_coherent(substream->dma_buffer.dev.dev,
|
||||
area,
|
||||
substream->runtime->dma_area,
|
||||
|
|
|
@ -272,7 +272,13 @@ int snd_seq_timer_open(struct snd_seq_queue *q)
|
|||
return -EINVAL;
|
||||
if (tmr->alsa_id.dev_class != SNDRV_TIMER_CLASS_SLAVE)
|
||||
tmr->alsa_id.dev_sclass = SNDRV_TIMER_SCLASS_SEQUENCER;
|
||||
err = snd_timer_open(&t, str, &tmr->alsa_id, q->queue);
|
||||
t = snd_timer_instance_new(str);
|
||||
if (!t)
|
||||
return -ENOMEM;
|
||||
t->callback = snd_seq_timer_interrupt;
|
||||
t->callback_data = q;
|
||||
t->flags |= SNDRV_TIMER_IFLG_AUTO;
|
||||
err = snd_timer_open(t, &tmr->alsa_id, q->queue);
|
||||
if (err < 0 && tmr->alsa_id.dev_class != SNDRV_TIMER_CLASS_SLAVE) {
|
||||
if (tmr->alsa_id.dev_class != SNDRV_TIMER_CLASS_GLOBAL ||
|
||||
tmr->alsa_id.device != SNDRV_TIMER_GLOBAL_SYSTEM) {
|
||||
|
@ -282,16 +288,14 @@ int snd_seq_timer_open(struct snd_seq_queue *q)
|
|||
tid.dev_sclass = SNDRV_TIMER_SCLASS_SEQUENCER;
|
||||
tid.card = -1;
|
||||
tid.device = SNDRV_TIMER_GLOBAL_SYSTEM;
|
||||
err = snd_timer_open(&t, str, &tid, q->queue);
|
||||
err = snd_timer_open(t, &tid, q->queue);
|
||||
}
|
||||
}
|
||||
if (err < 0) {
|
||||
pr_err("ALSA: seq fatal error: cannot create timer (%i)\n", err);
|
||||
snd_timer_instance_free(t);
|
||||
return err;
|
||||
}
|
||||
t->callback = snd_seq_timer_interrupt;
|
||||
t->callback_data = q;
|
||||
t->flags |= SNDRV_TIMER_IFLG_AUTO;
|
||||
spin_lock_irq(&tmr->lock);
|
||||
tmr->timeri = t;
|
||||
spin_unlock_irq(&tmr->lock);
|
||||
|
@ -310,8 +314,10 @@ int snd_seq_timer_close(struct snd_seq_queue *q)
|
|||
t = tmr->timeri;
|
||||
tmr->timeri = NULL;
|
||||
spin_unlock_irq(&tmr->lock);
|
||||
if (t)
|
||||
if (t) {
|
||||
snd_timer_close(t);
|
||||
snd_timer_instance_free(t);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -74,6 +74,9 @@ static LIST_HEAD(snd_timer_slave_list);
|
|||
/* lock for slave active lists */
|
||||
static DEFINE_SPINLOCK(slave_active_lock);
|
||||
|
||||
#define MAX_SLAVE_INSTANCES 1000
|
||||
static int num_slaves;
|
||||
|
||||
static DEFINE_MUTEX(register_mutex);
|
||||
|
||||
static int snd_timer_free(struct snd_timer *timer);
|
||||
|
@ -85,12 +88,11 @@ static void snd_timer_reschedule(struct snd_timer * timer, unsigned long ticks_l
|
|||
|
||||
/*
|
||||
* create a timer instance with the given owner string.
|
||||
* when timer is not NULL, increments the module counter
|
||||
*/
|
||||
static struct snd_timer_instance *snd_timer_instance_new(char *owner,
|
||||
struct snd_timer *timer)
|
||||
struct snd_timer_instance *snd_timer_instance_new(const char *owner)
|
||||
{
|
||||
struct snd_timer_instance *timeri;
|
||||
|
||||
timeri = kzalloc(sizeof(*timeri), GFP_KERNEL);
|
||||
if (timeri == NULL)
|
||||
return NULL;
|
||||
|
@ -105,15 +107,20 @@ static struct snd_timer_instance *snd_timer_instance_new(char *owner,
|
|||
INIT_LIST_HEAD(&timeri->slave_list_head);
|
||||
INIT_LIST_HEAD(&timeri->slave_active_head);
|
||||
|
||||
timeri->timer = timer;
|
||||
if (timer && !try_module_get(timer->module)) {
|
||||
kfree(timeri->owner);
|
||||
kfree(timeri);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return timeri;
|
||||
}
|
||||
EXPORT_SYMBOL(snd_timer_instance_new);
|
||||
|
||||
void snd_timer_instance_free(struct snd_timer_instance *timeri)
|
||||
{
|
||||
if (timeri) {
|
||||
if (timeri->private_free)
|
||||
timeri->private_free(timeri);
|
||||
kfree(timeri->owner);
|
||||
kfree(timeri);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(snd_timer_instance_free);
|
||||
|
||||
/*
|
||||
* find a timer instance from the given timer id
|
||||
|
@ -160,6 +167,28 @@ static void snd_timer_request(struct snd_timer_id *tid)
|
|||
|
||||
#endif
|
||||
|
||||
/* move the slave if it belongs to the master; return 1 if match */
|
||||
static int check_matching_master_slave(struct snd_timer_instance *master,
|
||||
struct snd_timer_instance *slave)
|
||||
{
|
||||
if (slave->slave_class != master->slave_class ||
|
||||
slave->slave_id != master->slave_id)
|
||||
return 0;
|
||||
if (master->timer->num_instances >= master->timer->max_instances)
|
||||
return -EBUSY;
|
||||
list_move_tail(&slave->open_list, &master->slave_list_head);
|
||||
master->timer->num_instances++;
|
||||
spin_lock_irq(&slave_active_lock);
|
||||
spin_lock(&master->timer->lock);
|
||||
slave->master = master;
|
||||
slave->timer = master->timer;
|
||||
if (slave->flags & SNDRV_TIMER_IFLG_RUNNING)
|
||||
list_add_tail(&slave->active_list, &master->slave_active_head);
|
||||
spin_unlock(&master->timer->lock);
|
||||
spin_unlock_irq(&slave_active_lock);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* look for a master instance matching with the slave id of the given slave.
|
||||
* when found, relink the open_link of the slave.
|
||||
|
@ -170,27 +199,18 @@ static int snd_timer_check_slave(struct snd_timer_instance *slave)
|
|||
{
|
||||
struct snd_timer *timer;
|
||||
struct snd_timer_instance *master;
|
||||
int err = 0;
|
||||
|
||||
/* FIXME: it's really dumb to look up all entries.. */
|
||||
list_for_each_entry(timer, &snd_timer_list, device_list) {
|
||||
list_for_each_entry(master, &timer->open_list_head, open_list) {
|
||||
if (slave->slave_class == master->slave_class &&
|
||||
slave->slave_id == master->slave_id) {
|
||||
if (master->timer->num_instances >=
|
||||
master->timer->max_instances)
|
||||
return -EBUSY;
|
||||
list_move_tail(&slave->open_list,
|
||||
&master->slave_list_head);
|
||||
master->timer->num_instances++;
|
||||
spin_lock_irq(&slave_active_lock);
|
||||
slave->master = master;
|
||||
slave->timer = master->timer;
|
||||
spin_unlock_irq(&slave_active_lock);
|
||||
return 0;
|
||||
}
|
||||
err = check_matching_master_slave(master, slave);
|
||||
if (err != 0) /* match found or error */
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
out:
|
||||
return err < 0 ? err : 0;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -202,43 +222,29 @@ static int snd_timer_check_slave(struct snd_timer_instance *slave)
|
|||
static int snd_timer_check_master(struct snd_timer_instance *master)
|
||||
{
|
||||
struct snd_timer_instance *slave, *tmp;
|
||||
int err = 0;
|
||||
|
||||
/* check all pending slaves */
|
||||
list_for_each_entry_safe(slave, tmp, &snd_timer_slave_list, open_list) {
|
||||
if (slave->slave_class == master->slave_class &&
|
||||
slave->slave_id == master->slave_id) {
|
||||
if (master->timer->num_instances >=
|
||||
master->timer->max_instances)
|
||||
return -EBUSY;
|
||||
list_move_tail(&slave->open_list, &master->slave_list_head);
|
||||
master->timer->num_instances++;
|
||||
spin_lock_irq(&slave_active_lock);
|
||||
spin_lock(&master->timer->lock);
|
||||
slave->master = master;
|
||||
slave->timer = master->timer;
|
||||
if (slave->flags & SNDRV_TIMER_IFLG_RUNNING)
|
||||
list_add_tail(&slave->active_list,
|
||||
&master->slave_active_head);
|
||||
spin_unlock(&master->timer->lock);
|
||||
spin_unlock_irq(&slave_active_lock);
|
||||
}
|
||||
err = check_matching_master_slave(master, slave);
|
||||
if (err < 0)
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
return err < 0 ? err : 0;
|
||||
}
|
||||
|
||||
static int snd_timer_close_locked(struct snd_timer_instance *timeri,
|
||||
struct device **card_devp_to_put);
|
||||
static void snd_timer_close_locked(struct snd_timer_instance *timeri,
|
||||
struct device **card_devp_to_put);
|
||||
|
||||
/*
|
||||
* open a timer instance
|
||||
* when opening a master, the slave id must be here given.
|
||||
*/
|
||||
int snd_timer_open(struct snd_timer_instance **ti,
|
||||
char *owner, struct snd_timer_id *tid,
|
||||
int snd_timer_open(struct snd_timer_instance *timeri,
|
||||
struct snd_timer_id *tid,
|
||||
unsigned int slave_id)
|
||||
{
|
||||
struct snd_timer *timer;
|
||||
struct snd_timer_instance *timeri = NULL;
|
||||
struct device *card_dev_to_put = NULL;
|
||||
int err;
|
||||
|
||||
|
@ -252,21 +258,17 @@ int snd_timer_open(struct snd_timer_instance **ti,
|
|||
err = -EINVAL;
|
||||
goto unlock;
|
||||
}
|
||||
timeri = snd_timer_instance_new(owner, NULL);
|
||||
if (!timeri) {
|
||||
err = -ENOMEM;
|
||||
if (num_slaves >= MAX_SLAVE_INSTANCES) {
|
||||
err = -EBUSY;
|
||||
goto unlock;
|
||||
}
|
||||
timeri->slave_class = tid->dev_sclass;
|
||||
timeri->slave_id = tid->device;
|
||||
timeri->flags |= SNDRV_TIMER_IFLG_SLAVE;
|
||||
list_add_tail(&timeri->open_list, &snd_timer_slave_list);
|
||||
num_slaves++;
|
||||
err = snd_timer_check_slave(timeri);
|
||||
if (err < 0) {
|
||||
snd_timer_close_locked(timeri, &card_dev_to_put);
|
||||
timeri = NULL;
|
||||
}
|
||||
goto unlock;
|
||||
goto list_added;
|
||||
}
|
||||
|
||||
/* open a master instance */
|
||||
|
@ -296,45 +298,40 @@ int snd_timer_open(struct snd_timer_instance **ti,
|
|||
err = -EBUSY;
|
||||
goto unlock;
|
||||
}
|
||||
timeri = snd_timer_instance_new(owner, timer);
|
||||
if (!timeri) {
|
||||
err = -ENOMEM;
|
||||
if (!try_module_get(timer->module)) {
|
||||
err = -EBUSY;
|
||||
goto unlock;
|
||||
}
|
||||
/* take a card refcount for safe disconnection */
|
||||
if (timer->card)
|
||||
if (timer->card) {
|
||||
get_device(&timer->card->card_dev);
|
||||
timeri->slave_class = tid->dev_sclass;
|
||||
timeri->slave_id = slave_id;
|
||||
card_dev_to_put = &timer->card->card_dev;
|
||||
}
|
||||
|
||||
if (list_empty(&timer->open_list_head) && timer->hw.open) {
|
||||
err = timer->hw.open(timer);
|
||||
if (err) {
|
||||
kfree(timeri->owner);
|
||||
kfree(timeri);
|
||||
timeri = NULL;
|
||||
|
||||
if (timer->card)
|
||||
card_dev_to_put = &timer->card->card_dev;
|
||||
module_put(timer->module);
|
||||
goto unlock;
|
||||
}
|
||||
}
|
||||
|
||||
timeri->timer = timer;
|
||||
timeri->slave_class = tid->dev_sclass;
|
||||
timeri->slave_id = slave_id;
|
||||
|
||||
list_add_tail(&timeri->open_list, &timer->open_list_head);
|
||||
timer->num_instances++;
|
||||
err = snd_timer_check_master(timeri);
|
||||
if (err < 0) {
|
||||
list_added:
|
||||
if (err < 0)
|
||||
snd_timer_close_locked(timeri, &card_dev_to_put);
|
||||
timeri = NULL;
|
||||
}
|
||||
|
||||
unlock:
|
||||
mutex_unlock(®ister_mutex);
|
||||
/* put_device() is called after unlock for avoiding deadlock */
|
||||
if (card_dev_to_put)
|
||||
if (err < 0 && card_dev_to_put)
|
||||
put_device(card_dev_to_put);
|
||||
*ti = timeri;
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL(snd_timer_open);
|
||||
|
@ -343,8 +340,8 @@ EXPORT_SYMBOL(snd_timer_open);
|
|||
* close a timer instance
|
||||
* call this with register_mutex down.
|
||||
*/
|
||||
static int snd_timer_close_locked(struct snd_timer_instance *timeri,
|
||||
struct device **card_devp_to_put)
|
||||
static void snd_timer_close_locked(struct snd_timer_instance *timeri,
|
||||
struct device **card_devp_to_put)
|
||||
{
|
||||
struct snd_timer *timer = timeri->timer;
|
||||
struct snd_timer_instance *slave, *tmp;
|
||||
|
@ -355,7 +352,11 @@ static int snd_timer_close_locked(struct snd_timer_instance *timeri,
|
|||
spin_unlock_irq(&timer->lock);
|
||||
}
|
||||
|
||||
list_del(&timeri->open_list);
|
||||
if (!list_empty(&timeri->open_list)) {
|
||||
list_del_init(&timeri->open_list);
|
||||
if (timeri->flags & SNDRV_TIMER_IFLG_SLAVE)
|
||||
num_slaves--;
|
||||
}
|
||||
|
||||
/* force to stop the timer */
|
||||
snd_timer_stop(timeri);
|
||||
|
@ -374,6 +375,7 @@ static int snd_timer_close_locked(struct snd_timer_instance *timeri,
|
|||
/* remove slave links */
|
||||
spin_lock_irq(&slave_active_lock);
|
||||
spin_lock(&timer->lock);
|
||||
timeri->timer = NULL;
|
||||
list_for_each_entry_safe(slave, tmp, &timeri->slave_list_head,
|
||||
open_list) {
|
||||
list_move_tail(&slave->open_list, &snd_timer_slave_list);
|
||||
|
@ -391,11 +393,6 @@ static int snd_timer_close_locked(struct snd_timer_instance *timeri,
|
|||
timer = NULL;
|
||||
}
|
||||
|
||||
if (timeri->private_free)
|
||||
timeri->private_free(timeri);
|
||||
kfree(timeri->owner);
|
||||
kfree(timeri);
|
||||
|
||||
if (timer) {
|
||||
if (list_empty(&timer->open_list_head) && timer->hw.close)
|
||||
timer->hw.close(timer);
|
||||
|
@ -404,28 +401,24 @@ static int snd_timer_close_locked(struct snd_timer_instance *timeri,
|
|||
*card_devp_to_put = &timer->card->card_dev;
|
||||
module_put(timer->module);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* close a timer instance
|
||||
*/
|
||||
int snd_timer_close(struct snd_timer_instance *timeri)
|
||||
void snd_timer_close(struct snd_timer_instance *timeri)
|
||||
{
|
||||
struct device *card_dev_to_put = NULL;
|
||||
int err;
|
||||
|
||||
if (snd_BUG_ON(!timeri))
|
||||
return -ENXIO;
|
||||
return;
|
||||
|
||||
mutex_lock(®ister_mutex);
|
||||
err = snd_timer_close_locked(timeri, &card_dev_to_put);
|
||||
snd_timer_close_locked(timeri, &card_dev_to_put);
|
||||
mutex_unlock(®ister_mutex);
|
||||
/* put_device() is called after unlock for avoiding deadlock */
|
||||
if (card_dev_to_put)
|
||||
put_device(card_dev_to_put);
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL(snd_timer_close);
|
||||
|
||||
|
@ -1474,8 +1467,10 @@ static int snd_timer_user_release(struct inode *inode, struct file *file)
|
|||
tu = file->private_data;
|
||||
file->private_data = NULL;
|
||||
mutex_lock(&tu->ioctl_lock);
|
||||
if (tu->timeri)
|
||||
if (tu->timeri) {
|
||||
snd_timer_close(tu->timeri);
|
||||
snd_timer_instance_free(tu->timeri);
|
||||
}
|
||||
mutex_unlock(&tu->ioctl_lock);
|
||||
kfree(tu->queue);
|
||||
kfree(tu->tqueue);
|
||||
|
@ -1716,6 +1711,7 @@ static int snd_timer_user_tselect(struct file *file,
|
|||
tu = file->private_data;
|
||||
if (tu->timeri) {
|
||||
snd_timer_close(tu->timeri);
|
||||
snd_timer_instance_free(tu->timeri);
|
||||
tu->timeri = NULL;
|
||||
}
|
||||
if (copy_from_user(&tselect, _tselect, sizeof(tselect))) {
|
||||
|
@ -1725,9 +1721,11 @@ static int snd_timer_user_tselect(struct file *file,
|
|||
sprintf(str, "application %i", current->pid);
|
||||
if (tselect.id.dev_class != SNDRV_TIMER_CLASS_SLAVE)
|
||||
tselect.id.dev_sclass = SNDRV_TIMER_SCLASS_APPLICATION;
|
||||
err = snd_timer_open(&tu->timeri, str, &tselect.id, current->pid);
|
||||
if (err < 0)
|
||||
tu->timeri = snd_timer_instance_new(str);
|
||||
if (!tu->timeri) {
|
||||
err = -ENOMEM;
|
||||
goto __err;
|
||||
}
|
||||
|
||||
tu->timeri->flags |= SNDRV_TIMER_IFLG_FAST;
|
||||
tu->timeri->callback = tu->tread
|
||||
|
@ -1736,6 +1734,12 @@ static int snd_timer_user_tselect(struct file *file,
|
|||
tu->timeri->callback_data = (void *)tu;
|
||||
tu->timeri->disconnect = snd_timer_user_disconnect;
|
||||
|
||||
err = snd_timer_open(tu->timeri, &tselect.id, current->pid);
|
||||
if (err < 0) {
|
||||
snd_timer_instance_free(tu->timeri);
|
||||
tu->timeri = NULL;
|
||||
}
|
||||
|
||||
__err:
|
||||
return err;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
config SND_MPU401_UART
|
||||
tristate
|
||||
select SND_RAWMIDI
|
||||
tristate
|
||||
select SND_RAWMIDI
|
||||
|
||||
config SND_OPL3_LIB
|
||||
tristate
|
||||
|
@ -90,16 +90,17 @@ config SND_DUMMY
|
|||
will be called snd-dummy.
|
||||
|
||||
config SND_ALOOP
|
||||
tristate "Generic loopback driver (PCM)"
|
||||
select SND_PCM
|
||||
help
|
||||
Say 'Y' or 'M' to include support for the PCM loopback device.
|
||||
tristate "Generic loopback driver (PCM)"
|
||||
select SND_PCM
|
||||
select SND_TIMER
|
||||
help
|
||||
Say 'Y' or 'M' to include support for the PCM loopback device.
|
||||
This module returns played samples back to the user space using
|
||||
the standard ALSA PCM device. The devices are routed 0->1 and
|
||||
1->0, where first number is the playback PCM device and second
|
||||
1->0, where first number is the playback PCM device and second
|
||||
number is the capture device. Module creates two PCM devices and
|
||||
configured number of substreams (see the pcm_substreams module
|
||||
parameter).
|
||||
parameter).
|
||||
|
||||
The loopback device allows time sychronization with an external
|
||||
timing source using the time shift universal control (+-20%
|
||||
|
@ -142,12 +143,12 @@ config SND_MTS64
|
|||
select SND_RAWMIDI
|
||||
help
|
||||
The ESI Miditerminal 4140 is a 4 In 4 Out MIDI Interface with
|
||||
additional SMPTE Timecode capabilities for the parallel port.
|
||||
additional SMPTE Timecode capabilities for the parallel port.
|
||||
|
||||
Say 'Y' to include support for this device.
|
||||
|
||||
To compile this driver as a module, chose 'M' here: the module
|
||||
will be called snd-mts64.
|
||||
will be called snd-mts64.
|
||||
|
||||
config SND_SERIAL_U16550
|
||||
tristate "UART16550 serial MIDI driver"
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include <sound/pcm_params.h>
|
||||
#include <sound/info.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/timer.h>
|
||||
|
||||
MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
|
||||
MODULE_DESCRIPTION("A loopback soundcard");
|
||||
|
@ -41,6 +42,7 @@ static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
|
|||
static bool enable[SNDRV_CARDS] = {1, [1 ... (SNDRV_CARDS - 1)] = 0};
|
||||
static int pcm_substreams[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 8};
|
||||
static int pcm_notify[SNDRV_CARDS];
|
||||
static char *timer_source[SNDRV_CARDS];
|
||||
|
||||
module_param_array(index, int, NULL, 0444);
|
||||
MODULE_PARM_DESC(index, "Index value for loopback soundcard.");
|
||||
|
@ -52,11 +54,48 @@ module_param_array(pcm_substreams, int, NULL, 0444);
|
|||
MODULE_PARM_DESC(pcm_substreams, "PCM substreams # (1-8) for loopback driver.");
|
||||
module_param_array(pcm_notify, int, NULL, 0444);
|
||||
MODULE_PARM_DESC(pcm_notify, "Break capture when PCM format/rate/channels changes.");
|
||||
module_param_array(timer_source, charp, NULL, 0444);
|
||||
MODULE_PARM_DESC(timer_source, "Sound card name or number and device/subdevice number of timer to be used. Empty string for jiffies timer [default].");
|
||||
|
||||
#define NO_PITCH 100000
|
||||
|
||||
#define CABLE_VALID_PLAYBACK BIT(SNDRV_PCM_STREAM_PLAYBACK)
|
||||
#define CABLE_VALID_CAPTURE BIT(SNDRV_PCM_STREAM_CAPTURE)
|
||||
#define CABLE_VALID_BOTH (CABLE_VALID_PLAYBACK | CABLE_VALID_CAPTURE)
|
||||
|
||||
struct loopback_cable;
|
||||
struct loopback_pcm;
|
||||
|
||||
struct loopback_ops {
|
||||
/* optional
|
||||
* call in loopback->cable_lock
|
||||
*/
|
||||
int (*open)(struct loopback_pcm *dpcm);
|
||||
/* required
|
||||
* call in cable->lock
|
||||
*/
|
||||
int (*start)(struct loopback_pcm *dpcm);
|
||||
/* required
|
||||
* call in cable->lock
|
||||
*/
|
||||
int (*stop)(struct loopback_pcm *dpcm);
|
||||
/* optional */
|
||||
int (*stop_sync)(struct loopback_pcm *dpcm);
|
||||
/* optional */
|
||||
int (*close_substream)(struct loopback_pcm *dpcm);
|
||||
/* optional
|
||||
* call in loopback->cable_lock
|
||||
*/
|
||||
int (*close_cable)(struct loopback_pcm *dpcm);
|
||||
/* optional
|
||||
* call in cable->lock
|
||||
*/
|
||||
unsigned int (*pos_update)(struct loopback_cable *cable);
|
||||
/* optional */
|
||||
void (*dpcm_info)(struct loopback_pcm *dpcm,
|
||||
struct snd_info_buffer *buffer);
|
||||
};
|
||||
|
||||
struct loopback_cable {
|
||||
spinlock_t lock;
|
||||
struct loopback_pcm *streams[2];
|
||||
|
@ -65,6 +104,15 @@ struct loopback_cable {
|
|||
unsigned int valid;
|
||||
unsigned int running;
|
||||
unsigned int pause;
|
||||
/* timer specific */
|
||||
struct loopback_ops *ops;
|
||||
/* If sound timer is used */
|
||||
struct {
|
||||
int stream;
|
||||
struct snd_timer_id id;
|
||||
struct tasklet_struct event_tasklet;
|
||||
struct snd_timer_instance *instance;
|
||||
} snd_timer;
|
||||
};
|
||||
|
||||
struct loopback_setup {
|
||||
|
@ -85,6 +133,7 @@ struct loopback {
|
|||
struct loopback_cable *cables[MAX_PCM_SUBSTREAMS][2];
|
||||
struct snd_pcm *pcm[2];
|
||||
struct loopback_setup setup[MAX_PCM_SUBSTREAMS][2];
|
||||
const char *timer_source;
|
||||
};
|
||||
|
||||
struct loopback_pcm {
|
||||
|
@ -102,10 +151,13 @@ struct loopback_pcm {
|
|||
/* flags */
|
||||
unsigned int period_update_pending :1;
|
||||
/* timer stuff */
|
||||
unsigned int irq_pos; /* fractional IRQ position */
|
||||
unsigned int period_size_frac;
|
||||
unsigned int irq_pos; /* fractional IRQ position in jiffies
|
||||
* ticks
|
||||
*/
|
||||
unsigned int period_size_frac; /* period size in jiffies ticks */
|
||||
unsigned int last_drift;
|
||||
unsigned long last_jiffies;
|
||||
/* If jiffies timer is used */
|
||||
struct timer_list timer;
|
||||
};
|
||||
|
||||
|
@ -153,7 +205,7 @@ static inline unsigned int get_rate_shift(struct loopback_pcm *dpcm)
|
|||
}
|
||||
|
||||
/* call in cable->lock */
|
||||
static void loopback_timer_start(struct loopback_pcm *dpcm)
|
||||
static int loopback_jiffies_timer_start(struct loopback_pcm *dpcm)
|
||||
{
|
||||
unsigned long tick;
|
||||
unsigned int rate_shift = get_rate_shift(dpcm);
|
||||
|
@ -169,23 +221,102 @@ static void loopback_timer_start(struct loopback_pcm *dpcm)
|
|||
tick = dpcm->period_size_frac - dpcm->irq_pos;
|
||||
tick = (tick + dpcm->pcm_bps - 1) / dpcm->pcm_bps;
|
||||
mod_timer(&dpcm->timer, jiffies + tick);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* call in cable->lock */
|
||||
static inline void loopback_timer_stop(struct loopback_pcm *dpcm)
|
||||
static int loopback_snd_timer_start(struct loopback_pcm *dpcm)
|
||||
{
|
||||
struct loopback_cable *cable = dpcm->cable;
|
||||
int err;
|
||||
|
||||
/* Loopback device has to use same period as timer card. Therefore
|
||||
* wake up for each snd_pcm_period_elapsed() call of timer card.
|
||||
*/
|
||||
err = snd_timer_start(cable->snd_timer.instance, 1);
|
||||
if (err < 0) {
|
||||
/* do not report error if trying to start but already
|
||||
* running. For example called by opposite substream
|
||||
* of the same cable
|
||||
*/
|
||||
if (err == -EBUSY)
|
||||
return 0;
|
||||
|
||||
pcm_err(dpcm->substream->pcm,
|
||||
"snd_timer_start(%d,%d,%d) failed with %d",
|
||||
cable->snd_timer.id.card,
|
||||
cable->snd_timer.id.device,
|
||||
cable->snd_timer.id.subdevice,
|
||||
err);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/* call in cable->lock */
|
||||
static inline int loopback_jiffies_timer_stop(struct loopback_pcm *dpcm)
|
||||
{
|
||||
del_timer(&dpcm->timer);
|
||||
dpcm->timer.expires = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void loopback_timer_stop_sync(struct loopback_pcm *dpcm)
|
||||
/* call in cable->lock */
|
||||
static int loopback_snd_timer_stop(struct loopback_pcm *dpcm)
|
||||
{
|
||||
struct loopback_cable *cable = dpcm->cable;
|
||||
int err;
|
||||
|
||||
/* only stop if both devices (playback and capture) are not running */
|
||||
if (cable->running ^ cable->pause)
|
||||
return 0;
|
||||
|
||||
err = snd_timer_stop(cable->snd_timer.instance);
|
||||
if (err < 0) {
|
||||
pcm_err(dpcm->substream->pcm,
|
||||
"snd_timer_stop(%d,%d,%d) failed with %d",
|
||||
cable->snd_timer.id.card,
|
||||
cable->snd_timer.id.device,
|
||||
cable->snd_timer.id.subdevice,
|
||||
err);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static inline int loopback_jiffies_timer_stop_sync(struct loopback_pcm *dpcm)
|
||||
{
|
||||
del_timer_sync(&dpcm->timer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define CABLE_VALID_PLAYBACK (1 << SNDRV_PCM_STREAM_PLAYBACK)
|
||||
#define CABLE_VALID_CAPTURE (1 << SNDRV_PCM_STREAM_CAPTURE)
|
||||
#define CABLE_VALID_BOTH (CABLE_VALID_PLAYBACK|CABLE_VALID_CAPTURE)
|
||||
/* call in loopback->cable_lock */
|
||||
static int loopback_snd_timer_close_cable(struct loopback_pcm *dpcm)
|
||||
{
|
||||
struct loopback_cable *cable = dpcm->cable;
|
||||
|
||||
/* snd_timer was not opened */
|
||||
if (!cable->snd_timer.instance)
|
||||
return 0;
|
||||
|
||||
/* will only be called from free_cable() when other stream was
|
||||
* already closed. Other stream cannot be reopened as long as
|
||||
* loopback->cable_lock is locked. Therefore no need to lock
|
||||
* cable->lock;
|
||||
*/
|
||||
snd_timer_close(cable->snd_timer.instance);
|
||||
|
||||
/* wait till drain tasklet has finished if requested */
|
||||
tasklet_kill(&cable->snd_timer.event_tasklet);
|
||||
|
||||
snd_timer_instance_free(cable->snd_timer.instance);
|
||||
memset(&cable->snd_timer, 0, sizeof(cable->snd_timer));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int loopback_check_format(struct loopback_cable *cable, int stream)
|
||||
{
|
||||
|
@ -249,7 +380,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
|
|||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct loopback_pcm *dpcm = runtime->private_data;
|
||||
struct loopback_cable *cable = dpcm->cable;
|
||||
int err, stream = 1 << substream->stream;
|
||||
int err = 0, stream = 1 << substream->stream;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
|
@ -262,7 +393,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
|
|||
spin_lock(&cable->lock);
|
||||
cable->running |= stream;
|
||||
cable->pause &= ~stream;
|
||||
loopback_timer_start(dpcm);
|
||||
err = cable->ops->start(dpcm);
|
||||
spin_unlock(&cable->lock);
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
loopback_active_notify(dpcm);
|
||||
|
@ -271,7 +402,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
|
|||
spin_lock(&cable->lock);
|
||||
cable->running &= ~stream;
|
||||
cable->pause &= ~stream;
|
||||
loopback_timer_stop(dpcm);
|
||||
err = cable->ops->stop(dpcm);
|
||||
spin_unlock(&cable->lock);
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
loopback_active_notify(dpcm);
|
||||
|
@ -280,7 +411,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
|
|||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
spin_lock(&cable->lock);
|
||||
cable->pause |= stream;
|
||||
loopback_timer_stop(dpcm);
|
||||
err = cable->ops->stop(dpcm);
|
||||
spin_unlock(&cable->lock);
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
loopback_active_notify(dpcm);
|
||||
|
@ -290,7 +421,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
|
|||
spin_lock(&cable->lock);
|
||||
dpcm->last_jiffies = jiffies;
|
||||
cable->pause &= ~stream;
|
||||
loopback_timer_start(dpcm);
|
||||
err = cable->ops->start(dpcm);
|
||||
spin_unlock(&cable->lock);
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
loopback_active_notify(dpcm);
|
||||
|
@ -298,7 +429,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
|
|||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
return err;
|
||||
}
|
||||
|
||||
static void params_change(struct snd_pcm_substream *substream)
|
||||
|
@ -312,6 +443,13 @@ static void params_change(struct snd_pcm_substream *substream)
|
|||
cable->hw.rate_max = runtime->rate;
|
||||
cable->hw.channels_min = runtime->channels;
|
||||
cable->hw.channels_max = runtime->channels;
|
||||
|
||||
if (cable->snd_timer.instance) {
|
||||
cable->hw.period_bytes_min =
|
||||
frames_to_bytes(runtime, runtime->period_size);
|
||||
cable->hw.period_bytes_max = cable->hw.period_bytes_min;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static int loopback_prepare(struct snd_pcm_substream *substream)
|
||||
|
@ -319,9 +457,13 @@ static int loopback_prepare(struct snd_pcm_substream *substream)
|
|||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct loopback_pcm *dpcm = runtime->private_data;
|
||||
struct loopback_cable *cable = dpcm->cable;
|
||||
int bps, salign;
|
||||
int err, bps, salign;
|
||||
|
||||
loopback_timer_stop_sync(dpcm);
|
||||
if (cable->ops->stop_sync) {
|
||||
err = cable->ops->stop_sync(dpcm);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
salign = (snd_pcm_format_physical_width(runtime->format) *
|
||||
runtime->channels) / 8;
|
||||
|
@ -457,7 +599,8 @@ static inline void bytepos_finish(struct loopback_pcm *dpcm,
|
|||
}
|
||||
|
||||
/* call in cable->lock */
|
||||
static unsigned int loopback_pos_update(struct loopback_cable *cable)
|
||||
static unsigned int loopback_jiffies_timer_pos_update
|
||||
(struct loopback_cable *cable)
|
||||
{
|
||||
struct loopback_pcm *dpcm_play =
|
||||
cable->streams[SNDRV_PCM_STREAM_PLAYBACK];
|
||||
|
@ -510,14 +653,15 @@ static unsigned int loopback_pos_update(struct loopback_cable *cable)
|
|||
return running;
|
||||
}
|
||||
|
||||
static void loopback_timer_function(struct timer_list *t)
|
||||
static void loopback_jiffies_timer_function(struct timer_list *t)
|
||||
{
|
||||
struct loopback_pcm *dpcm = from_timer(dpcm, t, timer);
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&dpcm->cable->lock, flags);
|
||||
if (loopback_pos_update(dpcm->cable) & (1 << dpcm->substream->stream)) {
|
||||
loopback_timer_start(dpcm);
|
||||
if (loopback_jiffies_timer_pos_update(dpcm->cable) &
|
||||
(1 << dpcm->substream->stream)) {
|
||||
loopback_jiffies_timer_start(dpcm);
|
||||
if (dpcm->period_update_pending) {
|
||||
dpcm->period_update_pending = 0;
|
||||
spin_unlock_irqrestore(&dpcm->cable->lock, flags);
|
||||
|
@ -529,6 +673,193 @@ static void loopback_timer_function(struct timer_list *t)
|
|||
spin_unlock_irqrestore(&dpcm->cable->lock, flags);
|
||||
}
|
||||
|
||||
/* call in cable->lock */
|
||||
static int loopback_snd_timer_check_resolution(struct snd_pcm_runtime *runtime,
|
||||
unsigned long resolution)
|
||||
{
|
||||
if (resolution != runtime->timer_resolution) {
|
||||
struct loopback_pcm *dpcm = runtime->private_data;
|
||||
struct loopback_cable *cable = dpcm->cable;
|
||||
/* Worst case estimation of possible values for resolution
|
||||
* resolution <= (512 * 1024) frames / 8kHz in nsec
|
||||
* resolution <= 65.536.000.000 nsec
|
||||
*
|
||||
* period_size <= 65.536.000.000 nsec / 1000nsec/usec * 192kHz +
|
||||
* 500.000
|
||||
* period_size <= 12.582.912.000.000 <64bit
|
||||
* / 1.000.000 usec/sec
|
||||
*/
|
||||
snd_pcm_uframes_t period_size_usec =
|
||||
resolution / 1000 * runtime->rate;
|
||||
/* round to nearest sample rate */
|
||||
snd_pcm_uframes_t period_size =
|
||||
(period_size_usec + 500 * 1000) / (1000 * 1000);
|
||||
|
||||
pcm_err(dpcm->substream->pcm,
|
||||
"Period size (%lu frames) of loopback device is not corresponding to timer resolution (%lu nsec = %lu frames) of card timer %d,%d,%d. Use period size of %lu frames for loopback device.",
|
||||
runtime->period_size, resolution, period_size,
|
||||
cable->snd_timer.id.card,
|
||||
cable->snd_timer.id.device,
|
||||
cable->snd_timer.id.subdevice,
|
||||
period_size);
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void loopback_snd_timer_period_elapsed(struct loopback_cable *cable,
|
||||
int event,
|
||||
unsigned long resolution)
|
||||
{
|
||||
struct loopback_pcm *dpcm_play, *dpcm_capt;
|
||||
struct snd_pcm_substream *substream_play, *substream_capt;
|
||||
struct snd_pcm_runtime *valid_runtime;
|
||||
unsigned int running, elapsed_bytes;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&cable->lock, flags);
|
||||
running = cable->running ^ cable->pause;
|
||||
/* no need to do anything if no stream is running */
|
||||
if (!running) {
|
||||
spin_unlock_irqrestore(&cable->lock, flags);
|
||||
return;
|
||||
}
|
||||
|
||||
dpcm_play = cable->streams[SNDRV_PCM_STREAM_PLAYBACK];
|
||||
dpcm_capt = cable->streams[SNDRV_PCM_STREAM_CAPTURE];
|
||||
substream_play = (running & (1 << SNDRV_PCM_STREAM_PLAYBACK)) ?
|
||||
dpcm_play->substream : NULL;
|
||||
substream_capt = (running & (1 << SNDRV_PCM_STREAM_CAPTURE)) ?
|
||||
dpcm_capt->substream : NULL;
|
||||
|
||||
if (event == SNDRV_TIMER_EVENT_MSTOP) {
|
||||
if (!dpcm_play ||
|
||||
dpcm_play->substream->runtime->status->state !=
|
||||
SNDRV_PCM_STATE_DRAINING) {
|
||||
spin_unlock_irqrestore(&cable->lock, flags);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
valid_runtime = (running & (1 << SNDRV_PCM_STREAM_PLAYBACK)) ?
|
||||
dpcm_play->substream->runtime :
|
||||
dpcm_capt->substream->runtime;
|
||||
|
||||
/* resolution is only valid for SNDRV_TIMER_EVENT_TICK events */
|
||||
if (event == SNDRV_TIMER_EVENT_TICK) {
|
||||
/* The hardware rules guarantee that playback and capture period
|
||||
* are the same. Therefore only one device has to be checked
|
||||
* here.
|
||||
*/
|
||||
if (loopback_snd_timer_check_resolution(valid_runtime,
|
||||
resolution) < 0) {
|
||||
spin_unlock_irqrestore(&cable->lock, flags);
|
||||
if (substream_play)
|
||||
snd_pcm_stop_xrun(substream_play);
|
||||
if (substream_capt)
|
||||
snd_pcm_stop_xrun(substream_capt);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
elapsed_bytes = frames_to_bytes(valid_runtime,
|
||||
valid_runtime->period_size);
|
||||
/* The same timer interrupt is used for playback and capture device */
|
||||
if ((running & (1 << SNDRV_PCM_STREAM_PLAYBACK)) &&
|
||||
(running & (1 << SNDRV_PCM_STREAM_CAPTURE))) {
|
||||
copy_play_buf(dpcm_play, dpcm_capt, elapsed_bytes);
|
||||
bytepos_finish(dpcm_play, elapsed_bytes);
|
||||
bytepos_finish(dpcm_capt, elapsed_bytes);
|
||||
} else if (running & (1 << SNDRV_PCM_STREAM_PLAYBACK)) {
|
||||
bytepos_finish(dpcm_play, elapsed_bytes);
|
||||
} else if (running & (1 << SNDRV_PCM_STREAM_CAPTURE)) {
|
||||
clear_capture_buf(dpcm_capt, elapsed_bytes);
|
||||
bytepos_finish(dpcm_capt, elapsed_bytes);
|
||||
}
|
||||
spin_unlock_irqrestore(&cable->lock, flags);
|
||||
|
||||
if (substream_play)
|
||||
snd_pcm_period_elapsed(substream_play);
|
||||
if (substream_capt)
|
||||
snd_pcm_period_elapsed(substream_capt);
|
||||
}
|
||||
|
||||
static void loopback_snd_timer_function(struct snd_timer_instance *timeri,
|
||||
unsigned long resolution,
|
||||
unsigned long ticks)
|
||||
{
|
||||
struct loopback_cable *cable = timeri->callback_data;
|
||||
|
||||
loopback_snd_timer_period_elapsed(cable, SNDRV_TIMER_EVENT_TICK,
|
||||
resolution);
|
||||
}
|
||||
|
||||
static void loopback_snd_timer_tasklet(unsigned long arg)
|
||||
{
|
||||
struct snd_timer_instance *timeri = (struct snd_timer_instance *)arg;
|
||||
struct loopback_cable *cable = timeri->callback_data;
|
||||
|
||||
loopback_snd_timer_period_elapsed(cable, SNDRV_TIMER_EVENT_MSTOP, 0);
|
||||
}
|
||||
|
||||
static void loopback_snd_timer_event(struct snd_timer_instance *timeri,
|
||||
int event,
|
||||
struct timespec *tstamp,
|
||||
unsigned long resolution)
|
||||
{
|
||||
/* Do not lock cable->lock here because timer->lock is already hold.
|
||||
* There are other functions which first lock cable->lock and than
|
||||
* timer->lock e.g.
|
||||
* loopback_trigger()
|
||||
* spin_lock(&cable->lock)
|
||||
* loopback_snd_timer_start()
|
||||
* snd_timer_start()
|
||||
* spin_lock(&timer->lock)
|
||||
* Therefore when using the oposit order of locks here it could result
|
||||
* in a deadlock.
|
||||
*/
|
||||
|
||||
if (event == SNDRV_TIMER_EVENT_MSTOP) {
|
||||
struct loopback_cable *cable = timeri->callback_data;
|
||||
|
||||
/* sound card of the timer was stopped. Therefore there will not
|
||||
* be any further timer callbacks. Due to this forward audio
|
||||
* data from here if in draining state. When still in running
|
||||
* state the streaming will be aborted by the usual timeout. It
|
||||
* should not be aborted here because may be the timer sound
|
||||
* card does only a recovery and the timer is back soon.
|
||||
* This tasklet triggers loopback_snd_timer_tasklet()
|
||||
*/
|
||||
tasklet_schedule(&cable->snd_timer.event_tasklet);
|
||||
}
|
||||
}
|
||||
|
||||
static void loopback_jiffies_timer_dpcm_info(struct loopback_pcm *dpcm,
|
||||
struct snd_info_buffer *buffer)
|
||||
{
|
||||
snd_iprintf(buffer, " update_pending:\t%u\n",
|
||||
dpcm->period_update_pending);
|
||||
snd_iprintf(buffer, " irq_pos:\t\t%u\n", dpcm->irq_pos);
|
||||
snd_iprintf(buffer, " period_frac:\t%u\n", dpcm->period_size_frac);
|
||||
snd_iprintf(buffer, " last_jiffies:\t%lu (%lu)\n",
|
||||
dpcm->last_jiffies, jiffies);
|
||||
snd_iprintf(buffer, " timer_expires:\t%lu\n", dpcm->timer.expires);
|
||||
}
|
||||
|
||||
static void loopback_snd_timer_dpcm_info(struct loopback_pcm *dpcm,
|
||||
struct snd_info_buffer *buffer)
|
||||
{
|
||||
struct loopback_cable *cable = dpcm->cable;
|
||||
|
||||
snd_iprintf(buffer, " sound timer:\thw:%d,%d,%d\n",
|
||||
cable->snd_timer.id.card,
|
||||
cable->snd_timer.id.device,
|
||||
cable->snd_timer.id.subdevice);
|
||||
snd_iprintf(buffer, " timer open:\t\t%s\n",
|
||||
(cable->snd_timer.stream == SNDRV_PCM_STREAM_CAPTURE) ?
|
||||
"capture" : "playback");
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t loopback_pointer(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
|
@ -536,7 +867,8 @@ static snd_pcm_uframes_t loopback_pointer(struct snd_pcm_substream *substream)
|
|||
snd_pcm_uframes_t pos;
|
||||
|
||||
spin_lock(&dpcm->cable->lock);
|
||||
loopback_pos_update(dpcm->cable);
|
||||
if (dpcm->cable->ops->pos_update)
|
||||
dpcm->cable->ops->pos_update(dpcm->cable);
|
||||
pos = dpcm->buf_pos;
|
||||
spin_unlock(&dpcm->cable->lock);
|
||||
return bytes_to_frames(runtime, pos);
|
||||
|
@ -576,8 +908,7 @@ static void loopback_runtime_free(struct snd_pcm_runtime *runtime)
|
|||
static int loopback_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
return snd_pcm_lib_alloc_vmalloc_buffer(substream,
|
||||
params_buffer_bytes(params));
|
||||
return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
|
||||
}
|
||||
|
||||
static int loopback_hw_free(struct snd_pcm_substream *substream)
|
||||
|
@ -589,7 +920,7 @@ static int loopback_hw_free(struct snd_pcm_substream *substream)
|
|||
mutex_lock(&dpcm->loopback->cable_lock);
|
||||
cable->valid &= ~(1 << substream->stream);
|
||||
mutex_unlock(&dpcm->loopback->cable_lock);
|
||||
return snd_pcm_lib_free_vmalloc_buffer(substream);
|
||||
return snd_pcm_lib_free_pages(substream);
|
||||
}
|
||||
|
||||
static unsigned int get_cable_index(struct snd_pcm_substream *substream)
|
||||
|
@ -647,6 +978,23 @@ static int rule_channels(struct snd_pcm_hw_params *params,
|
|||
return snd_interval_refine(hw_param_interval(params, rule->var), &t);
|
||||
}
|
||||
|
||||
static int rule_period_bytes(struct snd_pcm_hw_params *params,
|
||||
struct snd_pcm_hw_rule *rule)
|
||||
{
|
||||
struct loopback_pcm *dpcm = rule->private;
|
||||
struct loopback_cable *cable = dpcm->cable;
|
||||
struct snd_interval t;
|
||||
|
||||
mutex_lock(&dpcm->loopback->cable_lock);
|
||||
t.min = cable->hw.period_bytes_min;
|
||||
t.max = cable->hw.period_bytes_max;
|
||||
mutex_unlock(&dpcm->loopback->cable_lock);
|
||||
t.openmin = 0;
|
||||
t.openmax = 0;
|
||||
t.integer = 0;
|
||||
return snd_interval_refine(hw_param_interval(params, rule->var), &t);
|
||||
}
|
||||
|
||||
static void free_cable(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct loopback *loopback = substream->private_data;
|
||||
|
@ -662,12 +1010,183 @@ static void free_cable(struct snd_pcm_substream *substream)
|
|||
cable->streams[substream->stream] = NULL;
|
||||
spin_unlock_irq(&cable->lock);
|
||||
} else {
|
||||
struct loopback_pcm *dpcm = substream->runtime->private_data;
|
||||
|
||||
if (cable->ops && cable->ops->close_cable && dpcm)
|
||||
cable->ops->close_cable(dpcm);
|
||||
/* free the cable */
|
||||
loopback->cables[substream->number][dev] = NULL;
|
||||
kfree(cable);
|
||||
}
|
||||
}
|
||||
|
||||
static int loopback_jiffies_timer_open(struct loopback_pcm *dpcm)
|
||||
{
|
||||
timer_setup(&dpcm->timer, loopback_jiffies_timer_function, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct loopback_ops loopback_jiffies_timer_ops = {
|
||||
.open = loopback_jiffies_timer_open,
|
||||
.start = loopback_jiffies_timer_start,
|
||||
.stop = loopback_jiffies_timer_stop,
|
||||
.stop_sync = loopback_jiffies_timer_stop_sync,
|
||||
.close_substream = loopback_jiffies_timer_stop_sync,
|
||||
.pos_update = loopback_jiffies_timer_pos_update,
|
||||
.dpcm_info = loopback_jiffies_timer_dpcm_info,
|
||||
};
|
||||
|
||||
static int loopback_parse_timer_id(const char *str,
|
||||
struct snd_timer_id *tid)
|
||||
{
|
||||
/* [<pref>:](<card name>|<card idx>)[{.,}<dev idx>[{.,}<subdev idx>]] */
|
||||
const char * const sep_dev = ".,";
|
||||
const char * const sep_pref = ":";
|
||||
const char *name = str;
|
||||
char *sep, save = '\0';
|
||||
int card_idx = 0, dev = 0, subdev = 0;
|
||||
int err;
|
||||
|
||||
sep = strpbrk(str, sep_pref);
|
||||
if (sep)
|
||||
name = sep + 1;
|
||||
sep = strpbrk(name, sep_dev);
|
||||
if (sep) {
|
||||
save = *sep;
|
||||
*sep = '\0';
|
||||
}
|
||||
err = kstrtoint(name, 0, &card_idx);
|
||||
if (err == -EINVAL) {
|
||||
/* Must be the name, not number */
|
||||
for (card_idx = 0; card_idx < snd_ecards_limit; card_idx++) {
|
||||
struct snd_card *card = snd_card_ref(card_idx);
|
||||
|
||||
if (card) {
|
||||
if (!strcmp(card->id, name))
|
||||
err = 0;
|
||||
snd_card_unref(card);
|
||||
}
|
||||
if (!err)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (sep) {
|
||||
*sep = save;
|
||||
if (!err) {
|
||||
char *sep2, save2 = '\0';
|
||||
|
||||
sep2 = strpbrk(sep + 1, sep_dev);
|
||||
if (sep2) {
|
||||
save2 = *sep2;
|
||||
*sep2 = '\0';
|
||||
}
|
||||
err = kstrtoint(sep + 1, 0, &dev);
|
||||
if (sep2) {
|
||||
*sep2 = save2;
|
||||
if (!err)
|
||||
err = kstrtoint(sep2 + 1, 0, &subdev);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!err && tid) {
|
||||
tid->card = card_idx;
|
||||
tid->device = dev;
|
||||
tid->subdevice = subdev;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
/* call in loopback->cable_lock */
|
||||
static int loopback_snd_timer_open(struct loopback_pcm *dpcm)
|
||||
{
|
||||
int err = 0;
|
||||
struct snd_timer_id tid = {
|
||||
.dev_class = SNDRV_TIMER_CLASS_PCM,
|
||||
.dev_sclass = SNDRV_TIMER_SCLASS_APPLICATION,
|
||||
};
|
||||
struct snd_timer_instance *timeri;
|
||||
struct loopback_cable *cable = dpcm->cable;
|
||||
|
||||
/* check if timer was already opened. It is only opened once
|
||||
* per playback and capture subdevice (aka cable).
|
||||
*/
|
||||
if (cable->snd_timer.instance)
|
||||
goto exit;
|
||||
|
||||
err = loopback_parse_timer_id(dpcm->loopback->timer_source, &tid);
|
||||
if (err < 0) {
|
||||
pcm_err(dpcm->substream->pcm,
|
||||
"Parsing timer source \'%s\' failed with %d",
|
||||
dpcm->loopback->timer_source, err);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
cable->snd_timer.stream = dpcm->substream->stream;
|
||||
cable->snd_timer.id = tid;
|
||||
|
||||
timeri = snd_timer_instance_new(dpcm->loopback->card->id);
|
||||
if (!timeri) {
|
||||
err = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
/* The callback has to be called from another tasklet. If
|
||||
* SNDRV_TIMER_IFLG_FAST is specified it will be called from the
|
||||
* snd_pcm_period_elapsed() call of the selected sound card.
|
||||
* snd_pcm_period_elapsed() helds snd_pcm_stream_lock_irqsave().
|
||||
* Due to our callback loopback_snd_timer_function() also calls
|
||||
* snd_pcm_period_elapsed() which calls snd_pcm_stream_lock_irqsave().
|
||||
* This would end up in a dead lock.
|
||||
*/
|
||||
timeri->flags |= SNDRV_TIMER_IFLG_AUTO;
|
||||
timeri->callback = loopback_snd_timer_function;
|
||||
timeri->callback_data = (void *)cable;
|
||||
timeri->ccallback = loopback_snd_timer_event;
|
||||
|
||||
/* initialise a tasklet used for draining */
|
||||
tasklet_init(&cable->snd_timer.event_tasklet,
|
||||
loopback_snd_timer_tasklet, (unsigned long)timeri);
|
||||
|
||||
/* The mutex loopback->cable_lock is kept locked.
|
||||
* Therefore snd_timer_open() cannot be called a second time
|
||||
* by the other device of the same cable.
|
||||
* Therefore the following issue cannot happen:
|
||||
* [proc1] Call loopback_timer_open() ->
|
||||
* Unlock cable->lock for snd_timer_close/open() call
|
||||
* [proc2] Call loopback_timer_open() -> snd_timer_open(),
|
||||
* snd_timer_start()
|
||||
* [proc1] Call snd_timer_open() and overwrite running timer
|
||||
* instance
|
||||
*/
|
||||
err = snd_timer_open(timeri, &cable->snd_timer.id, current->pid);
|
||||
if (err < 0) {
|
||||
pcm_err(dpcm->substream->pcm,
|
||||
"snd_timer_open (%d,%d,%d) failed with %d",
|
||||
cable->snd_timer.id.card,
|
||||
cable->snd_timer.id.device,
|
||||
cable->snd_timer.id.subdevice,
|
||||
err);
|
||||
snd_timer_instance_free(timeri);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
cable->snd_timer.instance = timeri;
|
||||
|
||||
exit:
|
||||
return err;
|
||||
}
|
||||
|
||||
/* stop_sync() is not required for sound timer because it does not need to be
|
||||
* restarted in loopback_prepare() on Xrun recovery
|
||||
*/
|
||||
static struct loopback_ops loopback_snd_timer_ops = {
|
||||
.open = loopback_snd_timer_open,
|
||||
.start = loopback_snd_timer_start,
|
||||
.stop = loopback_snd_timer_stop,
|
||||
.close_cable = loopback_snd_timer_close_cable,
|
||||
.dpcm_info = loopback_snd_timer_dpcm_info,
|
||||
};
|
||||
|
||||
static int loopback_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
|
@ -685,7 +1204,6 @@ static int loopback_open(struct snd_pcm_substream *substream)
|
|||
}
|
||||
dpcm->loopback = loopback;
|
||||
dpcm->substream = substream;
|
||||
timer_setup(&dpcm->timer, loopback_timer_function, 0);
|
||||
|
||||
cable = loopback->cables[substream->number][dev];
|
||||
if (!cable) {
|
||||
|
@ -696,9 +1214,20 @@ static int loopback_open(struct snd_pcm_substream *substream)
|
|||
}
|
||||
spin_lock_init(&cable->lock);
|
||||
cable->hw = loopback_pcm_hardware;
|
||||
if (loopback->timer_source)
|
||||
cable->ops = &loopback_snd_timer_ops;
|
||||
else
|
||||
cable->ops = &loopback_jiffies_timer_ops;
|
||||
loopback->cables[substream->number][dev] = cable;
|
||||
}
|
||||
dpcm->cable = cable;
|
||||
runtime->private_data = dpcm;
|
||||
|
||||
if (cable->ops->open) {
|
||||
err = cable->ops->open(dpcm);
|
||||
if (err < 0)
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
|
||||
|
||||
|
@ -724,7 +1253,22 @@ static int loopback_open(struct snd_pcm_substream *substream)
|
|||
if (err < 0)
|
||||
goto unlock;
|
||||
|
||||
runtime->private_data = dpcm;
|
||||
/* In case of sound timer the period time of both devices of the same
|
||||
* loop has to be the same.
|
||||
* This rule only takes effect if a sound timer was chosen
|
||||
*/
|
||||
if (cable->snd_timer.instance) {
|
||||
err = snd_pcm_hw_rule_add(runtime, 0,
|
||||
SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
|
||||
rule_period_bytes, dpcm,
|
||||
SNDRV_PCM_HW_PARAM_PERIOD_BYTES, -1);
|
||||
if (err < 0)
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
/* loopback_runtime_free() has not to be called if kfree(dpcm) was
|
||||
* already called here. Otherwise it will end up with a double free.
|
||||
*/
|
||||
runtime->private_free = loopback_runtime_free;
|
||||
if (get_notify(dpcm))
|
||||
runtime->hw = loopback_pcm_hardware;
|
||||
|
@ -748,12 +1292,14 @@ static int loopback_close(struct snd_pcm_substream *substream)
|
|||
{
|
||||
struct loopback *loopback = substream->private_data;
|
||||
struct loopback_pcm *dpcm = substream->runtime->private_data;
|
||||
int err = 0;
|
||||
|
||||
loopback_timer_stop_sync(dpcm);
|
||||
if (dpcm->cable->ops->close_substream)
|
||||
err = dpcm->cable->ops->close_substream(dpcm);
|
||||
mutex_lock(&loopback->cable_lock);
|
||||
free_cable(substream);
|
||||
mutex_unlock(&loopback->cable_lock);
|
||||
return 0;
|
||||
return err;
|
||||
}
|
||||
|
||||
static const struct snd_pcm_ops loopback_pcm_ops = {
|
||||
|
@ -765,7 +1311,6 @@ static const struct snd_pcm_ops loopback_pcm_ops = {
|
|||
.prepare = loopback_prepare,
|
||||
.trigger = loopback_trigger,
|
||||
.pointer = loopback_pointer,
|
||||
.page = snd_pcm_lib_get_vmalloc_page,
|
||||
};
|
||||
|
||||
static int loopback_pcm_new(struct loopback *loopback,
|
||||
|
@ -780,6 +1325,8 @@ static int loopback_pcm_new(struct loopback *loopback,
|
|||
return err;
|
||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &loopback_pcm_ops);
|
||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &loopback_pcm_ops);
|
||||
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_VMALLOC,
|
||||
NULL, 0, 0);
|
||||
|
||||
pcm->private_data = loopback;
|
||||
pcm->info_flags = 0;
|
||||
|
@ -1076,13 +1623,8 @@ static void print_dpcm_info(struct snd_info_buffer *buffer,
|
|||
snd_iprintf(buffer, " bytes_per_sec:\t%u\n", dpcm->pcm_bps);
|
||||
snd_iprintf(buffer, " sample_align:\t%u\n", dpcm->pcm_salign);
|
||||
snd_iprintf(buffer, " rate_shift:\t\t%u\n", dpcm->pcm_rate_shift);
|
||||
snd_iprintf(buffer, " update_pending:\t%u\n",
|
||||
dpcm->period_update_pending);
|
||||
snd_iprintf(buffer, " irq_pos:\t\t%u\n", dpcm->irq_pos);
|
||||
snd_iprintf(buffer, " period_frac:\t%u\n", dpcm->period_size_frac);
|
||||
snd_iprintf(buffer, " last_jiffies:\t%lu (%lu)\n",
|
||||
dpcm->last_jiffies, jiffies);
|
||||
snd_iprintf(buffer, " timer_expires:\t%lu\n", dpcm->timer.expires);
|
||||
if (dpcm->cable->ops->dpcm_info)
|
||||
dpcm->cable->ops->dpcm_info(dpcm, buffer);
|
||||
}
|
||||
|
||||
static void print_substream_info(struct snd_info_buffer *buffer,
|
||||
|
@ -1118,7 +1660,7 @@ static void print_cable_info(struct snd_info_entry *entry,
|
|||
mutex_unlock(&loopback->cable_lock);
|
||||
}
|
||||
|
||||
static int loopback_proc_new(struct loopback *loopback, int cidx)
|
||||
static int loopback_cable_proc_new(struct loopback *loopback, int cidx)
|
||||
{
|
||||
char name[32];
|
||||
|
||||
|
@ -1127,6 +1669,48 @@ static int loopback_proc_new(struct loopback *loopback, int cidx)
|
|||
print_cable_info);
|
||||
}
|
||||
|
||||
static void loopback_set_timer_source(struct loopback *loopback,
|
||||
const char *value)
|
||||
{
|
||||
if (loopback->timer_source) {
|
||||
devm_kfree(loopback->card->dev, loopback->timer_source);
|
||||
loopback->timer_source = NULL;
|
||||
}
|
||||
if (value && *value)
|
||||
loopback->timer_source = devm_kstrdup(loopback->card->dev,
|
||||
value, GFP_KERNEL);
|
||||
}
|
||||
|
||||
static void print_timer_source_info(struct snd_info_entry *entry,
|
||||
struct snd_info_buffer *buffer)
|
||||
{
|
||||
struct loopback *loopback = entry->private_data;
|
||||
|
||||
mutex_lock(&loopback->cable_lock);
|
||||
snd_iprintf(buffer, "%s\n",
|
||||
loopback->timer_source ? loopback->timer_source : "");
|
||||
mutex_unlock(&loopback->cable_lock);
|
||||
}
|
||||
|
||||
static void change_timer_source_info(struct snd_info_entry *entry,
|
||||
struct snd_info_buffer *buffer)
|
||||
{
|
||||
struct loopback *loopback = entry->private_data;
|
||||
char line[64];
|
||||
|
||||
mutex_lock(&loopback->cable_lock);
|
||||
if (!snd_info_get_line(buffer, line, sizeof(line)))
|
||||
loopback_set_timer_source(loopback, strim(line));
|
||||
mutex_unlock(&loopback->cable_lock);
|
||||
}
|
||||
|
||||
static int loopback_timer_source_proc_new(struct loopback *loopback)
|
||||
{
|
||||
return snd_card_rw_proc_new(loopback->card, "timer_source", loopback,
|
||||
print_timer_source_info,
|
||||
change_timer_source_info);
|
||||
}
|
||||
|
||||
static int loopback_probe(struct platform_device *devptr)
|
||||
{
|
||||
struct snd_card *card;
|
||||
|
@ -1146,6 +1730,8 @@ static int loopback_probe(struct platform_device *devptr)
|
|||
pcm_substreams[dev] = MAX_PCM_SUBSTREAMS;
|
||||
|
||||
loopback->card = card;
|
||||
loopback_set_timer_source(loopback, timer_source[dev]);
|
||||
|
||||
mutex_init(&loopback->cable_lock);
|
||||
|
||||
err = loopback_pcm_new(loopback, 0, pcm_substreams[dev]);
|
||||
|
@ -1157,8 +1743,9 @@ static int loopback_probe(struct platform_device *devptr)
|
|||
err = loopback_mixer_new(loopback, pcm_notify[dev] ? 1 : 0);
|
||||
if (err < 0)
|
||||
goto __nodev;
|
||||
loopback_proc_new(loopback, 0);
|
||||
loopback_proc_new(loopback, 1);
|
||||
loopback_cable_proc_new(loopback, 0);
|
||||
loopback_cable_proc_new(loopback, 1);
|
||||
loopback_timer_source_proc_new(loopback);
|
||||
strcpy(card->driver, "Loopback");
|
||||
strcpy(card->shortname, "Loopback");
|
||||
sprintf(card->longname, "Loopback %i", dev + 1);
|
||||
|
|
|
@ -702,7 +702,7 @@ static int snd_card_dummy_pcm(struct snd_dummy *dummy, int device,
|
|||
if (!fake_buffer) {
|
||||
snd_pcm_lib_preallocate_pages_for_all(pcm,
|
||||
SNDRV_DMA_TYPE_CONTINUOUS,
|
||||
snd_dma_continuous_data(GFP_KERNEL),
|
||||
NULL,
|
||||
0, 64*1024);
|
||||
}
|
||||
return 0;
|
||||
|
|
|
@ -1242,7 +1242,7 @@ snd_ml403_ac97cr_pcm(struct snd_ml403_ac97cr *ml403_ac97cr, int device)
|
|||
ml403_ac97cr->pcm = pcm;
|
||||
|
||||
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
|
||||
snd_dma_continuous_data(GFP_KERNEL),
|
||||
NULL,
|
||||
64 * 1024,
|
||||
128 * 1024);
|
||||
return 0;
|
||||
|
|
|
@ -352,8 +352,8 @@ int snd_pcsp_new_pcm(struct snd_pcsp *chip)
|
|||
|
||||
snd_pcm_lib_preallocate_pages_for_all(chip->pcm,
|
||||
SNDRV_DMA_TYPE_CONTINUOUS,
|
||||
snd_dma_continuous_data
|
||||
(GFP_KERNEL), PCSP_BUFFER_SIZE,
|
||||
NULL,
|
||||
PCSP_BUFFER_SIZE,
|
||||
PCSP_BUFFER_SIZE);
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -778,8 +778,7 @@ static snd_pcm_uframes_t vx_pcm_playback_pointer(struct snd_pcm_substream *subs)
|
|||
static int vx_pcm_hw_params(struct snd_pcm_substream *subs,
|
||||
struct snd_pcm_hw_params *hw_params)
|
||||
{
|
||||
return snd_pcm_lib_alloc_vmalloc_32_buffer
|
||||
(subs, params_buffer_bytes(hw_params));
|
||||
return snd_pcm_lib_malloc_pages(subs, params_buffer_bytes(hw_params));
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -787,7 +786,7 @@ static int vx_pcm_hw_params(struct snd_pcm_substream *subs,
|
|||
*/
|
||||
static int vx_pcm_hw_free(struct snd_pcm_substream *subs)
|
||||
{
|
||||
return snd_pcm_lib_free_vmalloc_buffer(subs);
|
||||
return snd_pcm_lib_free_pages(subs);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -867,7 +866,6 @@ static const struct snd_pcm_ops vx_pcm_playback_ops = {
|
|||
.prepare = vx_pcm_prepare,
|
||||
.trigger = vx_pcm_trigger,
|
||||
.pointer = vx_pcm_playback_pointer,
|
||||
.page = snd_pcm_lib_get_vmalloc_page,
|
||||
};
|
||||
|
||||
|
||||
|
@ -1088,7 +1086,6 @@ static const struct snd_pcm_ops vx_pcm_capture_ops = {
|
|||
.prepare = vx_pcm_prepare,
|
||||
.trigger = vx_pcm_trigger,
|
||||
.pointer = vx_pcm_capture_pointer,
|
||||
.page = snd_pcm_lib_get_vmalloc_page,
|
||||
};
|
||||
|
||||
|
||||
|
@ -1233,6 +1230,9 @@ int snd_vx_pcm_new(struct vx_core *chip)
|
|||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &vx_pcm_playback_ops);
|
||||
if (ins)
|
||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &vx_pcm_capture_ops);
|
||||
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_VMALLOC,
|
||||
snd_dma_continuous_data(GFP_KERNEL | GFP_DMA32),
|
||||
0, 0);
|
||||
|
||||
pcm->private_data = chip;
|
||||
pcm->private_free = snd_vx_pcm_free;
|
||||
|
|
|
@ -77,7 +77,7 @@ config SND_BEBOB
|
|||
tristate "BridgeCo DM1000/DM1100/DM1500 with BeBoB firmware"
|
||||
select SND_FIREWIRE_LIB
|
||||
select SND_HWDEP
|
||||
help
|
||||
help
|
||||
Say Y here to include support for FireWire devices based
|
||||
on BridgeCo DM1000/DM1100/DM1500 with BeBoB firmware:
|
||||
* Edirol FA-66/FA-101
|
||||
|
@ -111,8 +111,8 @@ config SND_BEBOB
|
|||
* M-Audio FireWire 1814/ProjectMix IO
|
||||
* Digidesign Mbox 2 Pro
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called snd-bebob.
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called snd-bebob.
|
||||
|
||||
config SND_FIREWIRE_DIGI00X
|
||||
tristate "Digidesign Digi 002/003 family support"
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/firewire.h>
|
||||
#include <linux/firewire-constants.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <sound/pcm.h>
|
||||
|
@ -52,10 +53,6 @@
|
|||
#define CIP_FMT_AM 0x10
|
||||
#define AMDTP_FDF_NO_DATA 0xff
|
||||
|
||||
/* TODO: make these configurable */
|
||||
#define INTERRUPT_INTERVAL 16
|
||||
#define QUEUE_LENGTH 48
|
||||
|
||||
// For iso header, tstamp and 2 CIP header.
|
||||
#define IR_CTX_HEADER_SIZE_CIP 16
|
||||
// For iso header and tstamp.
|
||||
|
@ -180,6 +177,8 @@ int amdtp_stream_add_pcm_hw_constraints(struct amdtp_stream *s,
|
|||
struct snd_pcm_runtime *runtime)
|
||||
{
|
||||
struct snd_pcm_hardware *hw = &runtime->hw;
|
||||
unsigned int ctx_header_size;
|
||||
unsigned int maximum_usec_per_period;
|
||||
int err;
|
||||
|
||||
hw->info = SNDRV_PCM_INFO_BATCH |
|
||||
|
@ -200,19 +199,36 @@ int amdtp_stream_add_pcm_hw_constraints(struct amdtp_stream *s,
|
|||
hw->period_bytes_max = hw->period_bytes_min * 2048;
|
||||
hw->buffer_bytes_max = hw->period_bytes_max * hw->periods_min;
|
||||
|
||||
/*
|
||||
* Currently firewire-lib processes 16 packets in one software
|
||||
* interrupt callback. This equals to 2msec but actually the
|
||||
* interval of the interrupts has a jitter.
|
||||
* Additionally, even if adding a constraint to fit period size to
|
||||
* 2msec, actual calculated frames per period doesn't equal to 2msec,
|
||||
* depending on sampling rate.
|
||||
* Anyway, the interval to call snd_pcm_period_elapsed() cannot 2msec.
|
||||
* Here let us use 5msec for safe period interrupt.
|
||||
*/
|
||||
// Linux driver for 1394 OHCI controller voluntarily flushes isoc
|
||||
// context when total size of accumulated context header reaches
|
||||
// PAGE_SIZE. This kicks tasklet for the isoc context and brings
|
||||
// callback in the middle of scheduled interrupts.
|
||||
// Although AMDTP streams in the same domain use the same events per
|
||||
// IRQ, use the largest size of context header between IT/IR contexts.
|
||||
// Here, use the value of context header in IR context is for both
|
||||
// contexts.
|
||||
if (!(s->flags & CIP_NO_HEADER))
|
||||
ctx_header_size = IR_CTX_HEADER_SIZE_CIP;
|
||||
else
|
||||
ctx_header_size = IR_CTX_HEADER_SIZE_NO_CIP;
|
||||
maximum_usec_per_period = USEC_PER_SEC * PAGE_SIZE /
|
||||
CYCLES_PER_SECOND / ctx_header_size;
|
||||
|
||||
// In IEC 61883-6, one isoc packet can transfer events up to the value
|
||||
// of syt interval. This comes from the interval of isoc cycle. As 1394
|
||||
// OHCI controller can generate hardware IRQ per isoc packet, the
|
||||
// interval is 125 usec.
|
||||
// However, there are two ways of transmission in IEC 61883-6; blocking
|
||||
// and non-blocking modes. In blocking mode, the sequence of isoc packet
|
||||
// includes 'empty' or 'NODATA' packets which include no event. In
|
||||
// non-blocking mode, the number of events per packet is variable up to
|
||||
// the syt interval.
|
||||
// Due to the above protocol design, the minimum PCM frames per
|
||||
// interrupt should be double of the value of syt interval, thus it is
|
||||
// 250 usec.
|
||||
err = snd_pcm_hw_constraint_minmax(runtime,
|
||||
SNDRV_PCM_HW_PARAM_PERIOD_TIME,
|
||||
5000, UINT_MAX);
|
||||
250, maximum_usec_per_period);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
|
@ -436,11 +452,12 @@ static void pcm_period_tasklet(unsigned long data)
|
|||
snd_pcm_period_elapsed(pcm);
|
||||
}
|
||||
|
||||
static int queue_packet(struct amdtp_stream *s, struct fw_iso_packet *params)
|
||||
static int queue_packet(struct amdtp_stream *s, struct fw_iso_packet *params,
|
||||
bool sched_irq)
|
||||
{
|
||||
int err;
|
||||
|
||||
params->interrupt = IS_ALIGNED(s->packet_index + 1, INTERRUPT_INTERVAL);
|
||||
params->interrupt = sched_irq;
|
||||
params->tag = s->tag;
|
||||
params->sy = 0;
|
||||
|
||||
|
@ -451,18 +468,18 @@ static int queue_packet(struct amdtp_stream *s, struct fw_iso_packet *params)
|
|||
goto end;
|
||||
}
|
||||
|
||||
if (++s->packet_index >= QUEUE_LENGTH)
|
||||
if (++s->packet_index >= s->queue_size)
|
||||
s->packet_index = 0;
|
||||
end:
|
||||
return err;
|
||||
}
|
||||
|
||||
static inline int queue_out_packet(struct amdtp_stream *s,
|
||||
struct fw_iso_packet *params)
|
||||
struct fw_iso_packet *params, bool sched_irq)
|
||||
{
|
||||
params->skip =
|
||||
!!(params->header_length == 0 && params->payload_length == 0);
|
||||
return queue_packet(s, params);
|
||||
return queue_packet(s, params, sched_irq);
|
||||
}
|
||||
|
||||
static inline int queue_in_packet(struct amdtp_stream *s,
|
||||
|
@ -472,7 +489,7 @@ static inline int queue_in_packet(struct amdtp_stream *s,
|
|||
params->header_length = s->ctx_data.tx.ctx_header_size;
|
||||
params->payload_length = s->ctx_data.tx.max_ctx_payload_length;
|
||||
params->skip = false;
|
||||
return queue_packet(s, params);
|
||||
return queue_packet(s, params, false);
|
||||
}
|
||||
|
||||
static void generate_cip_header(struct amdtp_stream *s, __be32 cip_header[2],
|
||||
|
@ -669,13 +686,14 @@ static inline u32 increment_cycle_count(u32 cycle, unsigned int addend)
|
|||
}
|
||||
|
||||
// Align to actual cycle count for the packet which is going to be scheduled.
|
||||
// This module queued the same number of isochronous cycle as QUEUE_LENGTH to
|
||||
// skip isochronous cycle, therefore it's OK to just increment the cycle by
|
||||
// QUEUE_LENGTH for scheduled cycle.
|
||||
static inline u32 compute_it_cycle(const __be32 ctx_header_tstamp)
|
||||
// This module queued the same number of isochronous cycle as the size of queue
|
||||
// to kip isochronous cycle, therefore it's OK to just increment the cycle by
|
||||
// the size of queue for scheduled cycle.
|
||||
static inline u32 compute_it_cycle(const __be32 ctx_header_tstamp,
|
||||
unsigned int queue_size)
|
||||
{
|
||||
u32 cycle = compute_cycle_count(ctx_header_tstamp);
|
||||
return increment_cycle_count(cycle, QUEUE_LENGTH);
|
||||
return increment_cycle_count(cycle, queue_size);
|
||||
}
|
||||
|
||||
static int generate_device_pkt_descs(struct amdtp_stream *s,
|
||||
|
@ -689,7 +707,7 @@ static int generate_device_pkt_descs(struct amdtp_stream *s,
|
|||
|
||||
for (i = 0; i < packets; ++i) {
|
||||
struct pkt_desc *desc = descs + i;
|
||||
unsigned int index = (s->packet_index + i) % QUEUE_LENGTH;
|
||||
unsigned int index = (s->packet_index + i) % s->queue_size;
|
||||
unsigned int cycle;
|
||||
unsigned int payload_length;
|
||||
unsigned int data_blocks;
|
||||
|
@ -730,9 +748,9 @@ static void generate_ideal_pkt_descs(struct amdtp_stream *s,
|
|||
|
||||
for (i = 0; i < packets; ++i) {
|
||||
struct pkt_desc *desc = descs + i;
|
||||
unsigned int index = (s->packet_index + i) % QUEUE_LENGTH;
|
||||
unsigned int index = (s->packet_index + i) % s->queue_size;
|
||||
|
||||
desc->cycle = compute_it_cycle(*ctx_header);
|
||||
desc->cycle = compute_it_cycle(*ctx_header, s->queue_size);
|
||||
desc->syt = calculate_syt(s, desc->cycle);
|
||||
desc->data_blocks = calculate_data_blocks(s, desc->syt);
|
||||
|
||||
|
@ -773,22 +791,40 @@ static void process_ctx_payloads(struct amdtp_stream *s,
|
|||
update_pcm_pointers(s, pcm, pcm_frames);
|
||||
}
|
||||
|
||||
static void amdtp_stream_master_callback(struct fw_iso_context *context,
|
||||
u32 tstamp, size_t header_length,
|
||||
void *header, void *private_data);
|
||||
|
||||
static void amdtp_stream_master_first_callback(struct fw_iso_context *context,
|
||||
u32 tstamp, size_t header_length,
|
||||
void *header, void *private_data);
|
||||
|
||||
static void out_stream_callback(struct fw_iso_context *context, u32 tstamp,
|
||||
size_t header_length, void *header,
|
||||
void *private_data)
|
||||
{
|
||||
struct amdtp_stream *s = private_data;
|
||||
const __be32 *ctx_header = header;
|
||||
unsigned int packets = header_length / sizeof(*ctx_header);
|
||||
unsigned int events_per_period = s->ctx_data.rx.events_per_period;
|
||||
unsigned int event_count = s->ctx_data.rx.event_count;
|
||||
unsigned int packets;
|
||||
bool is_irq_target;
|
||||
int i;
|
||||
|
||||
if (s->packet_index < 0)
|
||||
return;
|
||||
|
||||
// Calculate the number of packets in buffer and check XRUN.
|
||||
packets = header_length / sizeof(*ctx_header);
|
||||
|
||||
generate_ideal_pkt_descs(s, s->pkt_descs, ctx_header, packets);
|
||||
|
||||
process_ctx_payloads(s, s->pkt_descs, packets);
|
||||
|
||||
is_irq_target =
|
||||
!!(context->callback.sc == amdtp_stream_master_callback ||
|
||||
context->callback.sc == amdtp_stream_master_first_callback);
|
||||
|
||||
for (i = 0; i < packets; ++i) {
|
||||
const struct pkt_desc *desc = s->pkt_descs + i;
|
||||
unsigned int syt;
|
||||
|
@ -796,6 +832,7 @@ static void out_stream_callback(struct fw_iso_context *context, u32 tstamp,
|
|||
struct fw_iso_packet params;
|
||||
__be32 header[IT_PKT_HEADER_SIZE_CIP / sizeof(__be32)];
|
||||
} template = { {0}, {0} };
|
||||
bool sched_irq = false;
|
||||
|
||||
if (s->ctx_data.rx.syt_override < 0)
|
||||
syt = desc->syt;
|
||||
|
@ -806,13 +843,21 @@ static void out_stream_callback(struct fw_iso_context *context, u32 tstamp,
|
|||
desc->data_blocks, desc->data_block_counter,
|
||||
syt, i);
|
||||
|
||||
if (queue_out_packet(s, &template.params) < 0) {
|
||||
if (is_irq_target) {
|
||||
event_count += desc->data_blocks;
|
||||
if (event_count >= events_per_period) {
|
||||
event_count -= events_per_period;
|
||||
sched_irq = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (queue_out_packet(s, &template.params, sched_irq) < 0) {
|
||||
cancel_stream(s);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fw_iso_context_queue_flush(s->context);
|
||||
s->ctx_data.rx.event_count = event_count;
|
||||
}
|
||||
|
||||
static void in_stream_callback(struct fw_iso_context *context, u32 tstamp,
|
||||
|
@ -820,15 +865,15 @@ static void in_stream_callback(struct fw_iso_context *context, u32 tstamp,
|
|||
void *private_data)
|
||||
{
|
||||
struct amdtp_stream *s = private_data;
|
||||
unsigned int packets;
|
||||
__be32 *ctx_header = header;
|
||||
unsigned int packets;
|
||||
int i;
|
||||
int err;
|
||||
|
||||
if (s->packet_index < 0)
|
||||
return;
|
||||
|
||||
// The number of packets in buffer.
|
||||
// Calculate the number of packets in buffer and check XRUN.
|
||||
packets = header_length / s->ctx_data.tx.ctx_header_size;
|
||||
|
||||
err = generate_device_pkt_descs(s, s->pkt_descs, ctx_header, packets);
|
||||
|
@ -849,11 +894,40 @@ static void in_stream_callback(struct fw_iso_context *context, u32 tstamp,
|
|||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fw_iso_context_queue_flush(s->context);
|
||||
}
|
||||
|
||||
/* this is executed one time */
|
||||
static void amdtp_stream_master_callback(struct fw_iso_context *context,
|
||||
u32 tstamp, size_t header_length,
|
||||
void *header, void *private_data)
|
||||
{
|
||||
struct amdtp_domain *d = private_data;
|
||||
struct amdtp_stream *irq_target = d->irq_target;
|
||||
struct amdtp_stream *s;
|
||||
|
||||
out_stream_callback(context, tstamp, header_length, header, irq_target);
|
||||
if (amdtp_streaming_error(irq_target))
|
||||
goto error;
|
||||
|
||||
list_for_each_entry(s, &d->streams, list) {
|
||||
if (s != irq_target && amdtp_stream_running(s)) {
|
||||
fw_iso_context_flush_completions(s->context);
|
||||
if (amdtp_streaming_error(s))
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
error:
|
||||
if (amdtp_stream_running(irq_target))
|
||||
cancel_stream(irq_target);
|
||||
|
||||
list_for_each_entry(s, &d->streams, list) {
|
||||
if (amdtp_stream_running(s))
|
||||
cancel_stream(s);
|
||||
}
|
||||
}
|
||||
|
||||
// this is executed one time.
|
||||
static void amdtp_stream_first_callback(struct fw_iso_context *context,
|
||||
u32 tstamp, size_t header_length,
|
||||
void *header, void *private_data)
|
||||
|
@ -874,7 +948,7 @@ static void amdtp_stream_first_callback(struct fw_iso_context *context,
|
|||
|
||||
context->callback.sc = in_stream_callback;
|
||||
} else {
|
||||
cycle = compute_it_cycle(*ctx_header);
|
||||
cycle = compute_it_cycle(*ctx_header, s->queue_size);
|
||||
|
||||
context->callback.sc = out_stream_callback;
|
||||
}
|
||||
|
@ -884,17 +958,42 @@ static void amdtp_stream_first_callback(struct fw_iso_context *context,
|
|||
context->callback.sc(context, tstamp, header_length, header, s);
|
||||
}
|
||||
|
||||
static void amdtp_stream_master_first_callback(struct fw_iso_context *context,
|
||||
u32 tstamp, size_t header_length,
|
||||
void *header, void *private_data)
|
||||
{
|
||||
struct amdtp_domain *d = private_data;
|
||||
struct amdtp_stream *s = d->irq_target;
|
||||
const __be32 *ctx_header = header;
|
||||
|
||||
s->callbacked = true;
|
||||
wake_up(&s->callback_wait);
|
||||
|
||||
s->start_cycle = compute_it_cycle(*ctx_header, s->queue_size);
|
||||
|
||||
context->callback.sc = amdtp_stream_master_callback;
|
||||
|
||||
context->callback.sc(context, tstamp, header_length, header, d);
|
||||
}
|
||||
|
||||
/**
|
||||
* amdtp_stream_start - start transferring packets
|
||||
* @s: the AMDTP stream to start
|
||||
* @channel: the isochronous channel on the bus
|
||||
* @speed: firewire speed code
|
||||
* @d: the AMDTP domain to which the AMDTP stream belongs
|
||||
* @is_irq_target: whether isoc context for the AMDTP stream is used to generate
|
||||
* hardware IRQ.
|
||||
* @start_cycle: the isochronous cycle to start the context. Start immediately
|
||||
* if negative value is given.
|
||||
*
|
||||
* The stream cannot be started until it has been configured with
|
||||
* amdtp_stream_set_parameters() and it must be started before any PCM or MIDI
|
||||
* device can be started.
|
||||
*/
|
||||
static int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed)
|
||||
static int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed,
|
||||
struct amdtp_domain *d, bool is_irq_target,
|
||||
int start_cycle)
|
||||
{
|
||||
static const struct {
|
||||
unsigned int data_block;
|
||||
|
@ -908,10 +1007,15 @@ static int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed)
|
|||
[CIP_SFC_88200] = { 0, 67 },
|
||||
[CIP_SFC_176400] = { 0, 67 },
|
||||
};
|
||||
unsigned int events_per_buffer = d->events_per_buffer;
|
||||
unsigned int events_per_period = d->events_per_period;
|
||||
unsigned int idle_irq_interval;
|
||||
unsigned int ctx_header_size;
|
||||
unsigned int max_ctx_payload_size;
|
||||
enum dma_data_direction dir;
|
||||
int type, tag, err;
|
||||
fw_iso_callback_t ctx_cb;
|
||||
void *ctx_data;
|
||||
|
||||
mutex_lock(&s->mutex);
|
||||
|
||||
|
@ -922,6 +1026,12 @@ static int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed)
|
|||
}
|
||||
|
||||
if (s->direction == AMDTP_IN_STREAM) {
|
||||
// NOTE: IT context should be used for constant IRQ.
|
||||
if (is_irq_target) {
|
||||
err = -EINVAL;
|
||||
goto err_unlock;
|
||||
}
|
||||
|
||||
s->data_block_counter = UINT_MAX;
|
||||
} else {
|
||||
entry = &initial_state[s->sfc];
|
||||
|
@ -953,14 +1063,37 @@ static int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed)
|
|||
max_ctx_payload_size -= IT_PKT_HEADER_SIZE_CIP;
|
||||
}
|
||||
|
||||
err = iso_packets_buffer_init(&s->buffer, s->unit, QUEUE_LENGTH,
|
||||
// This is a case that AMDTP streams in domain run just for MIDI
|
||||
// substream. Use the number of events equivalent to 10 msec as
|
||||
// interval of hardware IRQ.
|
||||
if (events_per_period == 0)
|
||||
events_per_period = amdtp_rate_table[s->sfc] / 100;
|
||||
if (events_per_buffer == 0)
|
||||
events_per_buffer = events_per_period * 3;
|
||||
|
||||
idle_irq_interval = DIV_ROUND_UP(CYCLES_PER_SECOND * events_per_period,
|
||||
amdtp_rate_table[s->sfc]);
|
||||
s->queue_size = DIV_ROUND_UP(CYCLES_PER_SECOND * events_per_buffer,
|
||||
amdtp_rate_table[s->sfc]);
|
||||
|
||||
err = iso_packets_buffer_init(&s->buffer, s->unit, s->queue_size,
|
||||
max_ctx_payload_size, dir);
|
||||
if (err < 0)
|
||||
goto err_unlock;
|
||||
|
||||
if (is_irq_target) {
|
||||
s->ctx_data.rx.events_per_period = events_per_period;
|
||||
s->ctx_data.rx.event_count = 0;
|
||||
ctx_cb = amdtp_stream_master_first_callback;
|
||||
ctx_data = d;
|
||||
} else {
|
||||
ctx_cb = amdtp_stream_first_callback;
|
||||
ctx_data = s;
|
||||
}
|
||||
|
||||
s->context = fw_iso_context_create(fw_parent_device(s->unit)->card,
|
||||
type, channel, speed, ctx_header_size,
|
||||
amdtp_stream_first_callback, s);
|
||||
ctx_cb, ctx_data);
|
||||
if (IS_ERR(s->context)) {
|
||||
err = PTR_ERR(s->context);
|
||||
if (err == -EBUSY)
|
||||
|
@ -981,7 +1114,7 @@ static int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed)
|
|||
else
|
||||
s->tag = TAG_CIP;
|
||||
|
||||
s->pkt_descs = kcalloc(INTERRUPT_INTERVAL, sizeof(*s->pkt_descs),
|
||||
s->pkt_descs = kcalloc(s->queue_size, sizeof(*s->pkt_descs),
|
||||
GFP_KERNEL);
|
||||
if (!s->pkt_descs) {
|
||||
err = -ENOMEM;
|
||||
|
@ -991,12 +1124,21 @@ static int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed)
|
|||
s->packet_index = 0;
|
||||
do {
|
||||
struct fw_iso_packet params;
|
||||
|
||||
if (s->direction == AMDTP_IN_STREAM) {
|
||||
err = queue_in_packet(s, ¶ms);
|
||||
} else {
|
||||
bool sched_irq = false;
|
||||
|
||||
params.header_length = 0;
|
||||
params.payload_length = 0;
|
||||
err = queue_out_packet(s, ¶ms);
|
||||
|
||||
if (is_irq_target) {
|
||||
sched_irq = !((s->packet_index + 1) %
|
||||
idle_irq_interval);
|
||||
}
|
||||
|
||||
err = queue_out_packet(s, ¶ms, sched_irq);
|
||||
}
|
||||
if (err < 0)
|
||||
goto err_pkt_descs;
|
||||
|
@ -1008,7 +1150,7 @@ static int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed)
|
|||
tag |= FW_ISO_CONTEXT_MATCH_TAG0;
|
||||
|
||||
s->callbacked = false;
|
||||
err = fw_iso_context_start(s->context, -1, 0, tag);
|
||||
err = fw_iso_context_start(s->context, start_cycle, 0, tag);
|
||||
if (err < 0)
|
||||
goto err_pkt_descs;
|
||||
|
||||
|
@ -1029,54 +1171,69 @@ err_unlock:
|
|||
}
|
||||
|
||||
/**
|
||||
* amdtp_stream_pcm_pointer - get the PCM buffer position
|
||||
* amdtp_domain_stream_pcm_pointer - get the PCM buffer position
|
||||
* @d: the AMDTP domain.
|
||||
* @s: the AMDTP stream that transports the PCM data
|
||||
*
|
||||
* Returns the current buffer position, in frames.
|
||||
*/
|
||||
unsigned long amdtp_stream_pcm_pointer(struct amdtp_stream *s)
|
||||
unsigned long amdtp_domain_stream_pcm_pointer(struct amdtp_domain *d,
|
||||
struct amdtp_stream *s)
|
||||
{
|
||||
/*
|
||||
* This function is called in software IRQ context of period_tasklet or
|
||||
* process context.
|
||||
*
|
||||
* When the software IRQ context was scheduled by software IRQ context
|
||||
* of IR/IT contexts, queued packets were already handled. Therefore,
|
||||
* no need to flush the queue in buffer anymore.
|
||||
*
|
||||
* When the process context reach here, some packets will be already
|
||||
* queued in the buffer. These packets should be handled immediately
|
||||
* to keep better granularity of PCM pointer.
|
||||
*
|
||||
* Later, the process context will sometimes schedules software IRQ
|
||||
* context of the period_tasklet. Then, no need to flush the queue by
|
||||
* the same reason as described for IR/IT contexts.
|
||||
*/
|
||||
if (!in_interrupt() && amdtp_stream_running(s))
|
||||
fw_iso_context_flush_completions(s->context);
|
||||
struct amdtp_stream *irq_target = d->irq_target;
|
||||
|
||||
if (irq_target && amdtp_stream_running(irq_target)) {
|
||||
// This function is called in software IRQ context of
|
||||
// period_tasklet or process context.
|
||||
//
|
||||
// When the software IRQ context was scheduled by software IRQ
|
||||
// context of IT contexts, queued packets were already handled.
|
||||
// Therefore, no need to flush the queue in buffer furthermore.
|
||||
//
|
||||
// When the process context reach here, some packets will be
|
||||
// already queued in the buffer. These packets should be handled
|
||||
// immediately to keep better granularity of PCM pointer.
|
||||
//
|
||||
// Later, the process context will sometimes schedules software
|
||||
// IRQ context of the period_tasklet. Then, no need to flush the
|
||||
// queue by the same reason as described in the above
|
||||
if (!in_interrupt()) {
|
||||
// Queued packet should be processed without any kernel
|
||||
// preemption to keep latency against bus cycle.
|
||||
preempt_disable();
|
||||
fw_iso_context_flush_completions(irq_target->context);
|
||||
preempt_enable();
|
||||
}
|
||||
}
|
||||
|
||||
return READ_ONCE(s->pcm_buffer_pointer);
|
||||
}
|
||||
EXPORT_SYMBOL(amdtp_stream_pcm_pointer);
|
||||
EXPORT_SYMBOL_GPL(amdtp_domain_stream_pcm_pointer);
|
||||
|
||||
/**
|
||||
* amdtp_stream_pcm_ack - acknowledge queued PCM frames
|
||||
* amdtp_domain_stream_pcm_ack - acknowledge queued PCM frames
|
||||
* @d: the AMDTP domain.
|
||||
* @s: the AMDTP stream that transfers the PCM frames
|
||||
*
|
||||
* Returns zero always.
|
||||
*/
|
||||
int amdtp_stream_pcm_ack(struct amdtp_stream *s)
|
||||
int amdtp_domain_stream_pcm_ack(struct amdtp_domain *d, struct amdtp_stream *s)
|
||||
{
|
||||
/*
|
||||
* Process isochronous packets for recent isochronous cycle to handle
|
||||
* queued PCM frames.
|
||||
*/
|
||||
if (amdtp_stream_running(s))
|
||||
fw_iso_context_flush_completions(s->context);
|
||||
struct amdtp_stream *irq_target = d->irq_target;
|
||||
|
||||
// Process isochronous packets for recent isochronous cycle to handle
|
||||
// queued PCM frames.
|
||||
if (irq_target && amdtp_stream_running(irq_target)) {
|
||||
// Queued packet should be processed without any kernel
|
||||
// preemption to keep latency against bus cycle.
|
||||
preempt_disable();
|
||||
fw_iso_context_flush_completions(irq_target->context);
|
||||
preempt_enable();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(amdtp_stream_pcm_ack);
|
||||
EXPORT_SYMBOL_GPL(amdtp_domain_stream_pcm_ack);
|
||||
|
||||
/**
|
||||
* amdtp_stream_update - update the stream after a bus reset
|
||||
|
@ -1143,6 +1300,8 @@ int amdtp_domain_init(struct amdtp_domain *d)
|
|||
{
|
||||
INIT_LIST_HEAD(&d->streams);
|
||||
|
||||
d->events_per_period = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(amdtp_domain_init);
|
||||
|
@ -1184,26 +1343,105 @@ int amdtp_domain_add_stream(struct amdtp_domain *d, struct amdtp_stream *s,
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(amdtp_domain_add_stream);
|
||||
|
||||
static int get_current_cycle_time(struct fw_card *fw_card, int *cur_cycle)
|
||||
{
|
||||
int generation;
|
||||
int rcode;
|
||||
__be32 reg;
|
||||
u32 data;
|
||||
|
||||
// This is a request to local 1394 OHCI controller and expected to
|
||||
// complete without any event waiting.
|
||||
generation = fw_card->generation;
|
||||
smp_rmb(); // node_id vs. generation.
|
||||
rcode = fw_run_transaction(fw_card, TCODE_READ_QUADLET_REQUEST,
|
||||
fw_card->node_id, generation, SCODE_100,
|
||||
CSR_REGISTER_BASE + CSR_CYCLE_TIME,
|
||||
®, sizeof(reg));
|
||||
if (rcode != RCODE_COMPLETE)
|
||||
return -EIO;
|
||||
|
||||
data = be32_to_cpu(reg);
|
||||
*cur_cycle = data >> 12;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* amdtp_domain_start - start sending packets for isoc context in the domain.
|
||||
* @d: the AMDTP domain.
|
||||
* @ir_delay_cycle: the cycle delay to start all IR contexts.
|
||||
*/
|
||||
int amdtp_domain_start(struct amdtp_domain *d)
|
||||
int amdtp_domain_start(struct amdtp_domain *d, unsigned int ir_delay_cycle)
|
||||
{
|
||||
struct amdtp_stream *s;
|
||||
int err = 0;
|
||||
int cycle;
|
||||
int err;
|
||||
|
||||
// Select an IT context as IRQ target.
|
||||
list_for_each_entry(s, &d->streams, list) {
|
||||
err = amdtp_stream_start(s, s->channel, s->speed);
|
||||
if (err < 0)
|
||||
if (s->direction == AMDTP_OUT_STREAM)
|
||||
break;
|
||||
}
|
||||
if (!s)
|
||||
return -ENXIO;
|
||||
d->irq_target = s;
|
||||
|
||||
if (err < 0) {
|
||||
list_for_each_entry(s, &d->streams, list)
|
||||
amdtp_stream_stop(s);
|
||||
if (ir_delay_cycle > 0) {
|
||||
struct fw_card *fw_card = fw_parent_device(s->unit)->card;
|
||||
|
||||
err = get_current_cycle_time(fw_card, &cycle);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
// No need to care overflow in cycle field because of enough
|
||||
// width.
|
||||
cycle += ir_delay_cycle;
|
||||
|
||||
// Round up to sec field.
|
||||
if ((cycle & 0x00001fff) >= CYCLES_PER_SECOND) {
|
||||
unsigned int sec;
|
||||
|
||||
// The sec field can overflow.
|
||||
sec = (cycle & 0xffffe000) >> 13;
|
||||
cycle = (++sec << 13) |
|
||||
((cycle & 0x00001fff) / CYCLES_PER_SECOND);
|
||||
}
|
||||
|
||||
// In OHCI 1394 specification, lower 2 bits are available for
|
||||
// sec field.
|
||||
cycle &= 0x00007fff;
|
||||
} else {
|
||||
cycle = -1;
|
||||
}
|
||||
|
||||
list_for_each_entry(s, &d->streams, list) {
|
||||
int cycle_match;
|
||||
|
||||
if (s->direction == AMDTP_IN_STREAM) {
|
||||
cycle_match = cycle;
|
||||
} else {
|
||||
// IT context starts immediately.
|
||||
cycle_match = -1;
|
||||
}
|
||||
|
||||
if (s != d->irq_target) {
|
||||
err = amdtp_stream_start(s, s->channel, s->speed, d,
|
||||
false, cycle_match);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
s = d->irq_target;
|
||||
err = amdtp_stream_start(s, s->channel, s->speed, d, true, -1);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
return 0;
|
||||
error:
|
||||
list_for_each_entry(s, &d->streams, list)
|
||||
amdtp_stream_stop(s);
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(amdtp_domain_start);
|
||||
|
@ -1216,10 +1454,17 @@ void amdtp_domain_stop(struct amdtp_domain *d)
|
|||
{
|
||||
struct amdtp_stream *s, *next;
|
||||
|
||||
if (d->irq_target)
|
||||
amdtp_stream_stop(d->irq_target);
|
||||
|
||||
list_for_each_entry_safe(s, next, &d->streams, list) {
|
||||
list_del(&s->list);
|
||||
|
||||
amdtp_stream_stop(s);
|
||||
if (s != d->irq_target)
|
||||
amdtp_stream_stop(s);
|
||||
}
|
||||
|
||||
d->events_per_period = 0;
|
||||
d->irq_target = NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(amdtp_domain_stop);
|
||||
|
|
|
@ -117,6 +117,7 @@ struct amdtp_stream {
|
|||
/* For packet processing. */
|
||||
struct fw_iso_context *context;
|
||||
struct iso_packets_buffer buffer;
|
||||
unsigned int queue_size;
|
||||
int packet_index;
|
||||
struct pkt_desc *pkt_descs;
|
||||
int tag;
|
||||
|
@ -142,6 +143,10 @@ struct amdtp_stream {
|
|||
// To generate CIP header.
|
||||
unsigned int fdf;
|
||||
int syt_override;
|
||||
|
||||
// To generate constant hardware IRQ.
|
||||
unsigned int event_count;
|
||||
unsigned int events_per_period;
|
||||
} rx;
|
||||
} ctx_data;
|
||||
|
||||
|
@ -194,8 +199,6 @@ int amdtp_stream_add_pcm_hw_constraints(struct amdtp_stream *s,
|
|||
struct snd_pcm_runtime *runtime);
|
||||
|
||||
void amdtp_stream_pcm_prepare(struct amdtp_stream *s);
|
||||
unsigned long amdtp_stream_pcm_pointer(struct amdtp_stream *s);
|
||||
int amdtp_stream_pcm_ack(struct amdtp_stream *s);
|
||||
void amdtp_stream_pcm_abort(struct amdtp_stream *s);
|
||||
|
||||
extern const unsigned int amdtp_syt_intervals[CIP_SFC_COUNT];
|
||||
|
@ -272,6 +275,11 @@ static inline bool amdtp_stream_wait_callback(struct amdtp_stream *s,
|
|||
|
||||
struct amdtp_domain {
|
||||
struct list_head streams;
|
||||
|
||||
unsigned int events_per_period;
|
||||
unsigned int events_per_buffer;
|
||||
|
||||
struct amdtp_stream *irq_target;
|
||||
};
|
||||
|
||||
int amdtp_domain_init(struct amdtp_domain *d);
|
||||
|
@ -280,7 +288,21 @@ void amdtp_domain_destroy(struct amdtp_domain *d);
|
|||
int amdtp_domain_add_stream(struct amdtp_domain *d, struct amdtp_stream *s,
|
||||
int channel, int speed);
|
||||
|
||||
int amdtp_domain_start(struct amdtp_domain *d);
|
||||
int amdtp_domain_start(struct amdtp_domain *d, unsigned int ir_delay_cycle);
|
||||
void amdtp_domain_stop(struct amdtp_domain *d);
|
||||
|
||||
static inline int amdtp_domain_set_events_per_period(struct amdtp_domain *d,
|
||||
unsigned int events_per_period,
|
||||
unsigned int events_per_buffer)
|
||||
{
|
||||
d->events_per_period = events_per_period;
|
||||
d->events_per_buffer = events_per_buffer;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned long amdtp_domain_stream_pcm_pointer(struct amdtp_domain *d,
|
||||
struct amdtp_stream *s);
|
||||
int amdtp_domain_stream_pcm_ack(struct amdtp_domain *d, struct amdtp_stream *s);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -217,7 +217,9 @@ int snd_bebob_stream_get_clock_src(struct snd_bebob *bebob,
|
|||
enum snd_bebob_clock_type *src);
|
||||
int snd_bebob_stream_discover(struct snd_bebob *bebob);
|
||||
int snd_bebob_stream_init_duplex(struct snd_bebob *bebob);
|
||||
int snd_bebob_stream_reserve_duplex(struct snd_bebob *bebob, unsigned int rate);
|
||||
int snd_bebob_stream_reserve_duplex(struct snd_bebob *bebob, unsigned int rate,
|
||||
unsigned int frames_per_period,
|
||||
unsigned int frames_per_buffer);
|
||||
int snd_bebob_stream_start_duplex(struct snd_bebob *bebob);
|
||||
void snd_bebob_stream_stop_duplex(struct snd_bebob *bebob);
|
||||
void snd_bebob_stream_destroy_duplex(struct snd_bebob *bebob);
|
||||
|
|
|
@ -17,7 +17,7 @@ static int midi_open(struct snd_rawmidi_substream *substream)
|
|||
return err;
|
||||
|
||||
mutex_lock(&bebob->mutex);
|
||||
err = snd_bebob_stream_reserve_duplex(bebob, 0);
|
||||
err = snd_bebob_stream_reserve_duplex(bebob, 0, 0, 0);
|
||||
if (err >= 0) {
|
||||
++bebob->substreams_counter;
|
||||
err = snd_bebob_stream_start_duplex(bebob);
|
||||
|
|
|
@ -129,18 +129,17 @@ end:
|
|||
return err;
|
||||
}
|
||||
|
||||
static int
|
||||
pcm_open(struct snd_pcm_substream *substream)
|
||||
static int pcm_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_bebob *bebob = substream->private_data;
|
||||
const struct snd_bebob_rate_spec *spec = bebob->spec->rate;
|
||||
unsigned int sampling_rate;
|
||||
struct amdtp_domain *d = &bebob->domain;
|
||||
enum snd_bebob_clock_type src;
|
||||
int err;
|
||||
|
||||
err = snd_bebob_stream_lock_try(bebob);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
return err;
|
||||
|
||||
err = pcm_init_hw_params(bebob, substream);
|
||||
if (err < 0)
|
||||
|
@ -150,15 +149,20 @@ pcm_open(struct snd_pcm_substream *substream)
|
|||
if (err < 0)
|
||||
goto err_locked;
|
||||
|
||||
/*
|
||||
* When source of clock is internal or any PCM stream are running,
|
||||
* the available sampling rate is limited at current sampling rate.
|
||||
*/
|
||||
mutex_lock(&bebob->mutex);
|
||||
|
||||
// When source of clock is not internal or any stream is reserved for
|
||||
// transmission of PCM frames, the available sampling rate is limited
|
||||
// at current one.
|
||||
if (src == SND_BEBOB_CLOCK_TYPE_EXTERNAL ||
|
||||
amdtp_stream_pcm_running(&bebob->tx_stream) ||
|
||||
amdtp_stream_pcm_running(&bebob->rx_stream)) {
|
||||
(bebob->substreams_counter > 0 && d->events_per_period > 0)) {
|
||||
unsigned int frames_per_period = d->events_per_period;
|
||||
unsigned int frames_per_buffer = d->events_per_buffer;
|
||||
unsigned int sampling_rate;
|
||||
|
||||
err = spec->get(bebob, &sampling_rate);
|
||||
if (err < 0) {
|
||||
mutex_unlock(&bebob->mutex);
|
||||
dev_err(&bebob->unit->device,
|
||||
"fail to get sampling rate: %d\n", err);
|
||||
goto err_locked;
|
||||
|
@ -166,11 +170,31 @@ pcm_open(struct snd_pcm_substream *substream)
|
|||
|
||||
substream->runtime->hw.rate_min = sampling_rate;
|
||||
substream->runtime->hw.rate_max = sampling_rate;
|
||||
|
||||
if (frames_per_period > 0) {
|
||||
err = snd_pcm_hw_constraint_minmax(substream->runtime,
|
||||
SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
|
||||
frames_per_period, frames_per_period);
|
||||
if (err < 0) {
|
||||
mutex_unlock(&bebob->mutex);
|
||||
goto err_locked;
|
||||
}
|
||||
|
||||
err = snd_pcm_hw_constraint_minmax(substream->runtime,
|
||||
SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
|
||||
frames_per_buffer, frames_per_buffer);
|
||||
if (err < 0) {
|
||||
mutex_unlock(&bebob->mutex);
|
||||
goto err_locked;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&bebob->mutex);
|
||||
|
||||
snd_pcm_set_sync(substream);
|
||||
end:
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
err_locked:
|
||||
snd_bebob_stream_lock_release(bebob);
|
||||
return err;
|
||||
|
@ -190,16 +214,18 @@ static int pcm_hw_params(struct snd_pcm_substream *substream,
|
|||
struct snd_bebob *bebob = substream->private_data;
|
||||
int err;
|
||||
|
||||
err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
|
||||
params_buffer_bytes(hw_params));
|
||||
err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
|
||||
unsigned int rate = params_rate(hw_params);
|
||||
unsigned int frames_per_period = params_period_size(hw_params);
|
||||
unsigned int frames_per_buffer = params_buffer_size(hw_params);
|
||||
|
||||
mutex_lock(&bebob->mutex);
|
||||
err = snd_bebob_stream_reserve_duplex(bebob, rate);
|
||||
err = snd_bebob_stream_reserve_duplex(bebob, rate,
|
||||
frames_per_period, frames_per_buffer);
|
||||
if (err >= 0)
|
||||
++bebob->substreams_counter;
|
||||
mutex_unlock(&bebob->mutex);
|
||||
|
@ -221,7 +247,7 @@ static int pcm_hw_free(struct snd_pcm_substream *substream)
|
|||
|
||||
mutex_unlock(&bebob->mutex);
|
||||
|
||||
return snd_pcm_lib_free_vmalloc_buffer(substream);
|
||||
return snd_pcm_lib_free_pages(substream);
|
||||
}
|
||||
|
||||
static int
|
||||
|
@ -286,31 +312,33 @@ pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t
|
||||
pcm_capture_pointer(struct snd_pcm_substream *sbstrm)
|
||||
static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstrm)
|
||||
{
|
||||
struct snd_bebob *bebob = sbstrm->private_data;
|
||||
return amdtp_stream_pcm_pointer(&bebob->tx_stream);
|
||||
|
||||
return amdtp_domain_stream_pcm_pointer(&bebob->domain,
|
||||
&bebob->tx_stream);
|
||||
}
|
||||
static snd_pcm_uframes_t
|
||||
pcm_playback_pointer(struct snd_pcm_substream *sbstrm)
|
||||
static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstrm)
|
||||
{
|
||||
struct snd_bebob *bebob = sbstrm->private_data;
|
||||
return amdtp_stream_pcm_pointer(&bebob->rx_stream);
|
||||
|
||||
return amdtp_domain_stream_pcm_pointer(&bebob->domain,
|
||||
&bebob->rx_stream);
|
||||
}
|
||||
|
||||
static int pcm_capture_ack(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_bebob *bebob = substream->private_data;
|
||||
|
||||
return amdtp_stream_pcm_ack(&bebob->tx_stream);
|
||||
return amdtp_domain_stream_pcm_ack(&bebob->domain, &bebob->tx_stream);
|
||||
}
|
||||
|
||||
static int pcm_playback_ack(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_bebob *bebob = substream->private_data;
|
||||
|
||||
return amdtp_stream_pcm_ack(&bebob->rx_stream);
|
||||
return amdtp_domain_stream_pcm_ack(&bebob->domain, &bebob->rx_stream);
|
||||
}
|
||||
|
||||
int snd_bebob_create_pcm_devices(struct snd_bebob *bebob)
|
||||
|
@ -325,7 +353,6 @@ int snd_bebob_create_pcm_devices(struct snd_bebob *bebob)
|
|||
.trigger = pcm_capture_trigger,
|
||||
.pointer = pcm_capture_pointer,
|
||||
.ack = pcm_capture_ack,
|
||||
.page = snd_pcm_lib_get_vmalloc_page,
|
||||
};
|
||||
static const struct snd_pcm_ops playback_ops = {
|
||||
.open = pcm_open,
|
||||
|
@ -337,7 +364,6 @@ int snd_bebob_create_pcm_devices(struct snd_bebob *bebob)
|
|||
.trigger = pcm_playback_trigger,
|
||||
.pointer = pcm_playback_pointer,
|
||||
.ack = pcm_playback_ack,
|
||||
.page = snd_pcm_lib_get_vmalloc_page,
|
||||
};
|
||||
struct snd_pcm *pcm;
|
||||
int err;
|
||||
|
@ -351,6 +377,8 @@ int snd_bebob_create_pcm_devices(struct snd_bebob *bebob)
|
|||
"%s PCM", bebob->card->shortname);
|
||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops);
|
||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_ops);
|
||||
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_VMALLOC,
|
||||
NULL, 0, 0);
|
||||
end:
|
||||
return err;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
#include "./bebob.h"
|
||||
|
||||
#define CALLBACK_TIMEOUT 2000
|
||||
#define CALLBACK_TIMEOUT 2500
|
||||
#define FW_ISO_RESOURCE_DELAY 1000
|
||||
|
||||
/*
|
||||
|
@ -398,36 +398,19 @@ check_connection_used_by_others(struct snd_bebob *bebob, struct amdtp_stream *s)
|
|||
return err;
|
||||
}
|
||||
|
||||
static int make_both_connections(struct snd_bebob *bebob)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
err = cmp_connection_establish(&bebob->out_conn);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = cmp_connection_establish(&bebob->in_conn);
|
||||
if (err < 0) {
|
||||
cmp_connection_break(&bebob->out_conn);
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
break_both_connections(struct snd_bebob *bebob)
|
||||
static void break_both_connections(struct snd_bebob *bebob)
|
||||
{
|
||||
cmp_connection_break(&bebob->in_conn);
|
||||
cmp_connection_break(&bebob->out_conn);
|
||||
|
||||
/* These models seems to be in transition state for a longer time. */
|
||||
if (bebob->maudio_special_quirk != NULL)
|
||||
msleep(200);
|
||||
// These models seem to be in transition state for a longer time. When
|
||||
// accessing in the state, any transactions is corrupted. In the worst
|
||||
// case, the device is going to reboot.
|
||||
if (bebob->version < 2)
|
||||
msleep(600);
|
||||
}
|
||||
|
||||
static int
|
||||
start_stream(struct snd_bebob *bebob, struct amdtp_stream *stream)
|
||||
static int start_stream(struct snd_bebob *bebob, struct amdtp_stream *stream)
|
||||
{
|
||||
struct cmp_connection *conn;
|
||||
int err = 0;
|
||||
|
@ -437,18 +420,19 @@ start_stream(struct snd_bebob *bebob, struct amdtp_stream *stream)
|
|||
else
|
||||
conn = &bebob->out_conn;
|
||||
|
||||
/* channel mapping */
|
||||
// channel mapping.
|
||||
if (bebob->maudio_special_quirk == NULL) {
|
||||
err = map_data_channels(bebob, stream);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
return err;
|
||||
}
|
||||
|
||||
// start amdtp stream.
|
||||
err = amdtp_domain_add_stream(&bebob->domain, stream,
|
||||
conn->resources.channel, conn->speed);
|
||||
end:
|
||||
return err;
|
||||
err = cmp_connection_establish(conn);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return amdtp_domain_add_stream(&bebob->domain, stream,
|
||||
conn->resources.channel, conn->speed);
|
||||
}
|
||||
|
||||
static int init_stream(struct snd_bebob *bebob, struct amdtp_stream *stream)
|
||||
|
@ -553,7 +537,9 @@ static int keep_resources(struct snd_bebob *bebob, struct amdtp_stream *stream,
|
|||
return cmp_connection_reserve(conn, amdtp_stream_get_max_payload(stream));
|
||||
}
|
||||
|
||||
int snd_bebob_stream_reserve_duplex(struct snd_bebob *bebob, unsigned int rate)
|
||||
int snd_bebob_stream_reserve_duplex(struct snd_bebob *bebob, unsigned int rate,
|
||||
unsigned int frames_per_period,
|
||||
unsigned int frames_per_buffer)
|
||||
{
|
||||
unsigned int curr_rate;
|
||||
int err;
|
||||
|
@ -606,6 +592,14 @@ int snd_bebob_stream_reserve_duplex(struct snd_bebob *bebob, unsigned int rate)
|
|||
cmp_connection_release(&bebob->out_conn);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = amdtp_domain_set_events_per_period(&bebob->domain,
|
||||
frames_per_period, frames_per_buffer);
|
||||
if (err < 0) {
|
||||
cmp_connection_release(&bebob->out_conn);
|
||||
cmp_connection_release(&bebob->in_conn);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -627,7 +621,10 @@ int snd_bebob_stream_start_duplex(struct snd_bebob *bebob)
|
|||
}
|
||||
|
||||
if (!amdtp_stream_running(&bebob->rx_stream)) {
|
||||
enum snd_bebob_clock_type src;
|
||||
struct amdtp_stream *master, *slave;
|
||||
unsigned int curr_rate;
|
||||
unsigned int ir_delay_cycle;
|
||||
|
||||
if (bebob->maudio_special_quirk) {
|
||||
err = bebob->spec->rate->get(bebob, &curr_rate);
|
||||
|
@ -635,19 +632,40 @@ int snd_bebob_stream_start_duplex(struct snd_bebob *bebob)
|
|||
return err;
|
||||
}
|
||||
|
||||
err = make_both_connections(bebob);
|
||||
err = snd_bebob_stream_get_clock_src(bebob, &src);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = start_stream(bebob, &bebob->rx_stream);
|
||||
if (src != SND_BEBOB_CLOCK_TYPE_SYT) {
|
||||
master = &bebob->tx_stream;
|
||||
slave = &bebob->rx_stream;
|
||||
} else {
|
||||
master = &bebob->rx_stream;
|
||||
slave = &bebob->tx_stream;
|
||||
}
|
||||
|
||||
err = start_stream(bebob, master);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
err = start_stream(bebob, &bebob->tx_stream);
|
||||
err = start_stream(bebob, slave);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
err = amdtp_domain_start(&bebob->domain);
|
||||
// The device postpones start of transmission mostly for 1 sec
|
||||
// after receives packets firstly. For safe, IR context starts
|
||||
// 0.4 sec (=3200 cycles) later to version 1 or 2 firmware,
|
||||
// 2.0 sec (=16000 cycles) for version 3 firmware. This is
|
||||
// within 2.5 sec (=CALLBACK_TIMEOUT).
|
||||
// Furthermore, some devices transfer isoc packets with
|
||||
// discontinuous counter in the beginning of packet streaming.
|
||||
// The delay has an effect to avoid detection of this
|
||||
// discontinuity.
|
||||
if (bebob->version < 2)
|
||||
ir_delay_cycle = 3200;
|
||||
else
|
||||
ir_delay_cycle = 16000;
|
||||
err = amdtp_domain_start(&bebob->domain, ir_delay_cycle);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ static int midi_open(struct snd_rawmidi_substream *substream)
|
|||
|
||||
mutex_lock(&dice->mutex);
|
||||
|
||||
err = snd_dice_stream_reserve_duplex(dice, 0);
|
||||
err = snd_dice_stream_reserve_duplex(dice, 0, 0, 0);
|
||||
if (err >= 0) {
|
||||
++dice->substreams_counter;
|
||||
err = snd_dice_stream_start_duplex(dice);
|
||||
|
|
|
@ -164,13 +164,14 @@ static int init_hw_info(struct snd_dice *dice,
|
|||
static int pcm_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_dice *dice = substream->private_data;
|
||||
struct amdtp_domain *d = &dice->domain;
|
||||
unsigned int source;
|
||||
bool internal;
|
||||
int err;
|
||||
|
||||
err = snd_dice_stream_lock_try(dice);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
return err;
|
||||
|
||||
err = init_hw_info(dice, substream);
|
||||
if (err < 0)
|
||||
|
@ -195,27 +196,56 @@ static int pcm_open(struct snd_pcm_substream *substream)
|
|||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* When source of clock is not internal or any PCM streams are running,
|
||||
* available sampling rate is limited at current sampling rate.
|
||||
*/
|
||||
mutex_lock(&dice->mutex);
|
||||
|
||||
// When source of clock is not internal or any stream is reserved for
|
||||
// transmission of PCM frames, the available sampling rate is limited
|
||||
// at current one.
|
||||
if (!internal ||
|
||||
amdtp_stream_pcm_running(&dice->tx_stream[0]) ||
|
||||
amdtp_stream_pcm_running(&dice->tx_stream[1]) ||
|
||||
amdtp_stream_pcm_running(&dice->rx_stream[0]) ||
|
||||
amdtp_stream_pcm_running(&dice->rx_stream[1])) {
|
||||
(dice->substreams_counter > 0 && d->events_per_period > 0)) {
|
||||
unsigned int frames_per_period = d->events_per_period;
|
||||
unsigned int frames_per_buffer = d->events_per_buffer;
|
||||
unsigned int rate;
|
||||
|
||||
err = snd_dice_transaction_get_rate(dice, &rate);
|
||||
if (err < 0)
|
||||
if (err < 0) {
|
||||
mutex_unlock(&dice->mutex);
|
||||
goto err_locked;
|
||||
}
|
||||
|
||||
substream->runtime->hw.rate_min = rate;
|
||||
substream->runtime->hw.rate_max = rate;
|
||||
|
||||
if (frames_per_period > 0) {
|
||||
// For double_pcm_frame quirk.
|
||||
if (rate > 96000) {
|
||||
frames_per_period *= 2;
|
||||
frames_per_buffer *= 2;
|
||||
}
|
||||
|
||||
err = snd_pcm_hw_constraint_minmax(substream->runtime,
|
||||
SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
|
||||
frames_per_period, frames_per_period);
|
||||
if (err < 0) {
|
||||
mutex_unlock(&dice->mutex);
|
||||
goto err_locked;
|
||||
}
|
||||
|
||||
err = snd_pcm_hw_constraint_minmax(substream->runtime,
|
||||
SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
|
||||
frames_per_buffer, frames_per_buffer);
|
||||
if (err < 0) {
|
||||
mutex_unlock(&dice->mutex);
|
||||
goto err_locked;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&dice->mutex);
|
||||
|
||||
snd_pcm_set_sync(substream);
|
||||
end:
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
err_locked:
|
||||
snd_dice_stream_lock_release(dice);
|
||||
return err;
|
||||
|
@ -236,16 +266,23 @@ static int pcm_hw_params(struct snd_pcm_substream *substream,
|
|||
struct snd_dice *dice = substream->private_data;
|
||||
int err;
|
||||
|
||||
err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
|
||||
params_buffer_bytes(hw_params));
|
||||
err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
|
||||
unsigned int rate = params_rate(hw_params);
|
||||
unsigned int events_per_period = params_period_size(hw_params);
|
||||
unsigned int events_per_buffer = params_buffer_size(hw_params);
|
||||
|
||||
mutex_lock(&dice->mutex);
|
||||
err = snd_dice_stream_reserve_duplex(dice, rate);
|
||||
// For double_pcm_frame quirk.
|
||||
if (rate > 96000) {
|
||||
events_per_period /= 2;
|
||||
events_per_buffer /= 2;
|
||||
}
|
||||
err = snd_dice_stream_reserve_duplex(dice, rate,
|
||||
events_per_period, events_per_buffer);
|
||||
if (err >= 0)
|
||||
++dice->substreams_counter;
|
||||
mutex_unlock(&dice->mutex);
|
||||
|
@ -267,7 +304,7 @@ static int pcm_hw_free(struct snd_pcm_substream *substream)
|
|||
|
||||
mutex_unlock(&dice->mutex);
|
||||
|
||||
return snd_pcm_lib_free_vmalloc_buffer(substream);
|
||||
return snd_pcm_lib_free_pages(substream);
|
||||
}
|
||||
|
||||
static int capture_prepare(struct snd_pcm_substream *substream)
|
||||
|
@ -341,14 +378,14 @@ static snd_pcm_uframes_t capture_pointer(struct snd_pcm_substream *substream)
|
|||
struct snd_dice *dice = substream->private_data;
|
||||
struct amdtp_stream *stream = &dice->tx_stream[substream->pcm->device];
|
||||
|
||||
return amdtp_stream_pcm_pointer(stream);
|
||||
return amdtp_domain_stream_pcm_pointer(&dice->domain, stream);
|
||||
}
|
||||
static snd_pcm_uframes_t playback_pointer(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_dice *dice = substream->private_data;
|
||||
struct amdtp_stream *stream = &dice->rx_stream[substream->pcm->device];
|
||||
|
||||
return amdtp_stream_pcm_pointer(stream);
|
||||
return amdtp_domain_stream_pcm_pointer(&dice->domain, stream);
|
||||
}
|
||||
|
||||
static int capture_ack(struct snd_pcm_substream *substream)
|
||||
|
@ -356,7 +393,7 @@ static int capture_ack(struct snd_pcm_substream *substream)
|
|||
struct snd_dice *dice = substream->private_data;
|
||||
struct amdtp_stream *stream = &dice->tx_stream[substream->pcm->device];
|
||||
|
||||
return amdtp_stream_pcm_ack(stream);
|
||||
return amdtp_domain_stream_pcm_ack(&dice->domain, stream);
|
||||
}
|
||||
|
||||
static int playback_ack(struct snd_pcm_substream *substream)
|
||||
|
@ -364,7 +401,7 @@ static int playback_ack(struct snd_pcm_substream *substream)
|
|||
struct snd_dice *dice = substream->private_data;
|
||||
struct amdtp_stream *stream = &dice->rx_stream[substream->pcm->device];
|
||||
|
||||
return amdtp_stream_pcm_ack(stream);
|
||||
return amdtp_domain_stream_pcm_ack(&dice->domain, stream);
|
||||
}
|
||||
|
||||
int snd_dice_create_pcm(struct snd_dice *dice)
|
||||
|
@ -379,7 +416,6 @@ int snd_dice_create_pcm(struct snd_dice *dice)
|
|||
.trigger = capture_trigger,
|
||||
.pointer = capture_pointer,
|
||||
.ack = capture_ack,
|
||||
.page = snd_pcm_lib_get_vmalloc_page,
|
||||
};
|
||||
static const struct snd_pcm_ops playback_ops = {
|
||||
.open = pcm_open,
|
||||
|
@ -391,7 +427,6 @@ int snd_dice_create_pcm(struct snd_dice *dice)
|
|||
.trigger = playback_trigger,
|
||||
.pointer = playback_pointer,
|
||||
.ack = playback_ack,
|
||||
.page = snd_pcm_lib_get_vmalloc_page,
|
||||
};
|
||||
struct snd_pcm *pcm;
|
||||
unsigned int capture, playback;
|
||||
|
@ -421,6 +456,10 @@ int snd_dice_create_pcm(struct snd_dice *dice)
|
|||
if (playback > 0)
|
||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
|
||||
&playback_ops);
|
||||
|
||||
snd_pcm_lib_preallocate_pages_for_all(pcm,
|
||||
SNDRV_DMA_TYPE_VMALLOC,
|
||||
NULL, 0, 0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -278,7 +278,9 @@ static void finish_session(struct snd_dice *dice, struct reg_params *tx_params,
|
|||
snd_dice_transaction_clear_enable(dice);
|
||||
}
|
||||
|
||||
int snd_dice_stream_reserve_duplex(struct snd_dice *dice, unsigned int rate)
|
||||
int snd_dice_stream_reserve_duplex(struct snd_dice *dice, unsigned int rate,
|
||||
unsigned int events_per_period,
|
||||
unsigned int events_per_buffer)
|
||||
{
|
||||
unsigned int curr_rate;
|
||||
int err;
|
||||
|
@ -324,6 +326,11 @@ int snd_dice_stream_reserve_duplex(struct snd_dice *dice, unsigned int rate)
|
|||
&rx_params);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
err = amdtp_domain_set_events_per_period(&dice->domain,
|
||||
events_per_period, events_per_buffer);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -455,7 +462,7 @@ int snd_dice_stream_start_duplex(struct snd_dice *dice)
|
|||
goto error;
|
||||
}
|
||||
|
||||
err = amdtp_domain_start(&dice->domain);
|
||||
err = amdtp_domain_start(&dice->domain, 0);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
|
|
|
@ -210,7 +210,9 @@ int snd_dice_stream_start_duplex(struct snd_dice *dice);
|
|||
void snd_dice_stream_stop_duplex(struct snd_dice *dice);
|
||||
int snd_dice_stream_init_duplex(struct snd_dice *dice);
|
||||
void snd_dice_stream_destroy_duplex(struct snd_dice *dice);
|
||||
int snd_dice_stream_reserve_duplex(struct snd_dice *dice, unsigned int rate);
|
||||
int snd_dice_stream_reserve_duplex(struct snd_dice *dice, unsigned int rate,
|
||||
unsigned int events_per_period,
|
||||
unsigned int events_per_buffer);
|
||||
void snd_dice_stream_update_duplex(struct snd_dice *dice);
|
||||
int snd_dice_stream_detect_current_formats(struct snd_dice *dice);
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ static int midi_open(struct snd_rawmidi_substream *substream)
|
|||
return err;
|
||||
|
||||
mutex_lock(&dg00x->mutex);
|
||||
err = snd_dg00x_stream_reserve_duplex(dg00x, 0);
|
||||
err = snd_dg00x_stream_reserve_duplex(dg00x, 0, 0, 0);
|
||||
if (err >= 0) {
|
||||
++dg00x->substreams_counter;
|
||||
err = snd_dg00x_stream_start_duplex(dg00x);
|
||||
|
|
|
@ -100,14 +100,14 @@ static int pcm_init_hw_params(struct snd_dg00x *dg00x,
|
|||
static int pcm_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_dg00x *dg00x = substream->private_data;
|
||||
struct amdtp_domain *d = &dg00x->domain;
|
||||
enum snd_dg00x_clock clock;
|
||||
bool detect;
|
||||
unsigned int rate;
|
||||
int err;
|
||||
|
||||
err = snd_dg00x_stream_lock_try(dg00x);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
return err;
|
||||
|
||||
err = pcm_init_hw_params(dg00x, substream);
|
||||
if (err < 0)
|
||||
|
@ -127,19 +127,49 @@ static int pcm_open(struct snd_pcm_substream *substream)
|
|||
}
|
||||
}
|
||||
|
||||
mutex_lock(&dg00x->mutex);
|
||||
|
||||
// When source of clock is not internal or any stream is reserved for
|
||||
// transmission of PCM frames, the available sampling rate is limited
|
||||
// at current one.
|
||||
if ((clock != SND_DG00X_CLOCK_INTERNAL) ||
|
||||
amdtp_stream_pcm_running(&dg00x->rx_stream) ||
|
||||
amdtp_stream_pcm_running(&dg00x->tx_stream)) {
|
||||
(dg00x->substreams_counter > 0 && d->events_per_period > 0)) {
|
||||
unsigned int frames_per_period = d->events_per_period;
|
||||
unsigned int frames_per_buffer = d->events_per_buffer;
|
||||
unsigned int rate;
|
||||
|
||||
err = snd_dg00x_stream_get_external_rate(dg00x, &rate);
|
||||
if (err < 0)
|
||||
if (err < 0) {
|
||||
mutex_unlock(&dg00x->mutex);
|
||||
goto err_locked;
|
||||
}
|
||||
substream->runtime->hw.rate_min = rate;
|
||||
substream->runtime->hw.rate_max = rate;
|
||||
|
||||
if (frames_per_period > 0) {
|
||||
err = snd_pcm_hw_constraint_minmax(substream->runtime,
|
||||
SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
|
||||
frames_per_period, frames_per_period);
|
||||
if (err < 0) {
|
||||
mutex_unlock(&dg00x->mutex);
|
||||
goto err_locked;
|
||||
}
|
||||
|
||||
err = snd_pcm_hw_constraint_minmax(substream->runtime,
|
||||
SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
|
||||
frames_per_buffer, frames_per_buffer);
|
||||
if (err < 0) {
|
||||
mutex_unlock(&dg00x->mutex);
|
||||
goto err_locked;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&dg00x->mutex);
|
||||
|
||||
snd_pcm_set_sync(substream);
|
||||
end:
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
err_locked:
|
||||
snd_dg00x_stream_lock_release(dg00x);
|
||||
return err;
|
||||
|
@ -160,16 +190,18 @@ static int pcm_hw_params(struct snd_pcm_substream *substream,
|
|||
struct snd_dg00x *dg00x = substream->private_data;
|
||||
int err;
|
||||
|
||||
err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
|
||||
params_buffer_bytes(hw_params));
|
||||
err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
|
||||
unsigned int rate = params_rate(hw_params);
|
||||
unsigned int frames_per_period = params_period_size(hw_params);
|
||||
unsigned int frames_per_buffer = params_buffer_size(hw_params);
|
||||
|
||||
mutex_lock(&dg00x->mutex);
|
||||
err = snd_dg00x_stream_reserve_duplex(dg00x, rate);
|
||||
err = snd_dg00x_stream_reserve_duplex(dg00x, rate,
|
||||
frames_per_period, frames_per_buffer);
|
||||
if (err >= 0)
|
||||
++dg00x->substreams_counter;
|
||||
mutex_unlock(&dg00x->mutex);
|
||||
|
@ -191,7 +223,7 @@ static int pcm_hw_free(struct snd_pcm_substream *substream)
|
|||
|
||||
mutex_unlock(&dg00x->mutex);
|
||||
|
||||
return snd_pcm_lib_free_vmalloc_buffer(substream);
|
||||
return snd_pcm_lib_free_pages(substream);
|
||||
}
|
||||
|
||||
static int pcm_capture_prepare(struct snd_pcm_substream *substream)
|
||||
|
@ -268,28 +300,28 @@ static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstrm)
|
|||
{
|
||||
struct snd_dg00x *dg00x = sbstrm->private_data;
|
||||
|
||||
return amdtp_stream_pcm_pointer(&dg00x->tx_stream);
|
||||
return amdtp_domain_stream_pcm_pointer(&dg00x->domain, &dg00x->tx_stream);
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstrm)
|
||||
{
|
||||
struct snd_dg00x *dg00x = sbstrm->private_data;
|
||||
|
||||
return amdtp_stream_pcm_pointer(&dg00x->rx_stream);
|
||||
return amdtp_domain_stream_pcm_pointer(&dg00x->domain, &dg00x->rx_stream);
|
||||
}
|
||||
|
||||
static int pcm_capture_ack(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_dg00x *dg00x = substream->private_data;
|
||||
|
||||
return amdtp_stream_pcm_ack(&dg00x->tx_stream);
|
||||
return amdtp_domain_stream_pcm_ack(&dg00x->domain, &dg00x->tx_stream);
|
||||
}
|
||||
|
||||
static int pcm_playback_ack(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_dg00x *dg00x = substream->private_data;
|
||||
|
||||
return amdtp_stream_pcm_ack(&dg00x->rx_stream);
|
||||
return amdtp_domain_stream_pcm_ack(&dg00x->domain, &dg00x->rx_stream);
|
||||
}
|
||||
|
||||
int snd_dg00x_create_pcm_devices(struct snd_dg00x *dg00x)
|
||||
|
@ -304,7 +336,6 @@ int snd_dg00x_create_pcm_devices(struct snd_dg00x *dg00x)
|
|||
.trigger = pcm_capture_trigger,
|
||||
.pointer = pcm_capture_pointer,
|
||||
.ack = pcm_capture_ack,
|
||||
.page = snd_pcm_lib_get_vmalloc_page,
|
||||
};
|
||||
static const struct snd_pcm_ops playback_ops = {
|
||||
.open = pcm_open,
|
||||
|
@ -316,7 +347,6 @@ int snd_dg00x_create_pcm_devices(struct snd_dg00x *dg00x)
|
|||
.trigger = pcm_playback_trigger,
|
||||
.pointer = pcm_playback_pointer,
|
||||
.ack = pcm_playback_ack,
|
||||
.page = snd_pcm_lib_get_vmalloc_page,
|
||||
};
|
||||
struct snd_pcm *pcm;
|
||||
int err;
|
||||
|
@ -330,6 +360,8 @@ int snd_dg00x_create_pcm_devices(struct snd_dg00x *dg00x)
|
|||
"%s PCM", dg00x->card->shortname);
|
||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops);
|
||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_ops);
|
||||
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_VMALLOC,
|
||||
NULL, 0, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -283,7 +283,9 @@ void snd_dg00x_stream_destroy_duplex(struct snd_dg00x *dg00x)
|
|||
destroy_stream(dg00x, &dg00x->tx_stream);
|
||||
}
|
||||
|
||||
int snd_dg00x_stream_reserve_duplex(struct snd_dg00x *dg00x, unsigned int rate)
|
||||
int snd_dg00x_stream_reserve_duplex(struct snd_dg00x *dg00x, unsigned int rate,
|
||||
unsigned int frames_per_period,
|
||||
unsigned int frames_per_buffer)
|
||||
{
|
||||
unsigned int curr_rate;
|
||||
int err;
|
||||
|
@ -315,6 +317,14 @@ int snd_dg00x_stream_reserve_duplex(struct snd_dg00x *dg00x, unsigned int rate)
|
|||
fw_iso_resources_free(&dg00x->rx_resources);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = amdtp_domain_set_events_per_period(&dg00x->domain,
|
||||
frames_per_period, frames_per_buffer);
|
||||
if (err < 0) {
|
||||
fw_iso_resources_free(&dg00x->rx_resources);
|
||||
fw_iso_resources_free(&dg00x->tx_resources);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -365,7 +375,7 @@ int snd_dg00x_stream_start_duplex(struct snd_dg00x *dg00x)
|
|||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
err = amdtp_domain_start(&dg00x->domain);
|
||||
err = amdtp_domain_start(&dg00x->domain, 0);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
|
|
|
@ -141,7 +141,9 @@ int snd_dg00x_stream_get_clock(struct snd_dg00x *dg00x,
|
|||
int snd_dg00x_stream_check_external_clock(struct snd_dg00x *dg00x,
|
||||
bool *detect);
|
||||
int snd_dg00x_stream_init_duplex(struct snd_dg00x *dg00x);
|
||||
int snd_dg00x_stream_reserve_duplex(struct snd_dg00x *dg00x, unsigned int rate);
|
||||
int snd_dg00x_stream_reserve_duplex(struct snd_dg00x *dg00x, unsigned int rate,
|
||||
unsigned int frames_per_period,
|
||||
unsigned int frames_per_buffer);
|
||||
int snd_dg00x_stream_start_duplex(struct snd_dg00x *dg00x);
|
||||
void snd_dg00x_stream_stop_duplex(struct snd_dg00x *dg00x);
|
||||
void snd_dg00x_stream_update_duplex(struct snd_dg00x *dg00x);
|
||||
|
|
|
@ -139,6 +139,7 @@ static int pcm_init_hw_params(struct snd_ff *ff,
|
|||
static int pcm_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_ff *ff = substream->private_data;
|
||||
struct amdtp_domain *d = &ff->domain;
|
||||
unsigned int rate;
|
||||
enum snd_ff_clock_src src;
|
||||
int i, err;
|
||||
|
@ -155,16 +156,21 @@ static int pcm_open(struct snd_pcm_substream *substream)
|
|||
if (err < 0)
|
||||
goto release_lock;
|
||||
|
||||
mutex_lock(&ff->mutex);
|
||||
|
||||
// When source of clock is not internal or any stream is reserved for
|
||||
// transmission of PCM frames, the available sampling rate is limited
|
||||
// at current one.
|
||||
if (src != SND_FF_CLOCK_SRC_INTERNAL) {
|
||||
for (i = 0; i < CIP_SFC_COUNT; ++i) {
|
||||
if (amdtp_rate_table[i] == rate)
|
||||
break;
|
||||
}
|
||||
/*
|
||||
* The unit is configured at sampling frequency which packet
|
||||
* streaming engine can't support.
|
||||
*/
|
||||
|
||||
// The unit is configured at sampling frequency which packet
|
||||
// streaming engine can't support.
|
||||
if (i >= CIP_SFC_COUNT) {
|
||||
mutex_unlock(&ff->mutex);
|
||||
err = -EIO;
|
||||
goto release_lock;
|
||||
}
|
||||
|
@ -172,14 +178,34 @@ static int pcm_open(struct snd_pcm_substream *substream)
|
|||
substream->runtime->hw.rate_min = rate;
|
||||
substream->runtime->hw.rate_max = rate;
|
||||
} else {
|
||||
if (amdtp_stream_pcm_running(&ff->rx_stream) ||
|
||||
amdtp_stream_pcm_running(&ff->tx_stream)) {
|
||||
if (ff->substreams_counter > 0) {
|
||||
unsigned int frames_per_period = d->events_per_period;
|
||||
unsigned int frames_per_buffer = d->events_per_buffer;
|
||||
|
||||
rate = amdtp_rate_table[ff->rx_stream.sfc];
|
||||
substream->runtime->hw.rate_min = rate;
|
||||
substream->runtime->hw.rate_max = rate;
|
||||
|
||||
err = snd_pcm_hw_constraint_minmax(substream->runtime,
|
||||
SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
|
||||
frames_per_period, frames_per_period);
|
||||
if (err < 0) {
|
||||
mutex_unlock(&ff->mutex);
|
||||
goto release_lock;
|
||||
}
|
||||
|
||||
err = snd_pcm_hw_constraint_minmax(substream->runtime,
|
||||
SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
|
||||
frames_per_buffer, frames_per_buffer);
|
||||
if (err < 0) {
|
||||
mutex_unlock(&ff->mutex);
|
||||
goto release_lock;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&ff->mutex);
|
||||
|
||||
snd_pcm_set_sync(substream);
|
||||
|
||||
return 0;
|
||||
|
@ -204,16 +230,18 @@ static int pcm_hw_params(struct snd_pcm_substream *substream,
|
|||
struct snd_ff *ff = substream->private_data;
|
||||
int err;
|
||||
|
||||
err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
|
||||
params_buffer_bytes(hw_params));
|
||||
err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
|
||||
unsigned int rate = params_rate(hw_params);
|
||||
unsigned int frames_per_period = params_period_size(hw_params);
|
||||
unsigned int frames_per_buffer = params_buffer_size(hw_params);
|
||||
|
||||
mutex_lock(&ff->mutex);
|
||||
err = snd_ff_stream_reserve_duplex(ff, rate);
|
||||
err = snd_ff_stream_reserve_duplex(ff, rate, frames_per_period,
|
||||
frames_per_buffer);
|
||||
if (err >= 0)
|
||||
++ff->substreams_counter;
|
||||
mutex_unlock(&ff->mutex);
|
||||
|
@ -235,7 +263,7 @@ static int pcm_hw_free(struct snd_pcm_substream *substream)
|
|||
|
||||
mutex_unlock(&ff->mutex);
|
||||
|
||||
return snd_pcm_lib_free_vmalloc_buffer(substream);
|
||||
return snd_pcm_lib_free_pages(substream);
|
||||
}
|
||||
|
||||
static int pcm_capture_prepare(struct snd_pcm_substream *substream)
|
||||
|
@ -312,28 +340,28 @@ static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstrm)
|
|||
{
|
||||
struct snd_ff *ff = sbstrm->private_data;
|
||||
|
||||
return amdtp_stream_pcm_pointer(&ff->tx_stream);
|
||||
return amdtp_domain_stream_pcm_pointer(&ff->domain, &ff->tx_stream);
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstrm)
|
||||
{
|
||||
struct snd_ff *ff = sbstrm->private_data;
|
||||
|
||||
return amdtp_stream_pcm_pointer(&ff->rx_stream);
|
||||
return amdtp_domain_stream_pcm_pointer(&ff->domain, &ff->rx_stream);
|
||||
}
|
||||
|
||||
static int pcm_capture_ack(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_ff *ff = substream->private_data;
|
||||
|
||||
return amdtp_stream_pcm_ack(&ff->tx_stream);
|
||||
return amdtp_domain_stream_pcm_ack(&ff->domain, &ff->tx_stream);
|
||||
}
|
||||
|
||||
static int pcm_playback_ack(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_ff *ff = substream->private_data;
|
||||
|
||||
return amdtp_stream_pcm_ack(&ff->rx_stream);
|
||||
return amdtp_domain_stream_pcm_ack(&ff->domain, &ff->rx_stream);
|
||||
}
|
||||
|
||||
int snd_ff_create_pcm_devices(struct snd_ff *ff)
|
||||
|
@ -348,7 +376,6 @@ int snd_ff_create_pcm_devices(struct snd_ff *ff)
|
|||
.trigger = pcm_capture_trigger,
|
||||
.pointer = pcm_capture_pointer,
|
||||
.ack = pcm_capture_ack,
|
||||
.page = snd_pcm_lib_get_vmalloc_page,
|
||||
};
|
||||
static const struct snd_pcm_ops pcm_playback_ops = {
|
||||
.open = pcm_open,
|
||||
|
@ -360,7 +387,6 @@ int snd_ff_create_pcm_devices(struct snd_ff *ff)
|
|||
.trigger = pcm_playback_trigger,
|
||||
.pointer = pcm_playback_pointer,
|
||||
.ack = pcm_playback_ack,
|
||||
.page = snd_pcm_lib_get_vmalloc_page,
|
||||
};
|
||||
struct snd_pcm *pcm;
|
||||
int err;
|
||||
|
@ -374,6 +400,8 @@ int snd_ff_create_pcm_devices(struct snd_ff *ff)
|
|||
"%s PCM", ff->card->shortname);
|
||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_playback_ops);
|
||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pcm_capture_ops);
|
||||
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_VMALLOC,
|
||||
NULL, 0, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -106,7 +106,9 @@ void snd_ff_stream_destroy_duplex(struct snd_ff *ff)
|
|||
destroy_stream(ff, &ff->tx_stream);
|
||||
}
|
||||
|
||||
int snd_ff_stream_reserve_duplex(struct snd_ff *ff, unsigned int rate)
|
||||
int snd_ff_stream_reserve_duplex(struct snd_ff *ff, unsigned int rate,
|
||||
unsigned int frames_per_period,
|
||||
unsigned int frames_per_buffer)
|
||||
{
|
||||
unsigned int curr_rate;
|
||||
enum snd_ff_clock_src src;
|
||||
|
@ -150,6 +152,14 @@ int snd_ff_stream_reserve_duplex(struct snd_ff *ff, unsigned int rate)
|
|||
err = ff->spec->protocol->allocate_resources(ff, rate);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = amdtp_domain_set_events_per_period(&ff->domain,
|
||||
frames_per_period, frames_per_buffer);
|
||||
if (err < 0) {
|
||||
fw_iso_resources_free(&ff->tx_resources);
|
||||
fw_iso_resources_free(&ff->rx_resources);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -174,6 +184,7 @@ int snd_ff_stream_start_duplex(struct snd_ff *ff, unsigned int rate)
|
|||
*/
|
||||
if (!amdtp_stream_running(&ff->rx_stream)) {
|
||||
int spd = fw_parent_device(ff->unit)->max_speed;
|
||||
unsigned int ir_delay_cycle;
|
||||
|
||||
err = ff->spec->protocol->begin_session(ff, rate);
|
||||
if (err < 0)
|
||||
|
@ -189,7 +200,14 @@ int snd_ff_stream_start_duplex(struct snd_ff *ff, unsigned int rate)
|
|||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
err = amdtp_domain_start(&ff->domain);
|
||||
// The device postpones start of transmission mostly for several
|
||||
// cycles after receiving packets firstly.
|
||||
if (ff->spec->protocol == &snd_ff_protocol_ff800)
|
||||
ir_delay_cycle = 800; // = 100 msec
|
||||
else
|
||||
ir_delay_cycle = 16; // = 2 msec
|
||||
|
||||
err = amdtp_domain_start(&ff->domain, ir_delay_cycle);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
|
|
|
@ -139,7 +139,9 @@ int snd_ff_stream_get_multiplier_mode(enum cip_sfc sfc,
|
|||
enum snd_ff_stream_mode *mode);
|
||||
int snd_ff_stream_init_duplex(struct snd_ff *ff);
|
||||
void snd_ff_stream_destroy_duplex(struct snd_ff *ff);
|
||||
int snd_ff_stream_reserve_duplex(struct snd_ff *ff, unsigned int rate);
|
||||
int snd_ff_stream_reserve_duplex(struct snd_ff *ff, unsigned int rate,
|
||||
unsigned int frames_per_period,
|
||||
unsigned int frames_per_buffer);
|
||||
int snd_ff_stream_start_duplex(struct snd_ff *ff, unsigned int rate);
|
||||
void snd_ff_stream_stop_duplex(struct snd_ff *ff);
|
||||
void snd_ff_stream_update_duplex(struct snd_ff *ff);
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue