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:
85
Documentation/devicetree/bindings/sound/adi,adau7118.yaml
Normal file
85
Documentation/devicetree/bindings/sound/adi,adau7118.yaml
Normal file
@@ -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
|
Audio Binding for Arndale boards
|
||||||
|
|
||||||
Required properties:
|
Required properties:
|
||||||
- compatible : Can be the following,
|
- compatible : Can be one of the following:
|
||||||
"samsung,arndale-rt5631"
|
"samsung,arndale-rt5631",
|
||||||
|
"samsung,arndale-wm1811"
|
||||||
|
|
||||||
- samsung,audio-cpu: The phandle of the Samsung I2S controller
|
- samsung,audio-cpu: The phandle of the Samsung I2S controller
|
||||||
- samsung,audio-codec: The phandle of the audio codec
|
- samsung,audio-codec: The phandle of the audio codec
|
||||||
|
36
Documentation/devicetree/bindings/sound/fsl,mqs.txt
Normal file
36
Documentation/devicetree/bindings/sound/fsl,mqs.txt
Normal file
@@ -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
|
Google's ChromeOS EC codec is a digital mic codec provided by the
|
||||||
Embedded Controller (EC) and is controlled via a host-command interface.
|
Embedded Controller (EC) and is controlled via a host-command interface.
|
||||||
@@ -9,10 +9,27 @@ Documentation/devicetree/bindings/mfd/cros-ec.txt).
|
|||||||
Required properties:
|
Required properties:
|
||||||
- compatible: Must contain "google,cros-ec-codec"
|
- compatible: Must contain "google,cros-ec-codec"
|
||||||
- #sound-dai-cells: Should be 1. The cell specifies number of DAIs.
|
- #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:
|
Example:
|
||||||
|
|
||||||
|
{
|
||||||
|
...
|
||||||
|
|
||||||
|
reserved_mem: reserved_mem {
|
||||||
|
compatible = "shared-dma-pool";
|
||||||
|
reg = <0 0x52800000 0 0x100000>;
|
||||||
|
no-map;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
cros-ec@0 {
|
cros-ec@0 {
|
||||||
compatible = "google,cros-ec-spi";
|
compatible = "google,cros-ec-spi";
|
||||||
|
|
||||||
@@ -21,6 +38,7 @@ cros-ec@0 {
|
|||||||
cros_ec_codec: ec-codec {
|
cros_ec_codec: ec-codec {
|
||||||
compatible = "google,cros-ec-codec";
|
compatible = "google,cros-ec-codec";
|
||||||
#sound-dai-cells = <1>;
|
#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";
|
- compatible = "mediatek,mt68183-audio";
|
||||||
- reg: register location and size
|
- reg: register location and size
|
||||||
- interrupts: should contain AFE interrupt
|
- 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
|
- power-domains: should define the power domain
|
||||||
- clocks: Must contain an entry for each entry in clock-names
|
- clocks: Must contain an entry for each entry in clock-names
|
||||||
- clock-names: should have these clock names:
|
- clock-names: should have these clock names:
|
||||||
@@ -20,6 +24,8 @@ Example:
|
|||||||
compatible = "mediatek,mt8183-audio";
|
compatible = "mediatek,mt8183-audio";
|
||||||
reg = <0 0x11220000 0 0x1000>;
|
reg = <0 0x11220000 0 0x1000>;
|
||||||
interrupts = <GIC_SPI 161 IRQ_TYPE_LEVEL_LOW>;
|
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>;
|
power-domains = <&scpsys MT8183_POWER_DOMAIN_AUDIO>;
|
||||||
clocks = <&infrasys CLK_INFRA_AUDIO>,
|
clocks = <&infrasys CLK_INFRA_AUDIO>,
|
||||||
<&infrasys CLK_INFRA_AUDIO_26M_BCLK>,
|
<&infrasys CLK_INFRA_AUDIO_26M_BCLK>,
|
||||||
|
@@ -2,14 +2,19 @@ MT8183 with MT6358, TS3A227 and MAX98357 CODECS
|
|||||||
|
|
||||||
Required properties:
|
Required properties:
|
||||||
- compatible : "mediatek,mt8183_mt6358_ts3a227_max98357"
|
- compatible : "mediatek,mt8183_mt6358_ts3a227_max98357"
|
||||||
- mediatek,headset-codec: the phandles of ts3a227 codecs
|
|
||||||
- mediatek,platform: the phandle of MT8183 ASoC platform
|
- 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:
|
Example:
|
||||||
|
|
||||||
sound {
|
sound {
|
||||||
compatible = "mediatek,mt8183_mt6358_ts3a227_max98357";
|
compatible = "mediatek,mt8183_mt6358_ts3a227_max98357";
|
||||||
mediatek,headset-codec = <&ts3a227>;
|
mediatek,headset-codec = <&ts3a227>;
|
||||||
|
mediatek,ec-codec = <&ec_codec>;
|
||||||
mediatek,platform = <&afe>;
|
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;
|
|
||||||
};
|
|
76
Documentation/devicetree/bindings/sound/renesas,fsi.yaml
Normal file
76
Documentation/devicetree/bindings/sound/renesas,fsi.yaml
Normal file
@@ -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-r8a7745" (RZ/G1E)
|
||||||
- "renesas,rcar_sound-r8a77470" (RZ/G1C)
|
- "renesas,rcar_sound-r8a77470" (RZ/G1C)
|
||||||
- "renesas,rcar_sound-r8a774a1" (RZ/G2M)
|
- "renesas,rcar_sound-r8a774a1" (RZ/G2M)
|
||||||
|
- "renesas,rcar_sound-r8a774b1" (RZ/G2N)
|
||||||
- "renesas,rcar_sound-r8a774c0" (RZ/G2E)
|
- "renesas,rcar_sound-r8a774c0" (RZ/G2E)
|
||||||
- "renesas,rcar_sound-r8a7778" (R-Car M1A)
|
- "renesas,rcar_sound-r8a7778" (R-Car M1A)
|
||||||
- "renesas,rcar_sound-r8a7779" (R-Car H1)
|
- "renesas,rcar_sound-r8a7779" (R-Car H1)
|
||||||
|
@@ -5,11 +5,16 @@ Required properties:
|
|||||||
- rockchip,model: The user-visible name of this sound complex
|
- rockchip,model: The user-visible name of this sound complex
|
||||||
- rockchip,i2s-controller: The phandle of the Rockchip I2S controller that's
|
- rockchip,i2s-controller: The phandle of the Rockchip I2S controller that's
|
||||||
connected to the CODEC
|
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:
|
Example:
|
||||||
|
|
||||||
|
/* For max98090-only board. */
|
||||||
sound {
|
sound {
|
||||||
compatible = "rockchip,rockchip-audio-max98090";
|
compatible = "rockchip,rockchip-audio-max98090";
|
||||||
rockchip,model = "ROCKCHIP-I2S";
|
rockchip,model = "ROCKCHIP-I2S";
|
||||||
@@ -17,3 +22,21 @@ sound {
|
|||||||
rockchip,audio-codec = <&max98090>;
|
rockchip,audio-codec = <&max98090>;
|
||||||
rockchip,headset-codec = <&headsetcodec>;
|
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 |
|
| 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:
|
Pins on the device (for linking into audio routes) for RT1011:
|
||||||
|
|
||||||
* SPO
|
* SPO
|
||||||
@@ -29,4 +37,6 @@ Example:
|
|||||||
rt1011: codec@38 {
|
rt1011: codec@38 {
|
||||||
compatible = "realtek,rt1011";
|
compatible = "realtek,rt1011";
|
||||||
reg = <0x38>;
|
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,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:
|
Pins on the device (for linking into audio routes) for RT5682:
|
||||||
|
|
||||||
* DMIC L1
|
* DMIC L1
|
||||||
@@ -47,4 +52,5 @@ rt5682 {
|
|||||||
realtek,dmic1-data-pin = <1>;
|
realtek,dmic1-data-pin = <1>;
|
||||||
realtek,dmic1-clk-pin = <1>;
|
realtek,dmic1-clk-pin = <1>;
|
||||||
realtek,jd-src = <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>;
|
|
||||||
};
|
|
||||||
};
|
|
91
Documentation/devicetree/bindings/sound/samsung,odroid.yaml
Normal file
91
Documentation/devicetree/bindings/sound/samsung,odroid.yaml
Normal file
@@ -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>;
|
|
||||||
};
|
|
138
Documentation/devicetree/bindings/sound/samsung-i2s.yaml
Normal file
138
Documentation/devicetree/bindings/sound/samsung-i2s.yaml
Normal file
@@ -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";
|
|
||||||
};
|
|
||||||
};
|
|
34
Documentation/devicetree/bindings/sound/tas2562.txt
Normal file
34
Documentation/devicetree/bindings/sound/tas2562.txt
Normal file
@@ -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>;
|
||||||
|
};
|
||||||
|
|
37
Documentation/devicetree/bindings/sound/tas2770.txt
Normal file
37
Documentation/devicetree/bindings/sound/tas2770.txt
Normal file
@@ -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
|
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:
|
Examples:
|
||||||
|
|
||||||
i2c0: i2c0@0 {
|
i2c0: i2c0@0 {
|
||||||
@@ -34,6 +41,7 @@ i2c0: i2c0@0 {
|
|||||||
pcm3168a: audio-codec@44 {
|
pcm3168a: audio-codec@44 {
|
||||||
compatible = "ti,pcm3168a";
|
compatible = "ti,pcm3168a";
|
||||||
reg = <0x44>;
|
reg = <0x44>;
|
||||||
|
reset-gpios = <&gpio0 4 GPIO_ACTIVE_LOW>;
|
||||||
clocks = <&clk_core CLK_AUDIO>;
|
clocks = <&clk_core CLK_AUDIO>;
|
||||||
clock-names = "scki";
|
clock-names = "scki";
|
||||||
VDD1-supply = <&supply3v3>;
|
VDD1-supply = <&supply3v3>;
|
||||||
|
@@ -29,6 +29,11 @@ Optional properties:
|
|||||||
3 or MICBIAS_AVDD - MICBIAS output is connected to AVDD
|
3 or MICBIAS_AVDD - MICBIAS output is connected to AVDD
|
||||||
If this node is not mentioned or if the value is unknown, then
|
If this node is not mentioned or if the value is unknown, then
|
||||||
micbias is set to 2.0V.
|
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:
|
Deprecated properties:
|
||||||
|
|
||||||
|
@@ -16,7 +16,7 @@ properties: {}
|
|||||||
patternProperties:
|
patternProperties:
|
||||||
# Prefixes which are not vendors, but followed the pattern
|
# Prefixes which are not vendors, but followed the pattern
|
||||||
# DO NOT ADD NEW PROPERTIES TO THIS LIST
|
# 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
|
"^(keypad|m25p|max8952|max8997|max8998|mpmc),.*": true
|
||||||
"^(pinctrl-single|#pinctrl-single|PowerPC),.*": true
|
"^(pinctrl-single|#pinctrl-single|PowerPC),.*": true
|
||||||
"^(pl022|pxa-mmc|rcar_sound|rotary-encoder|s5m8767|sdhci),.*": 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;
|
return -EBUSY;
|
||||||
}
|
}
|
||||||
chip->irq = pci->irq;
|
chip->irq = pci->irq;
|
||||||
|
card->sync_irq = chip->irq;
|
||||||
|
|
||||||
/* (2) initialization of the chip hardware */
|
/* (2) initialization of the chip hardware */
|
||||||
.... /* (not implemented in this document) */
|
.... /* (not implemented in this document) */
|
||||||
@@ -965,6 +966,15 @@ usually like the following:
|
|||||||
return IRQ_HANDLED;
|
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.
|
Now let's write the corresponding destructor for the resources above.
|
||||||
The role of destructor is simple: disable the hardware (if already
|
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 */
|
/* the hardware-specific codes will be here */
|
||||||
....
|
....
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* hw_params callback */
|
/* hw_params callback */
|
||||||
static int snd_mychip_pcm_hw_params(struct snd_pcm_substream *substream,
|
static int snd_mychip_pcm_hw_params(struct snd_pcm_substream *substream,
|
||||||
struct snd_pcm_hw_params *hw_params)
|
struct snd_pcm_hw_params *hw_params)
|
||||||
{
|
{
|
||||||
return snd_pcm_lib_malloc_pages(substream,
|
/* the hardware-specific codes will be here */
|
||||||
params_buffer_bytes(hw_params));
|
....
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* hw_free callback */
|
/* hw_free callback */
|
||||||
static int snd_mychip_pcm_hw_free(struct snd_pcm_substream *substream)
|
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 */
|
/* 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 = {
|
static struct snd_pcm_ops snd_mychip_playback_ops = {
|
||||||
.open = snd_mychip_playback_open,
|
.open = snd_mychip_playback_open,
|
||||||
.close = snd_mychip_playback_close,
|
.close = snd_mychip_playback_close,
|
||||||
.ioctl = snd_pcm_lib_ioctl,
|
|
||||||
.hw_params = snd_mychip_pcm_hw_params,
|
.hw_params = snd_mychip_pcm_hw_params,
|
||||||
.hw_free = snd_mychip_pcm_hw_free,
|
.hw_free = snd_mychip_pcm_hw_free,
|
||||||
.prepare = snd_mychip_pcm_prepare,
|
.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 = {
|
static struct snd_pcm_ops snd_mychip_capture_ops = {
|
||||||
.open = snd_mychip_capture_open,
|
.open = snd_mychip_capture_open,
|
||||||
.close = snd_mychip_capture_close,
|
.close = snd_mychip_capture_close,
|
||||||
.ioctl = snd_pcm_lib_ioctl,
|
|
||||||
.hw_params = snd_mychip_pcm_hw_params,
|
.hw_params = snd_mychip_pcm_hw_params,
|
||||||
.hw_free = snd_mychip_pcm_hw_free,
|
.hw_free = snd_mychip_pcm_hw_free,
|
||||||
.prepare = snd_mychip_pcm_prepare,
|
.prepare = snd_mychip_pcm_prepare,
|
||||||
@@ -1382,9 +1392,9 @@ shows only the skeleton, how to build up the PCM interfaces.
|
|||||||
&snd_mychip_capture_ops);
|
&snd_mychip_capture_ops);
|
||||||
/* pre-allocation of buffers */
|
/* pre-allocation of buffers */
|
||||||
/* NOTE: this may fail */
|
/* NOTE: this may fail */
|
||||||
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
|
snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
|
||||||
snd_dma_pci_data(chip->pci),
|
&chip->pci->dev,
|
||||||
64*1024, 64*1024);
|
64*1024, 64*1024);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1454,7 +1464,6 @@ The operators are defined typically like this:
|
|||||||
static struct snd_pcm_ops snd_mychip_playback_ops = {
|
static struct snd_pcm_ops snd_mychip_playback_ops = {
|
||||||
.open = snd_mychip_pcm_open,
|
.open = snd_mychip_pcm_open,
|
||||||
.close = snd_mychip_pcm_close,
|
.close = snd_mychip_pcm_close,
|
||||||
.ioctl = snd_pcm_lib_ioctl,
|
|
||||||
.hw_params = snd_mychip_pcm_hw_params,
|
.hw_params = snd_mychip_pcm_hw_params,
|
||||||
.hw_free = snd_mychip_pcm_hw_free,
|
.hw_free = snd_mychip_pcm_hw_free,
|
||||||
.prepare = snd_mychip_pcm_prepare,
|
.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.
|
All the callbacks are described in the Operators_ subsection.
|
||||||
|
|
||||||
After setting the operators, you probably will want to pre-allocate the
|
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_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
|
||||||
snd_dma_pci_data(chip->pci),
|
&chip->pci->dev,
|
||||||
64*1024, 64*1024);
|
64*1024, 64*1024);
|
||||||
|
|
||||||
It will allocate a buffer up to 64kB as default. Buffer management
|
It will allocate a buffer up to 64kB as default. Buffer management
|
||||||
details will be described in the later section `Buffer and Memory
|
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
|
records are supposed to be read-only. Only the PCM middle-layer changes
|
||||||
/ updates them. The exceptions are the hardware description (hw) DMA
|
/ updates them. The exceptions are the hardware description (hw) DMA
|
||||||
buffer information and the private data. Besides, if you use the
|
buffer information and the private data. Besides, if you use the
|
||||||
standard buffer allocation method via
|
standard managed buffer allocation mode, you don't need to set the
|
||||||
:c:func:`snd_pcm_lib_malloc_pages()`, you don't need to set the
|
|
||||||
DMA buffer information by yourself.
|
DMA buffer information by yourself.
|
||||||
|
|
||||||
In the sections below, important records are explained.
|
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
|
the buffer is a linear buffer. ``dma_bytes`` holds the size of buffer
|
||||||
in bytes. ``dma_private`` is used for the ALSA DMA allocator.
|
in bytes. ``dma_private`` is used for the ALSA DMA allocator.
|
||||||
|
|
||||||
If you use a standard ALSA function,
|
If you use either the managed buffer allocation mode or the standard
|
||||||
:c:func:`snd_pcm_lib_malloc_pages()`, for allocating the buffer,
|
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*
|
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
|
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
|
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
|
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
|
hw_params callback
|
||||||
~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~
|
||||||
@@ -1929,8 +1941,12 @@ Many hardware setups should be done in this callback, including the
|
|||||||
allocation of buffers.
|
allocation of buffers.
|
||||||
|
|
||||||
Parameters to be initialized are retrieved by
|
Parameters to be initialized are retrieved by
|
||||||
:c:func:`params_xxx()` macros. To allocate buffer, you can call a
|
:c:func:`params_xxx()` macros.
|
||||||
helper function,
|
|
||||||
|
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);
|
static int snd_xxx_hw_free(struct snd_pcm_substream *substream);
|
||||||
|
|
||||||
This is called to release the resources allocated via
|
This is called to release the resources allocated via
|
||||||
``hw_params``. For example, releasing the buffer via
|
``hw_params``.
|
||||||
:c:func:`snd_pcm_lib_malloc_pages()` is done by calling the
|
|
||||||
following:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
snd_pcm_lib_free_pages(substream);
|
|
||||||
|
|
||||||
This function is always called before the close callback is called.
|
This function is always called before the close callback is called.
|
||||||
Also, the callback may be called multiple times, too. Keep track
|
Also, the callback may be called multiple times, too. Keep track
|
||||||
whether the resource was already released.
|
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
|
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
|
triggering the DMA. The other stuff should be initialized
|
||||||
``hw_params`` and ``prepare`` callbacks properly beforehand.
|
``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
|
pointer callback
|
||||||
~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
@@ -2095,10 +2147,12 @@ This callback is atomic as default.
|
|||||||
page callback
|
page callback
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
This callback is optional too. This callback is used mainly for
|
This callback is optional too. The mmap calls this callback to get the
|
||||||
non-contiguous buffers. The mmap calls this callback to get the page
|
page fault address.
|
||||||
address. Some examples will be explained in the later section `Buffer
|
|
||||||
and Memory Management`_, too.
|
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
|
mmap calllback
|
||||||
~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~
|
||||||
@@ -3512,7 +3566,7 @@ bus).
|
|||||||
::
|
::
|
||||||
|
|
||||||
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
|
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
|
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
|
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
|
dependent on the bus. For normal devices, pass the device pointer
|
||||||
(typically identical as ``card->dev``) to the third argument with
|
(typically identical as ``card->dev``) to the third argument with
|
||||||
``SNDRV_DMA_TYPE_DEV`` type. For the continuous buffer unrelated to the
|
``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
|
bus can be pre-allocated with ``SNDRV_DMA_TYPE_CONTINUOUS`` type.
|
||||||
``snd_dma_continuous_data(GFP_KERNEL)`` device pointer, where
|
You can pass NULL to the device pointer in that case, which is the
|
||||||
``GFP_KERNEL`` is the kernel allocation flag to use. For the
|
default mode implying to allocate with ``GFP_KRENEL`` flag.
|
||||||
scatter-gather buffers, use ``SNDRV_DMA_TYPE_DEV_SG`` with the device
|
If you need a different GFP flag, you can pass it by encoding the flag
|
||||||
pointer (see the `Non-Contiguous Buffers`_
|
into the device pointer via a special macro
|
||||||
section).
|
: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
|
Once the buffer is pre-allocated, you can use the allocator in the
|
||||||
``hw_params`` callback:
|
``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.
|
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
|
External Hardware Buffers
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
@@ -3693,20 +3768,26 @@ provides an interface for handling SG-buffers. The API is provided in
|
|||||||
``<sound/pcm.h>``.
|
``<sound/pcm.h>``.
|
||||||
|
|
||||||
For creating the SG-buffer handler, call
|
For creating the SG-buffer handler, call
|
||||||
:c:func:`snd_pcm_lib_preallocate_pages()` or
|
:c:func:`snd_pcm_set_managed_buffer()` or
|
||||||
:c:func:`snd_pcm_lib_preallocate_pages_for_all()` with
|
:c:func:`snd_pcm_set_managed_buffer_all()` with
|
||||||
``SNDRV_DMA_TYPE_DEV_SG`` in the PCM constructor like other PCI
|
``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
|
the :c:type:`struct pci_dev <pci_dev>` pointer of the chip as
|
||||||
well. The ``struct snd_sg_buf`` instance is created as
|
well.
|
||||||
``substream->dma_private``. You can cast the pointer like:
|
|
||||||
|
::
|
||||||
|
|
||||||
|
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;
|
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``
|
Then in :c:func:`snd_pcm_lib_malloc_pages()` call, the common SG-buffer
|
||||||
callback as well as in the case of normal PCI buffer. The SG-buffer
|
|
||||||
handler will allocate the non-contiguous kernel pages of the given size
|
handler will allocate the non-contiguous kernel pages of the given size
|
||||||
and map them onto the virtually contiguous memory. The virtual pointer
|
and map them onto the virtually contiguous memory. The virtual pointer
|
||||||
is addressed in runtime->dma_area. The physical address
|
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
|
``sgbuf->table``. You can get the physical address at a certain offset
|
||||||
via :c:func:`snd_pcm_sgbuf_get_addr()`.
|
via :c:func:`snd_pcm_sgbuf_get_addr()`.
|
||||||
|
|
||||||
When a SG-handler is used, you need to set
|
If you need to release the SG-buffer data explicitly, call the
|
||||||
:c:func:`snd_pcm_sgbuf_ops_page()` as the ``page`` callback. (See
|
standard API function :c:func:`snd_pcm_lib_free_pages()` as usual.
|
||||||
`page callback`_ section.)
|
|
||||||
|
|
||||||
To release the data, call :c:func:`snd_pcm_lib_free_pages()` in
|
|
||||||
the ``hw_free`` callback as usual.
|
|
||||||
|
|
||||||
Vmalloc'ed Buffers
|
Vmalloc'ed Buffers
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
It's possible to use a buffer allocated via :c:func:`vmalloc()`, for
|
It's possible to use a buffer allocated via :c:func:`vmalloc()`, for
|
||||||
example, for an intermediate buffer. Since the allocated pages are not
|
example, for an intermediate buffer. In the recent version of kernel,
|
||||||
contiguous, you need to set the ``page`` callback to obtain the physical
|
you can simply allocate it via standard
|
||||||
address at every offset.
|
:c:func:`snd_pcm_lib_malloc_pages()` and co after setting up the
|
||||||
|
buffer preallocation with ``SNDRV_DMA_TYPE_VMALLOC`` type.
|
||||||
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:
|
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
#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 */
|
The NULL is passed to the device pointer argument, which indicates
|
||||||
static struct page *mychip_page(struct snd_pcm_substream *substream,
|
that the default pages (GFP_KERNEL and GFP_HIGHMEM) will be
|
||||||
unsigned long offset)
|
allocated.
|
||||||
{
|
|
||||||
void *pageptr = substream->runtime->dma_area + offset;
|
Also, note that zero is passed to both the size and the max size
|
||||||
return vmalloc_to_page(pageptr);
|
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
|
Proc Interface
|
||||||
==============
|
==============
|
||||||
|
@@ -1002,6 +1002,7 @@ F: drivers/media/i2c/adv7842*
|
|||||||
|
|
||||||
ANALOG DEVICES INC ASOC CODEC DRIVERS
|
ANALOG DEVICES INC ASOC CODEC DRIVERS
|
||||||
M: Lars-Peter Clausen <lars@metafoo.de>
|
M: Lars-Peter Clausen <lars@metafoo.de>
|
||||||
|
M: Nuno Sá <nuno.sa@analog.com>
|
||||||
L: alsa-devel@alsa-project.org (moderated for non-subscribers)
|
L: alsa-devel@alsa-project.org (moderated for non-subscribers)
|
||||||
W: http://wiki.analog.com/
|
W: http://wiki.analog.com/
|
||||||
W: http://ez.analog.com/community/linux-device-drivers
|
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;
|
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 = {
|
static struct hdmi_codec_ops dw_hdmi_i2s_ops = {
|
||||||
.hw_params = dw_hdmi_i2s_hw_params,
|
.hw_params = dw_hdmi_i2s_hw_params,
|
||||||
.audio_shutdown = dw_hdmi_i2s_audio_shutdown,
|
.audio_shutdown = dw_hdmi_i2s_audio_shutdown,
|
||||||
.get_eld = dw_hdmi_i2s_get_eld,
|
.get_eld = dw_hdmi_i2s_get_eld,
|
||||||
.get_dai_id = dw_hdmi_i2s_get_dai_id,
|
.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)
|
static int snd_dw_hdmi_probe(struct platform_device *pdev)
|
||||||
|
@@ -191,6 +191,10 @@ struct dw_hdmi {
|
|||||||
|
|
||||||
struct mutex cec_notifier_mutex;
|
struct mutex cec_notifier_mutex;
|
||||||
struct cec_notifier *cec_notifier;
|
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 \
|
#define HDMI_IH_PHY_STAT0_RX_SENSE \
|
||||||
@@ -215,6 +219,28 @@ static inline u8 hdmi_readb(struct dw_hdmi *hdmi, int offset)
|
|||||||
return val;
|
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)
|
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);
|
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,
|
struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi,
|
||||||
connector);
|
connector);
|
||||||
|
enum drm_connector_status result;
|
||||||
|
|
||||||
mutex_lock(&hdmi->mutex);
|
mutex_lock(&hdmi->mutex);
|
||||||
hdmi->force = DRM_FORCE_UNSPECIFIED;
|
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);
|
dw_hdmi_update_phy_mask(hdmi);
|
||||||
mutex_unlock(&hdmi->mutex);
|
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)
|
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->rxsense = true;
|
||||||
hdmi->phy_mask = (u8)~(HDMI_PHY_HPD | HDMI_PHY_RX_SENSE);
|
hdmi->phy_mask = (u8)~(HDMI_PHY_HPD | HDMI_PHY_RX_SENSE);
|
||||||
hdmi->mc_clkdis = 0x7f;
|
hdmi->mc_clkdis = 0x7f;
|
||||||
|
hdmi->last_connector_result = connector_status_disconnected;
|
||||||
|
|
||||||
mutex_init(&hdmi->mutex);
|
mutex_init(&hdmi->mutex);
|
||||||
mutex_init(&hdmi->audio_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,
|
snd_pcm_lib_preallocate_pages_for_all(pcm,
|
||||||
SNDRV_DMA_TYPE_CONTINUOUS,
|
SNDRV_DMA_TYPE_CONTINUOUS,
|
||||||
snd_dma_continuous_data(GFP_KERNEL),
|
NULL,
|
||||||
G723_PERIOD_BYTES * PERIODS,
|
G723_PERIOD_BYTES * PERIODS,
|
||||||
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,
|
snd_pcm_lib_preallocate_pages_for_all(pcm,
|
||||||
SNDRV_DMA_TYPE_DEV,
|
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,
|
||||||
TW686X_AUDIO_PAGE_MAX * AUDIO_DMA_SIZE_MAX);
|
TW686X_AUDIO_PAGE_MAX * AUDIO_DMA_SIZE_MAX);
|
||||||
return 0;
|
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_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_usbtv_pcm_ops);
|
||||||
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
|
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
|
||||||
snd_dma_continuous_data(GFP_KERNEL), USBTV_AUDIO_BUFFER,
|
NULL, USBTV_AUDIO_BUFFER, USBTV_AUDIO_BUFFER);
|
||||||
USBTV_AUDIO_BUFFER);
|
|
||||||
|
|
||||||
rv = snd_card_register(card);
|
rv = snd_card_register(card);
|
||||||
if (rv)
|
if (rv)
|
||||||
|
@@ -5854,6 +5854,24 @@ int pci_set_vga_state(struct pci_dev *dev, bool decode,
|
|||||||
return 0;
|
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
|
* pci_add_dma_alias - Add a DMA devfn alias for a device
|
||||||
* @dev: the PCI device for which alias is added
|
* @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_READ_BLOCK), \
|
||||||
TRACE_SYMBOL(EC_CMD_SB_WRITE_BLOCK), \
|
TRACE_SYMBOL(EC_CMD_SB_WRITE_BLOCK), \
|
||||||
TRACE_SYMBOL(EC_CMD_BATTERY_VENDOR_PARAM), \
|
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_REBOOT_EC), \
|
||||||
TRACE_SYMBOL(EC_CMD_GET_PANIC_INFO), \
|
TRACE_SYMBOL(EC_CMD_GET_PANIC_INFO), \
|
||||||
TRACE_SYMBOL(EC_CMD_ACPI_READ), \
|
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");
|
pr_err("Requested number of channels not supported.\n");
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
return snd_pcm_lib_alloc_vmalloc_buffer(substream,
|
return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
|
||||||
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)
|
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,
|
.prepare = pcm_prepare,
|
||||||
.trigger = pcm_trigger,
|
.trigger = pcm_trigger,
|
||||||
.pointer = pcm_pointer,
|
.pointer = pcm_pointer,
|
||||||
.page = snd_pcm_lib_get_vmalloc_page,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static int split_arg_list(char *buf, u16 *ch_num, char **sample_res)
|
static int split_arg_list(char *buf, u16 *ch_num, char **sample_res)
|
||||||
@@ -663,6 +661,8 @@ skip_adpt_alloc:
|
|||||||
pcm->private_data = channel;
|
pcm->private_data = channel;
|
||||||
strscpy(pcm->name, device_name, sizeof(pcm->name));
|
strscpy(pcm->name, device_name, sizeof(pcm->name));
|
||||||
snd_pcm_set_ops(pcm, direction, &pcm_ops);
|
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;
|
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);
|
sprintf(card->longname, "%s %i", card_name, card->dev->id);
|
||||||
|
|
||||||
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
|
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);
|
err = snd_card_register(card);
|
||||||
|
|
||||||
|
@@ -6,6 +6,8 @@
|
|||||||
#ifndef __DW_HDMI__
|
#ifndef __DW_HDMI__
|
||||||
#define __DW_HDMI__
|
#define __DW_HDMI__
|
||||||
|
|
||||||
|
#include <sound/hdmi-codec.h>
|
||||||
|
|
||||||
struct drm_connector;
|
struct drm_connector;
|
||||||
struct drm_display_mode;
|
struct drm_display_mode;
|
||||||
struct drm_encoder;
|
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);
|
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_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_count(struct dw_hdmi *hdmi, unsigned int cnt);
|
||||||
void dw_hdmi_set_channel_allocation(struct dw_hdmi *hdmi, unsigned int ca);
|
void dw_hdmi_set_channel_allocation(struct dw_hdmi *hdmi, unsigned int ca);
|
||||||
|
@@ -2,8 +2,14 @@
|
|||||||
#ifndef _DT_BINDINGS_SAMSUNG_I2S_H
|
#ifndef _DT_BINDINGS_SAMSUNG_I2S_H
|
||||||
#define _DT_BINDINGS_SAMSUNG_I2S_H
|
#define _DT_BINDINGS_SAMSUNG_I2S_H
|
||||||
|
|
||||||
#define CLK_I2S_CDCLK 0
|
#define CLK_I2S_CDCLK 0 /* the CDCLK (CODECLKO) gate clock */
|
||||||
#define CLK_I2S_RCLK_SRC 1
|
|
||||||
#define CLK_I2S_RCLK_PSR 2
|
#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 */
|
#endif /* _DT_BINDINGS_SAMSUNG_I2S_H */
|
||||||
|
@@ -2311,9 +2311,11 @@ struct irq_domain *pci_host_bridge_acpi_msi_domain(struct pci_bus *bus);
|
|||||||
|
|
||||||
void
|
void
|
||||||
pci_msi_register_fwnode_provider(struct fwnode_handle *(*fn)(struct device *));
|
pci_msi_register_fwnode_provider(struct fwnode_handle *(*fn)(struct device *));
|
||||||
|
bool pci_pr3_present(struct pci_dev *pdev);
|
||||||
#else
|
#else
|
||||||
static inline struct irq_domain *
|
static inline struct irq_domain *
|
||||||
pci_host_bridge_acpi_msi_domain(struct pci_bus *bus) { return NULL; }
|
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
|
#endif
|
||||||
|
|
||||||
#ifdef CONFIG_EEH
|
#ifdef CONFIG_EEH
|
||||||
|
@@ -556,6 +556,9 @@ enum host_event_code {
|
|||||||
/* Keyboard recovery combo with hardware reinitialization */
|
/* Keyboard recovery combo with hardware reinitialization */
|
||||||
EC_HOST_EVENT_KEYBOARD_RECOVERY_HW_REINIT = 30,
|
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
|
* 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
|
* 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.
|
* MOTIONSENSE_CMD_TABLET_MODE_LID_ANGLE.
|
||||||
*/
|
*/
|
||||||
EC_FEATURE_REFINED_TABLET_MODE_HYSTERESIS = 37,
|
EC_FEATURE_REFINED_TABLET_MODE_HYSTERESIS = 37,
|
||||||
/* EC supports audio codec. */
|
|
||||||
EC_FEATURE_AUDIO_CODEC = 38,
|
|
||||||
/* The MCU is a System Companion Processor (SCP). */
|
/* The MCU is a System Companion Processor (SCP). */
|
||||||
EC_FEATURE_SCP = 39,
|
EC_FEATURE_SCP = 39,
|
||||||
/* The MCU is an Integrated Sensor Hub */
|
/* 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
|
enum ec_codec_subcmd {
|
||||||
#define EC_WOV_I2S_SAMPLE_RATE 48000
|
EC_CODEC_GET_CAPABILITIES = 0x0,
|
||||||
|
EC_CODEC_GET_SHM_ADDR = 0x1,
|
||||||
enum ec_codec_i2s_subcmd {
|
EC_CODEC_SET_SHM_ADDR = 0x2,
|
||||||
EC_CODEC_SET_SAMPLE_DEPTH = 0x0,
|
EC_CODEC_SUBCMD_COUNT,
|
||||||
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_sample_depth_value {
|
enum ec_codec_cap {
|
||||||
EC_CODEC_SAMPLE_DEPTH_16 = 0,
|
EC_CODEC_CAP_WOV_AUDIO_SHM = 0,
|
||||||
EC_CODEC_SAMPLE_DEPTH_24 = 1,
|
EC_CODEC_CAP_WOV_LANG_SHM = 1,
|
||||||
|
EC_CODEC_CAP_LAST = 32,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum ec_i2s_config {
|
enum ec_codec_shm_id {
|
||||||
EC_DAI_FMT_I2S = 0,
|
EC_CODEC_SHM_ID_WOV_AUDIO = 0x0,
|
||||||
EC_DAI_FMT_RIGHT_J = 1,
|
EC_CODEC_SHM_ID_WOV_LANG = 0x1,
|
||||||
EC_DAI_FMT_LEFT_J = 2,
|
EC_CODEC_SHM_ID_LAST,
|
||||||
EC_DAI_FMT_PCM_A = 3,
|
|
||||||
EC_DAI_FMT_PCM_B = 4,
|
|
||||||
EC_DAI_FMT_PCM_TDM = 5,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
enum ec_codec_shm_type {
|
||||||
* For subcommand EC_CODEC_GET_GAIN.
|
EC_CODEC_SHM_TYPE_EC_RAM = 0x0,
|
||||||
*/
|
EC_CODEC_SHM_TYPE_SYSTEM_RAM = 0x1,
|
||||||
struct __ec_align1 ec_codec_i2s_gain {
|
|
||||||
uint8_t left;
|
|
||||||
uint8_t right;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct __ec_todo_unpacked ec_param_codec_i2s_tdm {
|
struct __ec_align1 ec_param_ec_codec_get_shm_addr {
|
||||||
int16_t ch0_delay; /* 0 to 496 */
|
uint8_t shm_id;
|
||||||
int16_t ch1_delay; /* -1 to 496 */
|
uint8_t reserved[3];
|
||||||
uint8_t adjacent_to_ch0;
|
|
||||||
uint8_t adjacent_to_ch1;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct __ec_todo_packed ec_param_codec_i2s {
|
struct __ec_align4 ec_param_ec_codec_set_shm_addr {
|
||||||
/* enum ec_codec_i2s_subcmd */
|
uint64_t phys_addr;
|
||||||
uint8_t cmd;
|
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 {
|
union {
|
||||||
/*
|
struct ec_param_ec_codec_get_shm_addr
|
||||||
* EC_CODEC_SET_SAMPLE_DEPTH
|
get_shm_addr_param;
|
||||||
* Value should be one of ec_sample_depth_value.
|
struct ec_param_ec_codec_set_shm_addr
|
||||||
*/
|
set_shm_addr_param;
|
||||||
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_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 */
|
/* System commands */
|
||||||
|
@@ -117,6 +117,7 @@ struct snd_card {
|
|||||||
struct device card_dev; /* cardX object for sysfs */
|
struct device card_dev; /* cardX object for sysfs */
|
||||||
const struct attribute_group *dev_groups[4]; /* assigned sysfs attr */
|
const struct attribute_group *dev_groups[4]; /* assigned sysfs attr */
|
||||||
bool registered; /* card_dev is registered? */
|
bool registered; /* card_dev is registered? */
|
||||||
|
int sync_irq; /* assigned irq, used for PCM sync */
|
||||||
wait_queue_head_t remove_sleep;
|
wait_queue_head_t remove_sleep;
|
||||||
|
|
||||||
#ifdef CONFIG_PM
|
#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,
|
const struct snd_dmaengine_dai_dma_data *dma_data,
|
||||||
struct dma_slave_config *config);
|
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
|
* 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 force_pin_prefix:1; /* Add location prefix */
|
||||||
unsigned int link_down_at_suspend:1; /* link down at runtime suspend */
|
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 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
|
#ifdef CONFIG_PM
|
||||||
unsigned long power_on_acct;
|
unsigned long power_on_acct;
|
||||||
|
34
include/sound/intel-dsp-config.h
Normal file
34
include/sound/intel-dsp-config.h
Normal file
@@ -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 */
|
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))
|
#define snd_dma_continuous_data(x) ((struct device *)(__force unsigned long)(x))
|
||||||
|
|
||||||
|
|
||||||
@@ -44,6 +43,7 @@ struct snd_dma_device {
|
|||||||
#else
|
#else
|
||||||
#define SNDRV_DMA_TYPE_DEV_IRAM SNDRV_DMA_TYPE_DEV
|
#define SNDRV_DMA_TYPE_DEV_IRAM SNDRV_DMA_TYPE_DEV
|
||||||
#endif
|
#endif
|
||||||
|
#define SNDRV_DMA_TYPE_VMALLOC 7 /* vmalloc'ed buffer */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* info for buffer allocation
|
* info for buffer allocation
|
||||||
|
@@ -59,6 +59,7 @@ struct snd_pcm_ops {
|
|||||||
int (*hw_free)(struct snd_pcm_substream *substream);
|
int (*hw_free)(struct snd_pcm_substream *substream);
|
||||||
int (*prepare)(struct snd_pcm_substream *substream);
|
int (*prepare)(struct snd_pcm_substream *substream);
|
||||||
int (*trigger)(struct snd_pcm_substream *substream, int cmd);
|
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);
|
snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream);
|
||||||
int (*get_time_info)(struct snd_pcm_substream *substream,
|
int (*get_time_info)(struct snd_pcm_substream *substream,
|
||||||
struct timespec *system_ts, struct timespec *audio_ts,
|
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 sleep; /* poll sleep */
|
||||||
wait_queue_head_t tsleep; /* transfer sleep */
|
wait_queue_head_t tsleep; /* transfer sleep */
|
||||||
struct fasync_struct *fasync;
|
struct fasync_struct *fasync;
|
||||||
|
bool stop_operating; /* sync_stop will be called */
|
||||||
|
|
||||||
/* -- private section -- */
|
/* -- private section -- */
|
||||||
void *private_data;
|
void *private_data;
|
||||||
@@ -414,6 +416,7 @@ struct snd_pcm_runtime {
|
|||||||
size_t dma_bytes; /* size of DMA area */
|
size_t dma_bytes; /* size of DMA area */
|
||||||
|
|
||||||
struct snd_dma_buffer *dma_buffer_p; /* allocated buffer */
|
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 -- */
|
/* -- audio timestamp config -- */
|
||||||
struct snd_pcm_audio_tstamp_config audio_tstamp_config;
|
struct snd_pcm_audio_tstamp_config audio_tstamp_config;
|
||||||
@@ -475,6 +478,7 @@ struct snd_pcm_substream {
|
|||||||
#endif /* CONFIG_SND_VERBOSE_PROCFS */
|
#endif /* CONFIG_SND_VERBOSE_PROCFS */
|
||||||
/* misc flags */
|
/* misc flags */
|
||||||
unsigned int hw_opened: 1;
|
unsigned int hw_opened: 1;
|
||||||
|
unsigned int managed_buffer_alloc:1;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define SUBSTREAM_BUSY(substream) ((substream)->ref_count > 0)
|
#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_malloc_pages(struct snd_pcm_substream *substream, size_t size);
|
||||||
int snd_pcm_lib_free_pages(struct snd_pcm_substream *substream);
|
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,
|
int _snd_pcm_lib_alloc_vmalloc_buffer(struct snd_pcm_substream *substream,
|
||||||
size_t size, gfp_t gfp_flags);
|
size_t size, gfp_t gfp_flags);
|
||||||
int snd_pcm_lib_free_vmalloc_buffer(struct snd_pcm_substream *substream);
|
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) \
|
#define snd_pcm_substream_sgbuf(substream) \
|
||||||
snd_pcm_get_dma_buf(substream)->private_data
|
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 */
|
#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_AES1_CON_PCM_CODER<<8)|\
|
||||||
(IEC958_AES3_CON_FS_48000<<24))
|
(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);
|
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_pcm_hw_params;
|
||||||
struct snd_soc_pcm_runtime;
|
struct snd_soc_pcm_runtime;
|
||||||
struct snd_pcm;
|
struct snd_pcm;
|
||||||
|
struct snd_soc_component;
|
||||||
|
|
||||||
extern int pxa2xx_pcm_hw_params(struct snd_pcm_substream *substream,
|
extern int pxa2xx_pcm_hw_params(struct snd_pcm_substream *substream,
|
||||||
struct snd_pcm_hw_params *params);
|
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);
|
struct vm_area_struct *vma);
|
||||||
extern int pxa2xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream);
|
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 void pxa2xx_pcm_free_dma_buffers(struct snd_pcm *pcm);
|
||||||
extern int pxa2xx_soc_pcm_new(struct snd_soc_pcm_runtime *rtd);
|
extern void pxa2xx_soc_pcm_free(struct snd_soc_component *component,
|
||||||
extern const struct snd_pcm_ops pxa2xx_pcm_ops;
|
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 */
|
/* AC97 */
|
||||||
|
|
||||||
|
@@ -31,6 +31,7 @@ struct rt5682_platform_data {
|
|||||||
enum rt5682_dmic1_data_pin dmic1_data_pin;
|
enum rt5682_dmic1_data_pin dmic1_data_pin;
|
||||||
enum rt5682_dmic1_clk_pin dmic1_clk_pin;
|
enum rt5682_dmic1_clk_pin dmic1_clk_pin;
|
||||||
enum rt5682_jd_src jd_src;
|
enum rt5682_jd_src jd_src;
|
||||||
|
unsigned int btndet_delay;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -8,6 +8,7 @@
|
|||||||
#ifndef __SIMPLE_CARD_UTILS_H
|
#ifndef __SIMPLE_CARD_UTILS_H
|
||||||
#define __SIMPLE_CARD_UTILS_H
|
#define __SIMPLE_CARD_UTILS_H
|
||||||
|
|
||||||
|
#include <linux/clk.h>
|
||||||
#include <sound/soc.h>
|
#include <sound/soc.h>
|
||||||
|
|
||||||
#define asoc_simple_init_hp(card, sjack, prefix) \
|
#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_bxt_machines[];
|
||||||
extern struct snd_soc_acpi_mach snd_soc_acpi_intel_glk_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_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_icl_machines[];
|
||||||
extern struct snd_soc_acpi_mach snd_soc_acpi_intel_tgl_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_ehl_machines[];
|
||||||
|
extern struct snd_soc_acpi_mach snd_soc_acpi_intel_jsl_machines[];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* generic table used for HDA codec-based platforms, possibly with
|
* 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
|
* @acpi_ipc_irq_index: used for BYT-CR detection
|
||||||
* @platform: string used for HDaudio codec support
|
* @platform: string used for HDaudio codec support
|
||||||
* @codec_mask: used for HDAudio support
|
* @codec_mask: used for HDAudio support
|
||||||
|
* @common_hdmi_codec_drv: use commom HDAudio HDMI codec driver
|
||||||
*/
|
*/
|
||||||
struct snd_soc_acpi_mach_params {
|
struct snd_soc_acpi_mach_params {
|
||||||
u32 acpi_ipc_irq_index;
|
u32 acpi_ipc_irq_index;
|
||||||
const char *platform;
|
const char *platform;
|
||||||
u32 codec_mask;
|
u32 codec_mask;
|
||||||
u32 dmic_num;
|
u32 dmic_num;
|
||||||
|
bool common_hdmi_codec_drv;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -75,6 +77,7 @@ struct snd_soc_acpi_mach_params {
|
|||||||
* all firmware/topology related fields.
|
* all firmware/topology related fields.
|
||||||
*
|
*
|
||||||
* @id: ACPI ID (usually the codec's) used to find a matching machine driver.
|
* @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
|
* @drv_name: machine driver name
|
||||||
* @fw_filename: firmware file name. Used when SOF is not enabled.
|
* @fw_filename: firmware file name. Used when SOF is not enabled.
|
||||||
* @board: board name
|
* @board: board name
|
||||||
@@ -90,6 +93,7 @@ struct snd_soc_acpi_mach_params {
|
|||||||
/* Descriptor for SST ASoC machine driver */
|
/* Descriptor for SST ASoC machine driver */
|
||||||
struct snd_soc_acpi_mach {
|
struct snd_soc_acpi_mach {
|
||||||
const u8 id[ACPI_ID_LEN];
|
const u8 id[ACPI_ID_LEN];
|
||||||
|
const u32 link_mask;
|
||||||
const char *drv_name;
|
const char *drv_name;
|
||||||
const char *fw_filename;
|
const char *fw_filename;
|
||||||
const char *board;
|
const char *board;
|
||||||
|
@@ -3,10 +3,6 @@
|
|||||||
* soc-component.h
|
* soc-component.h
|
||||||
*
|
*
|
||||||
* Copyright (c) 2019 Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
|
* 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
|
#ifndef __SOC_COMPONENT_H
|
||||||
#define __SOC_COMPONENT_H
|
#define __SOC_COMPONENT_H
|
||||||
@@ -51,8 +47,10 @@ struct snd_soc_component_driver {
|
|||||||
unsigned int reg, unsigned int val);
|
unsigned int reg, unsigned int val);
|
||||||
|
|
||||||
/* pcm creation and destruction */
|
/* pcm creation and destruction */
|
||||||
int (*pcm_new)(struct snd_soc_pcm_runtime *rtd);
|
int (*pcm_construct)(struct snd_soc_component *component,
|
||||||
void (*pcm_free)(struct snd_pcm *pcm);
|
struct snd_soc_pcm_runtime *rtd);
|
||||||
|
void (*pcm_destruct)(struct snd_soc_component *component,
|
||||||
|
struct snd_pcm *pcm);
|
||||||
|
|
||||||
/* component wide operations */
|
/* component wide operations */
|
||||||
int (*set_sysclk)(struct snd_soc_component *component,
|
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,
|
int (*set_bias_level)(struct snd_soc_component *component,
|
||||||
enum snd_soc_bias_level level);
|
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;
|
const struct snd_compr_ops *compr_ops;
|
||||||
|
|
||||||
/* probe ordering - for components with runtime dependencies */
|
/* 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_pointer(struct snd_pcm_substream *substream);
|
||||||
int snd_soc_pcm_component_ioctl(struct snd_pcm_substream *substream,
|
int snd_soc_pcm_component_ioctl(struct snd_pcm_substream *substream,
|
||||||
unsigned int cmd, void *arg);
|
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 snd_soc_pcm_component_copy_user(struct snd_pcm_substream *substream,
|
||||||
int channel, unsigned long pos,
|
int channel, unsigned long pos,
|
||||||
void __user *buf, unsigned long bytes);
|
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);
|
unsigned long offset);
|
||||||
int snd_soc_pcm_component_mmap(struct snd_pcm_substream *substream,
|
int snd_soc_pcm_component_mmap(struct snd_pcm_substream *substream,
|
||||||
struct vm_area_struct *vma);
|
struct vm_area_struct *vma);
|
||||||
int snd_soc_pcm_component_new(struct snd_pcm *pcm);
|
int snd_soc_pcm_component_new(struct snd_soc_pcm_runtime *rtd);
|
||||||
void snd_soc_pcm_component_free(struct snd_pcm *pcm);
|
void snd_soc_pcm_component_free(struct snd_soc_pcm_runtime *rtd);
|
||||||
|
|
||||||
#endif /* __SOC_COMPONENT_H */
|
#endif /* __SOC_COMPONENT_H */
|
||||||
|
@@ -103,15 +103,15 @@ struct snd_soc_dpcm_runtime {
|
|||||||
int trigger_pending; /* trigger cmd + 1 if pending, 0 if not */
|
int trigger_pending; /* trigger cmd + 1 if pending, 0 if not */
|
||||||
};
|
};
|
||||||
|
|
||||||
#define for_each_dpcm_fe(be, stream, dpcm) \
|
#define for_each_dpcm_fe(be, stream, _dpcm) \
|
||||||
list_for_each_entry(dpcm, &(be)->dpcm[stream].fe_clients, list_fe)
|
list_for_each_entry(_dpcm, &(be)->dpcm[stream].fe_clients, list_fe)
|
||||||
|
|
||||||
#define for_each_dpcm_be(fe, stream, dpcm) \
|
#define for_each_dpcm_be(fe, stream, _dpcm) \
|
||||||
list_for_each_entry(dpcm, &(fe)->dpcm[stream].be_clients, list_be)
|
list_for_each_entry(_dpcm, &(fe)->dpcm[stream].be_clients, list_be)
|
||||||
#define for_each_dpcm_be_safe(fe, stream, dpcm, _dpcm) \
|
#define for_each_dpcm_be_safe(fe, stream, _dpcm, __dpcm) \
|
||||||
list_for_each_entry_safe(dpcm, _dpcm, &(fe)->dpcm[stream].be_clients, list_be)
|
list_for_each_entry_safe(_dpcm, __dpcm, &(fe)->dpcm[stream].be_clients, list_be)
|
||||||
#define for_each_dpcm_be_rollback(fe, stream, dpcm) \
|
#define for_each_dpcm_be_rollback(fe, stream, _dpcm) \
|
||||||
list_for_each_entry_continue_reverse(dpcm, &(fe)->dpcm[stream].be_clients, list_be)
|
list_for_each_entry_continue_reverse(_dpcm, &(fe)->dpcm[stream].be_clients, list_be)
|
||||||
|
|
||||||
/* can this BE stop and free */
|
/* can this BE stop and free */
|
||||||
int snd_soc_dpcm_can_be_free_stop(struct snd_soc_pcm_runtime *fe,
|
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 = \
|
.put = snd_soc_bytes_put, .private_value = \
|
||||||
((unsigned long)&(struct soc_bytes) \
|
((unsigned long)&(struct soc_bytes) \
|
||||||
{.base = xbase, .num_regs = xregs }) }
|
{.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) \
|
#define SND_SOC_BYTES_MASK(xname, xbase, xregs, xmask) \
|
||||||
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
|
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
|
||||||
@@ -739,10 +745,12 @@ struct snd_soc_rtdcom_list {
|
|||||||
struct snd_soc_component*
|
struct snd_soc_component*
|
||||||
snd_soc_rtdcom_lookup(struct snd_soc_pcm_runtime *rtd,
|
snd_soc_rtdcom_lookup(struct snd_soc_pcm_runtime *rtd,
|
||||||
const char *driver_name);
|
const char *driver_name);
|
||||||
#define for_each_rtdcom(rtd, rtdcom) \
|
#define for_each_rtd_components(rtd, rtdcom, _component) \
|
||||||
list_for_each_entry(rtdcom, &(rtd)->component_list, list)
|
for (rtdcom = list_first_entry(&(rtd)->component_list, \
|
||||||
#define for_each_rtdcom_safe(rtd, rtdcom1, rtdcom2) \
|
typeof(*rtdcom), list); \
|
||||||
list_for_each_entry_safe(rtdcom1, rtdcom2, &(rtd)->component_list, list)
|
(&rtdcom->list != &(rtd)->component_list) && \
|
||||||
|
(_component = rtdcom->component); \
|
||||||
|
rtdcom = list_next_entry(rtdcom, list))
|
||||||
|
|
||||||
struct snd_soc_dai_link_component {
|
struct snd_soc_dai_link_component {
|
||||||
const char *name;
|
const char *name;
|
||||||
@@ -845,7 +853,9 @@ struct snd_soc_dai_link {
|
|||||||
unsigned int ignore:1;
|
unsigned int ignore:1;
|
||||||
|
|
||||||
struct list_head list; /* DAI link list of the soc card */
|
struct list_head list; /* DAI link list of the soc card */
|
||||||
|
#ifdef CONFIG_SND_SOC_TOPOLOGY
|
||||||
struct snd_soc_dobj dobj; /* For topology */
|
struct snd_soc_dobj dobj; /* For topology */
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
#define for_each_link_codecs(link, i, codec) \
|
#define for_each_link_codecs(link, i, codec) \
|
||||||
for ((i) = 0; \
|
for ((i) = 0; \
|
||||||
@@ -978,6 +988,7 @@ struct snd_soc_card {
|
|||||||
const char *name;
|
const char *name;
|
||||||
const char *long_name;
|
const char *long_name;
|
||||||
const char *driver_name;
|
const char *driver_name;
|
||||||
|
const char *components;
|
||||||
char dmi_longname[80];
|
char dmi_longname[80];
|
||||||
char topology_shortname[32];
|
char topology_shortname[32];
|
||||||
|
|
||||||
@@ -1148,7 +1159,6 @@ struct snd_soc_pcm_runtime {
|
|||||||
struct list_head component_list; /* list of connected components */
|
struct list_head component_list; /* list of connected components */
|
||||||
|
|
||||||
/* bit field */
|
/* bit field */
|
||||||
unsigned int dev_registered:1;
|
|
||||||
unsigned int pop_wait:1;
|
unsigned int pop_wait:1;
|
||||||
unsigned int fe_compr:1; /* for Dynamic PCM */
|
unsigned int fe_compr:1; /* for Dynamic PCM */
|
||||||
};
|
};
|
||||||
@@ -1168,7 +1178,9 @@ struct soc_mixer_control {
|
|||||||
unsigned int sign_bit;
|
unsigned int sign_bit;
|
||||||
unsigned int invert:1;
|
unsigned int invert:1;
|
||||||
unsigned int autodisable:1;
|
unsigned int autodisable:1;
|
||||||
|
#ifdef CONFIG_SND_SOC_TOPOLOGY
|
||||||
struct snd_soc_dobj dobj;
|
struct snd_soc_dobj dobj;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
struct soc_bytes {
|
struct soc_bytes {
|
||||||
@@ -1179,8 +1191,9 @@ struct soc_bytes {
|
|||||||
|
|
||||||
struct soc_bytes_ext {
|
struct soc_bytes_ext {
|
||||||
int max;
|
int max;
|
||||||
|
#ifdef CONFIG_SND_SOC_TOPOLOGY
|
||||||
struct snd_soc_dobj dobj;
|
struct snd_soc_dobj dobj;
|
||||||
|
#endif
|
||||||
/* used for TLV byte control */
|
/* used for TLV byte control */
|
||||||
int (*get)(struct snd_kcontrol *kcontrol, unsigned int __user *bytes,
|
int (*get)(struct snd_kcontrol *kcontrol, unsigned int __user *bytes,
|
||||||
unsigned int size);
|
unsigned int size);
|
||||||
@@ -1204,7 +1217,9 @@ struct soc_enum {
|
|||||||
const char * const *texts;
|
const char * const *texts;
|
||||||
const unsigned int *values;
|
const unsigned int *values;
|
||||||
unsigned int autodisable:1;
|
unsigned int autodisable:1;
|
||||||
|
#ifdef CONFIG_SND_SOC_TOPOLOGY
|
||||||
struct snd_soc_dobj dobj;
|
struct snd_soc_dobj dobj;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
/* device driver data */
|
/* 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,
|
int id, const char *name,
|
||||||
const char *stream_name);
|
const char *stream_name);
|
||||||
|
|
||||||
int snd_soc_register_dai(struct snd_soc_component *component,
|
struct snd_soc_dai *snd_soc_register_dai(struct snd_soc_component *component,
|
||||||
struct snd_soc_dai_driver *dai_drv);
|
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(
|
struct snd_soc_dai *snd_soc_find_dai(
|
||||||
const struct snd_soc_dai_link_component *dlc);
|
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);
|
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>
|
#include <sound/soc-component.h>
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -61,6 +61,9 @@ struct sof_dev_desc {
|
|||||||
/* list of machines using this configuration */
|
/* list of machines using this configuration */
|
||||||
struct snd_soc_acpi_mach *machines;
|
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. */
|
/* Platform resource indexes in BAR / ACPI resources. */
|
||||||
/* Must set to -1 if not used - add new items to end */
|
/* Must set to -1 if not used - add new items to end */
|
||||||
int resindex_lpe_base;
|
int resindex_lpe_base;
|
||||||
|
34
include/sound/sof/dai-imx.h
Normal file
34
include/sound/sof/dai-imx.h
Normal file
@@ -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/header.h>
|
||||||
#include <sound/sof/dai-intel.h>
|
#include <sound/sof/dai-intel.h>
|
||||||
|
#include <sound/sof/dai-imx.h>
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* DAI Configuration.
|
* DAI Configuration.
|
||||||
@@ -73,6 +74,7 @@ struct sof_ipc_dai_config {
|
|||||||
struct sof_ipc_dai_dmic_params dmic;
|
struct sof_ipc_dai_dmic_params dmic;
|
||||||
struct sof_ipc_dai_hda_params hda;
|
struct sof_ipc_dai_hda_params hda;
|
||||||
struct sof_ipc_dai_alh_params alh;
|
struct sof_ipc_dai_alh_params alh;
|
||||||
|
struct sof_ipc_dai_esai_params esai;
|
||||||
};
|
};
|
||||||
} __packed;
|
} __packed;
|
||||||
|
|
||||||
|
@@ -9,6 +9,7 @@
|
|||||||
#ifndef __INCLUDE_SOUND_SOF_HEADER_H__
|
#ifndef __INCLUDE_SOUND_SOF_HEADER_H__
|
||||||
#define __INCLUDE_SOUND_SOF_HEADER_H__
|
#define __INCLUDE_SOUND_SOF_HEADER_H__
|
||||||
|
|
||||||
|
#include <linux/types.h>
|
||||||
#include <uapi/sound/sof/abi.h>
|
#include <uapi/sound/sof/abi.h>
|
||||||
|
|
||||||
/** \addtogroup sof_uapi uAPI
|
/** \addtogroup sof_uapi uAPI
|
||||||
@@ -74,6 +75,7 @@
|
|||||||
#define SOF_IPC_PM_CLK_GET SOF_CMD_TYPE(0x005)
|
#define SOF_IPC_PM_CLK_GET SOF_CMD_TYPE(0x005)
|
||||||
#define SOF_IPC_PM_CLK_REQ SOF_CMD_TYPE(0x006)
|
#define SOF_IPC_PM_CLK_REQ SOF_CMD_TYPE(0x006)
|
||||||
#define SOF_IPC_PM_CORE_ENABLE SOF_CMD_TYPE(0x007)
|
#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 */
|
/* component runtime config - multiple different types */
|
||||||
#define SOF_IPC_COMP_SET_VALUE SOF_CMD_TYPE(0x001)
|
#define SOF_IPC_COMP_SET_VALUE SOF_CMD_TYPE(0x001)
|
||||||
|
@@ -45,4 +45,12 @@ struct sof_ipc_pm_core_config {
|
|||||||
uint32_t enable_mask;
|
uint32_t enable_mask;
|
||||||
} __packed;
|
} __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
|
#endif
|
||||||
|
@@ -83,10 +83,10 @@ struct sof_ipc_stream_params {
|
|||||||
uint16_t sample_valid_bytes;
|
uint16_t sample_valid_bytes;
|
||||||
uint16_t sample_container_bytes;
|
uint16_t sample_container_bytes;
|
||||||
|
|
||||||
/* for notifying host period has completed - 0 means no period IRQ */
|
|
||||||
uint32_t host_period_bytes;
|
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_ */
|
uint16_t chmap[SOF_IPC_MAX_CHANNELS]; /**< channel map - SOF_CHMAP_ */
|
||||||
} __packed;
|
} __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_free(struct snd_timer *timer);
|
||||||
int snd_timer_global_register(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);
|
struct snd_timer_instance *snd_timer_instance_new(const char *owner);
|
||||||
int snd_timer_close(struct snd_timer_instance *timeri);
|
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);
|
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_start(struct snd_timer_instance *timeri, unsigned int ticks);
|
||||||
int snd_timer_stop(struct snd_timer_instance *timeri);
|
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
|
* DRC configurations are specified with a label and a set of register
|
||||||
* values to write (the enable bits will be ignored). At runtime an
|
* values to write (the enable bits will be ignored). At runtime an
|
||||||
* enumerated control will be presented for each DRC block allowing
|
* 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
|
* Configurations may be generated by hand or by using the DRC control
|
||||||
* panel provided by the WISCE - see http://www.wolfsonmicro.com/wisce/
|
* 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 */
|
__s32 reserved[15]; /* Can be used for SND_AUDIOCODEC_BESPOKE */
|
||||||
} __attribute__((packed, aligned(4)));
|
} __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 {
|
union snd_codec_options {
|
||||||
struct snd_enc_wma wma;
|
struct snd_enc_wma wma;
|
||||||
struct snd_enc_vorbis vorbis;
|
struct snd_enc_vorbis vorbis;
|
||||||
struct snd_enc_real real;
|
struct snd_enc_real real;
|
||||||
struct snd_enc_flac flac;
|
struct snd_enc_flac flac;
|
||||||
struct snd_enc_generic generic;
|
struct snd_enc_generic generic;
|
||||||
|
struct snd_dec_flac flac_d;
|
||||||
} __attribute__((packed, aligned(4)));
|
} __attribute__((packed, aligned(4)));
|
||||||
|
|
||||||
/** struct snd_codec_desc - description of codec capabilities
|
/** struct snd_codec_desc - description of codec capabilities
|
||||||
|
@@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
/* SOF ABI version major, minor and patch numbers */
|
/* SOF ABI version major, minor and patch numbers */
|
||||||
#define SOF_ABI_MAJOR 3
|
#define SOF_ABI_MAJOR 3
|
||||||
#define SOF_ABI_MINOR 10
|
#define SOF_ABI_MINOR 11
|
||||||
#define SOF_ABI_PATCH 0
|
#define SOF_ABI_PATCH 0
|
||||||
|
|
||||||
/* SOF ABI version number. Format within 32bit word is MMmmmppp */
|
/* SOF ABI version number. Format within 32bit word is MMmmmppp */
|
||||||
|
@@ -111,7 +111,14 @@
|
|||||||
/* TODO: Add SAI tokens */
|
/* TODO: Add SAI tokens */
|
||||||
|
|
||||||
/* ESAI */
|
/* ESAI */
|
||||||
#define SOF_TKN_IMX_ESAI_FIRST_TOKEN 1100
|
#define SOF_TKN_IMX_ESAI_MCLK_ID 1100
|
||||||
/* TODO: Add ESAI tokens */
|
|
||||||
|
/* 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
|
#endif
|
||||||
|
@@ -1028,7 +1028,7 @@ i2sbus_attach_codec(struct soundbus_dev *dev, struct snd_card *card,
|
|||||||
/* well, we really should support scatter/gather DMA */
|
/* well, we really should support scatter/gather DMA */
|
||||||
snd_pcm_lib_preallocate_pages_for_all(
|
snd_pcm_lib_preallocate_pages_for_all(
|
||||||
dev->pcm, SNDRV_DMA_TYPE_DEV,
|
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);
|
64 * 1024, 64 * 1024);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@@ -175,7 +175,15 @@ void pxa2xx_pcm_free_dma_buffers(struct snd_pcm *pcm)
|
|||||||
}
|
}
|
||||||
EXPORT_SYMBOL(pxa2xx_pcm_free_dma_buffers);
|
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_card *card = rtd->card->snd_card;
|
||||||
struct snd_pcm *pcm = rtd->pcm;
|
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);
|
EXPORT_SYMBOL(pxa2xx_soc_pcm_new);
|
||||||
|
|
||||||
const struct snd_pcm_ops pxa2xx_pcm_ops = {
|
int pxa2xx_soc_pcm_open(struct snd_soc_component *component,
|
||||||
.open = pxa2xx_pcm_open,
|
struct snd_pcm_substream *substream)
|
||||||
.close = pxa2xx_pcm_close,
|
{
|
||||||
.ioctl = snd_pcm_lib_ioctl,
|
return pxa2xx_pcm_open(substream);
|
||||||
.hw_params = pxa2xx_pcm_hw_params,
|
}
|
||||||
.hw_free = pxa2xx_pcm_hw_free,
|
EXPORT_SYMBOL(pxa2xx_soc_pcm_open);
|
||||||
.prepare = pxa2xx_pcm_prepare,
|
|
||||||
.trigger = pxa2xx_pcm_trigger,
|
int pxa2xx_soc_pcm_close(struct snd_soc_component *component,
|
||||||
.pointer = pxa2xx_pcm_pointer,
|
struct snd_pcm_substream *substream)
|
||||||
.mmap = pxa2xx_pcm_mmap,
|
{
|
||||||
};
|
return pxa2xx_pcm_close(substream);
|
||||||
EXPORT_SYMBOL(pxa2xx_pcm_ops);
|
}
|
||||||
|
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_AUTHOR("Nicolas Pitre");
|
||||||
MODULE_DESCRIPTION("Intel PXA2xx sound library");
|
MODULE_DESCRIPTION("Intel PXA2xx sound library");
|
||||||
|
@@ -72,11 +72,11 @@ config SND_PCM_OSS
|
|||||||
config SND_PCM_OSS_PLUGINS
|
config SND_PCM_OSS_PLUGINS
|
||||||
bool "OSS PCM (digital audio) API - Include plugin system"
|
bool "OSS PCM (digital audio) API - Include plugin system"
|
||||||
depends on SND_PCM_OSS
|
depends on SND_PCM_OSS
|
||||||
default y
|
default y
|
||||||
help
|
help
|
||||||
If you disable this option, the ALSA's OSS PCM API will not
|
If you disable this option, the ALSA's OSS PCM API will not
|
||||||
support conversion of channels, formats and rates. It will
|
support conversion of channels, formats and rates. It will
|
||||||
behave like most of new OSS/Free drivers in 2.4/2.6 kernels.
|
behave like most of new OSS/Free drivers in 2.4/2.6 kernels.
|
||||||
|
|
||||||
config SND_PCM_TIMER
|
config SND_PCM_TIMER
|
||||||
bool "PCM timer interface" if EXPERT
|
bool "PCM timer interface" if EXPERT
|
||||||
@@ -128,13 +128,13 @@ config SND_SUPPORT_OLD_API
|
|||||||
or older).
|
or older).
|
||||||
|
|
||||||
config SND_PROC_FS
|
config SND_PROC_FS
|
||||||
bool "Sound Proc FS Support" if EXPERT
|
bool "Sound Proc FS Support" if EXPERT
|
||||||
depends on PROC_FS
|
depends on PROC_FS
|
||||||
default y
|
default y
|
||||||
help
|
help
|
||||||
Say 'N' to disable Sound proc FS, which may reduce code size about
|
Say 'N' to disable Sound proc FS, which may reduce code size about
|
||||||
9KB on x86_64 platform.
|
9KB on x86_64 platform.
|
||||||
If unsure say Y.
|
If unsure say Y.
|
||||||
|
|
||||||
config SND_VERBOSE_PROCFS
|
config SND_VERBOSE_PROCFS
|
||||||
bool "Verbose procfs contents"
|
bool "Verbose procfs contents"
|
||||||
@@ -142,8 +142,8 @@ config SND_VERBOSE_PROCFS
|
|||||||
default y
|
default y
|
||||||
help
|
help
|
||||||
Say Y here to include code for verbose procfs contents (provides
|
Say Y here to include code for verbose procfs contents (provides
|
||||||
useful information to developers when a problem occurs). On the
|
useful information to developers when a problem occurs). On the
|
||||||
other side, it makes the ALSA subsystem larger.
|
other side, it makes the ALSA subsystem larger.
|
||||||
|
|
||||||
config SND_VERBOSE_PRINTK
|
config SND_VERBOSE_PRINTK
|
||||||
bool "Verbose printk"
|
bool "Verbose printk"
|
||||||
@@ -164,7 +164,7 @@ config SND_DEBUG_VERBOSE
|
|||||||
depends on SND_DEBUG
|
depends on SND_DEBUG
|
||||||
help
|
help
|
||||||
Say Y here to enable extra-verbose debugging messages.
|
Say Y here to enable extra-verbose debugging messages.
|
||||||
|
|
||||||
Let me repeat: it enables EXTRA-VERBOSE DEBUGGING messages.
|
Let me repeat: it enables EXTRA-VERBOSE DEBUGGING messages.
|
||||||
So, say Y only if you are ready to be annoyed.
|
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);
|
init_waitqueue_head(&card->power_sleep);
|
||||||
#endif
|
#endif
|
||||||
init_waitqueue_head(&card->remove_sleep);
|
init_waitqueue_head(&card->remove_sleep);
|
||||||
|
card->sync_irq = -1;
|
||||||
|
|
||||||
device_initialize(&card->card_dev);
|
device_initialize(&card->card_dev);
|
||||||
card->card_dev.parent = parent;
|
card->card_dev.parent = parent;
|
||||||
|
@@ -10,6 +10,7 @@
|
|||||||
#include <linux/mm.h>
|
#include <linux/mm.h>
|
||||||
#include <linux/dma-mapping.h>
|
#include <linux/dma-mapping.h>
|
||||||
#include <linux/genalloc.h>
|
#include <linux/genalloc.h>
|
||||||
|
#include <linux/vmalloc.h>
|
||||||
#ifdef CONFIG_X86
|
#ifdef CONFIG_X86
|
||||||
#include <asm/set_memory.h>
|
#include <asm/set_memory.h>
|
||||||
#endif
|
#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
|
* 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,
|
int snd_dma_alloc_pages(int type, struct device *device, size_t size,
|
||||||
struct snd_dma_buffer *dmab)
|
struct snd_dma_buffer *dmab)
|
||||||
{
|
{
|
||||||
|
gfp_t gfp;
|
||||||
|
|
||||||
if (WARN_ON(!size))
|
if (WARN_ON(!size))
|
||||||
return -ENXIO;
|
return -ENXIO;
|
||||||
if (WARN_ON(!dmab))
|
if (WARN_ON(!dmab))
|
||||||
return -ENXIO;
|
return -ENXIO;
|
||||||
if (WARN_ON(!device))
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
dmab->dev.type = type;
|
dmab->dev.type = type;
|
||||||
dmab->dev.dev = device;
|
dmab->dev.dev = device;
|
||||||
dmab->bytes = 0;
|
dmab->bytes = 0;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case SNDRV_DMA_TYPE_CONTINUOUS:
|
case SNDRV_DMA_TYPE_CONTINUOUS:
|
||||||
dmab->area = alloc_pages_exact(size,
|
gfp = snd_mem_get_gfp_flags(device, GFP_KERNEL);
|
||||||
(__force gfp_t)(unsigned long)device);
|
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;
|
dmab->addr = 0;
|
||||||
break;
|
break;
|
||||||
#ifdef CONFIG_HAS_DMA
|
#ifdef CONFIG_HAS_DMA
|
||||||
@@ -215,6 +229,9 @@ void snd_dma_free_pages(struct snd_dma_buffer *dmab)
|
|||||||
case SNDRV_DMA_TYPE_CONTINUOUS:
|
case SNDRV_DMA_TYPE_CONTINUOUS:
|
||||||
free_pages_exact(dmab->area, dmab->bytes);
|
free_pages_exact(dmab->area, dmab->bytes);
|
||||||
break;
|
break;
|
||||||
|
case SNDRV_DMA_TYPE_VMALLOC:
|
||||||
|
vfree(dmab->area);
|
||||||
|
break;
|
||||||
#ifdef CONFIG_HAS_DMA
|
#ifdef CONFIG_HAS_DMA
|
||||||
#ifdef CONFIG_GENERIC_ALLOCATOR
|
#ifdef CONFIG_GENERIC_ALLOCATOR
|
||||||
case SNDRV_DMA_TYPE_DEV_IRAM:
|
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);
|
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");
|
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_xrun(struct snd_pcm_substream *substream);
|
||||||
void snd_pcm_group_init(struct snd_pcm_group *group);
|
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 */
|
#endif /* __SOUND_CORE_PCM_LOCAL_H */
|
||||||
|
@@ -15,6 +15,7 @@
|
|||||||
#include <sound/pcm.h>
|
#include <sound/pcm.h>
|
||||||
#include <sound/info.h>
|
#include <sound/info.h>
|
||||||
#include <sound/initval.h>
|
#include <sound/initval.h>
|
||||||
|
#include "pcm_local.h"
|
||||||
|
|
||||||
static int preallocate_dma = 1;
|
static int preallocate_dma = 1;
|
||||||
module_param(preallocate_dma, int, 0444);
|
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
|
* pre-allocate the buffer and create a proc file for the substream
|
||||||
*/
|
*/
|
||||||
static void snd_pcm_lib_preallocate_pages1(struct snd_pcm_substream *substream,
|
static void preallocate_pages(struct snd_pcm_substream *substream,
|
||||||
size_t size, size_t max)
|
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)
|
if (size > 0 && preallocate_dma && substream->number < maximum_substreams)
|
||||||
preallocate_pcm_pages(substream, size);
|
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)
|
if (substream->dma_buffer.bytes > 0)
|
||||||
substream->buffer_bytes_max = substream->dma_buffer.bytes;
|
substream->buffer_bytes_max = substream->dma_buffer.bytes;
|
||||||
substream->dma_max = max;
|
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
|
* 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,
|
int type, struct device *data,
|
||||||
size_t size, size_t max)
|
size_t size, size_t max)
|
||||||
{
|
{
|
||||||
substream->dma_buffer.dev.type = type;
|
preallocate_pages(substream, type, data, size, max, false);
|
||||||
substream->dma_buffer.dev.dev = data;
|
|
||||||
snd_pcm_lib_preallocate_pages1(substream, size, max);
|
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(snd_pcm_lib_preallocate_pages);
|
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,
|
int type, void *data,
|
||||||
size_t size, size_t max)
|
size_t size, size_t max)
|
||||||
{
|
{
|
||||||
struct snd_pcm_substream *substream;
|
preallocate_pages_for_all(pcm, type, data, size, max, false);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(snd_pcm_lib_preallocate_pages_for_all);
|
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
|
* snd_pcm_sgbuf_ops_page - get the page struct at the given offset
|
||||||
* @substream: the pcm substream instance
|
* @substream: the pcm substream instance
|
||||||
* @offset: the buffer offset
|
* @offset: the buffer offset
|
||||||
@@ -270,7 +331,6 @@ struct page *snd_pcm_sgbuf_ops_page(struct snd_pcm_substream *substream, unsigne
|
|||||||
return NULL;
|
return NULL;
|
||||||
return sgbuf->page_table[idx];
|
return sgbuf->page_table[idx];
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(snd_pcm_sgbuf_ops_page);
|
|
||||||
#endif /* CONFIG_SND_DMA_SGBUF */
|
#endif /* CONFIG_SND_DMA_SGBUF */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -13,6 +13,7 @@
|
|||||||
#include <linux/pm_qos.h>
|
#include <linux/pm_qos.h>
|
||||||
#include <linux/io.h>
|
#include <linux/io.h>
|
||||||
#include <linux/dma-mapping.h>
|
#include <linux/dma-mapping.h>
|
||||||
|
#include <linux/vmalloc.h>
|
||||||
#include <sound/core.h>
|
#include <sound/core.h>
|
||||||
#include <sound/control.h>
|
#include <sound/control.h>
|
||||||
#include <sound/info.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);
|
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)
|
int snd_pcm_info(struct snd_pcm_substream *substream, struct snd_pcm_info *info)
|
||||||
{
|
{
|
||||||
struct snd_pcm *pcm = substream->pcm;
|
struct snd_pcm *pcm = substream->pcm;
|
||||||
@@ -222,7 +233,8 @@ static bool hw_support_mmap(struct snd_pcm_substream *substream)
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (substream->ops->mmap ||
|
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 true;
|
||||||
|
|
||||||
return dma_can_mmap(substream->dma_buffer.dev.dev);
|
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);
|
m = hw_param_mask_c(params, SNDRV_PCM_HW_PARAM_FORMAT);
|
||||||
i = hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS);
|
i = hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS);
|
||||||
if (snd_mask_single(m) && snd_interval_single(i)) {
|
if (snd_mask_single(m) && snd_interval_single(i)) {
|
||||||
err = substream->ops->ioctl(substream,
|
err = snd_pcm_ops_ioctl(substream,
|
||||||
SNDRV_PCM_IOCTL1_FIFO_SIZE, params);
|
SNDRV_PCM_IOCTL1_FIFO_SIZE,
|
||||||
|
params);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
@@ -555,6 +568,17 @@ static inline void snd_pcm_timer_notify(struct snd_pcm_substream *substream,
|
|||||||
#endif
|
#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
|
* snd_pcm_hw_param_choose - choose a configuration defined by @params
|
||||||
* @pcm: PCM instance
|
* @pcm: PCM instance
|
||||||
@@ -647,6 +671,8 @@ static int snd_pcm_hw_params(struct snd_pcm_substream *substream,
|
|||||||
if (atomic_read(&substream->mmap_count))
|
if (atomic_read(&substream->mmap_count))
|
||||||
return -EBADFD;
|
return -EBADFD;
|
||||||
|
|
||||||
|
snd_pcm_sync_stop(substream);
|
||||||
|
|
||||||
params->rmask = ~0U;
|
params->rmask = ~0U;
|
||||||
err = snd_pcm_hw_refine(substream, params);
|
err = snd_pcm_hw_refine(substream, params);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
@@ -660,6 +686,14 @@ static int snd_pcm_hw_params(struct snd_pcm_substream *substream,
|
|||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto _error;
|
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) {
|
if (substream->ops->hw_params != NULL) {
|
||||||
err = substream->ops->hw_params(substream, params);
|
err = substream->ops->hw_params(substream, params);
|
||||||
if (err < 0)
|
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);
|
snd_pcm_set_state(substream, SNDRV_PCM_STATE_OPEN);
|
||||||
if (substream->ops->hw_free != NULL)
|
if (substream->ops->hw_free != NULL)
|
||||||
substream->ops->hw_free(substream);
|
substream->ops->hw_free(substream);
|
||||||
|
if (substream->managed_buffer_alloc)
|
||||||
|
snd_pcm_lib_free_pages(substream);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -765,8 +801,11 @@ static int snd_pcm_hw_free(struct snd_pcm_substream *substream)
|
|||||||
snd_pcm_stream_unlock_irq(substream);
|
snd_pcm_stream_unlock_irq(substream);
|
||||||
if (atomic_read(&substream->mmap_count))
|
if (atomic_read(&substream->mmap_count))
|
||||||
return -EBADFD;
|
return -EBADFD;
|
||||||
|
snd_pcm_sync_stop(substream);
|
||||||
if (substream->ops->hw_free)
|
if (substream->ops->hw_free)
|
||||||
result = substream->ops->hw_free(substream);
|
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);
|
snd_pcm_set_state(substream, SNDRV_PCM_STATE_OPEN);
|
||||||
pm_qos_remove_request(&substream->latency_pm_qos_req);
|
pm_qos_remove_request(&substream->latency_pm_qos_req);
|
||||||
return result;
|
return result;
|
||||||
@@ -957,7 +996,7 @@ static int snd_pcm_channel_info(struct snd_pcm_substream *substream,
|
|||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
memset(info, 0, sizeof(*info));
|
memset(info, 0, sizeof(*info));
|
||||||
info->channel = channel;
|
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,
|
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;
|
runtime->status->state = state;
|
||||||
snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MSTOP);
|
snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MSTOP);
|
||||||
}
|
}
|
||||||
|
runtime->stop_operating = true;
|
||||||
wake_up(&runtime->sleep);
|
wake_up(&runtime->sleep);
|
||||||
wake_up(&runtime->tsleep);
|
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);
|
snd_pcm_trigger_tstamp(substream);
|
||||||
runtime->status->state = runtime->status->suspended_state;
|
runtime->status->state = runtime->status->suspended_state;
|
||||||
snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MRESUME);
|
snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MRESUME);
|
||||||
|
snd_pcm_sync_stop(substream);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct action_ops snd_pcm_action_resume = {
|
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)
|
static int snd_pcm_do_reset(struct snd_pcm_substream *substream, int state)
|
||||||
{
|
{
|
||||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
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)
|
if (err < 0)
|
||||||
return err;
|
return err;
|
||||||
runtime->hw_ptr_base = 0;
|
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)
|
static int snd_pcm_do_prepare(struct snd_pcm_substream *substream, int state)
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
|
snd_pcm_sync_stop(substream);
|
||||||
err = substream->ops->prepare(substream);
|
err = substream->ops->prepare(substream);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
return err;
|
return err;
|
||||||
@@ -3334,7 +3376,18 @@ static inline struct page *
|
|||||||
snd_pcm_default_page_ops(struct snd_pcm_substream *substream, unsigned long ofs)
|
snd_pcm_default_page_ops(struct snd_pcm_substream *substream, unsigned long ofs)
|
||||||
{
|
{
|
||||||
void *vaddr = substream->runtime->dma_area + 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 */
|
#endif /* CONFIG_GENERIC_ALLOCATOR */
|
||||||
#ifndef CONFIG_X86 /* for avoiding warnings arch/x86/mm/pat.c */
|
#ifndef CONFIG_X86 /* for avoiding warnings arch/x86/mm/pat.c */
|
||||||
if (IS_ENABLED(CONFIG_HAS_DMA) && !substream->ops->page &&
|
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,
|
return dma_mmap_coherent(substream->dma_buffer.dev.dev,
|
||||||
area,
|
area,
|
||||||
substream->runtime->dma_area,
|
substream->runtime->dma_area,
|
||||||
|
@@ -272,7 +272,13 @@ int snd_seq_timer_open(struct snd_seq_queue *q)
|
|||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
if (tmr->alsa_id.dev_class != SNDRV_TIMER_CLASS_SLAVE)
|
if (tmr->alsa_id.dev_class != SNDRV_TIMER_CLASS_SLAVE)
|
||||||
tmr->alsa_id.dev_sclass = SNDRV_TIMER_SCLASS_SEQUENCER;
|
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 (err < 0 && tmr->alsa_id.dev_class != SNDRV_TIMER_CLASS_SLAVE) {
|
||||||
if (tmr->alsa_id.dev_class != SNDRV_TIMER_CLASS_GLOBAL ||
|
if (tmr->alsa_id.dev_class != SNDRV_TIMER_CLASS_GLOBAL ||
|
||||||
tmr->alsa_id.device != SNDRV_TIMER_GLOBAL_SYSTEM) {
|
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.dev_sclass = SNDRV_TIMER_SCLASS_SEQUENCER;
|
||||||
tid.card = -1;
|
tid.card = -1;
|
||||||
tid.device = SNDRV_TIMER_GLOBAL_SYSTEM;
|
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) {
|
if (err < 0) {
|
||||||
pr_err("ALSA: seq fatal error: cannot create timer (%i)\n", err);
|
pr_err("ALSA: seq fatal error: cannot create timer (%i)\n", err);
|
||||||
|
snd_timer_instance_free(t);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
t->callback = snd_seq_timer_interrupt;
|
|
||||||
t->callback_data = q;
|
|
||||||
t->flags |= SNDRV_TIMER_IFLG_AUTO;
|
|
||||||
spin_lock_irq(&tmr->lock);
|
spin_lock_irq(&tmr->lock);
|
||||||
tmr->timeri = t;
|
tmr->timeri = t;
|
||||||
spin_unlock_irq(&tmr->lock);
|
spin_unlock_irq(&tmr->lock);
|
||||||
@@ -310,8 +314,10 @@ int snd_seq_timer_close(struct snd_seq_queue *q)
|
|||||||
t = tmr->timeri;
|
t = tmr->timeri;
|
||||||
tmr->timeri = NULL;
|
tmr->timeri = NULL;
|
||||||
spin_unlock_irq(&tmr->lock);
|
spin_unlock_irq(&tmr->lock);
|
||||||
if (t)
|
if (t) {
|
||||||
snd_timer_close(t);
|
snd_timer_close(t);
|
||||||
|
snd_timer_instance_free(t);
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -74,6 +74,9 @@ static LIST_HEAD(snd_timer_slave_list);
|
|||||||
/* lock for slave active lists */
|
/* lock for slave active lists */
|
||||||
static DEFINE_SPINLOCK(slave_active_lock);
|
static DEFINE_SPINLOCK(slave_active_lock);
|
||||||
|
|
||||||
|
#define MAX_SLAVE_INSTANCES 1000
|
||||||
|
static int num_slaves;
|
||||||
|
|
||||||
static DEFINE_MUTEX(register_mutex);
|
static DEFINE_MUTEX(register_mutex);
|
||||||
|
|
||||||
static int snd_timer_free(struct snd_timer *timer);
|
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.
|
* 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_instance *snd_timer_instance_new(const char *owner)
|
||||||
struct snd_timer *timer)
|
|
||||||
{
|
{
|
||||||
struct snd_timer_instance *timeri;
|
struct snd_timer_instance *timeri;
|
||||||
|
|
||||||
timeri = kzalloc(sizeof(*timeri), GFP_KERNEL);
|
timeri = kzalloc(sizeof(*timeri), GFP_KERNEL);
|
||||||
if (timeri == NULL)
|
if (timeri == NULL)
|
||||||
return 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_list_head);
|
||||||
INIT_LIST_HEAD(&timeri->slave_active_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;
|
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
|
* find a timer instance from the given timer id
|
||||||
@@ -160,6 +167,28 @@ static void snd_timer_request(struct snd_timer_id *tid)
|
|||||||
|
|
||||||
#endif
|
#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.
|
* look for a master instance matching with the slave id of the given slave.
|
||||||
* when found, relink the open_link of the 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 *timer;
|
||||||
struct snd_timer_instance *master;
|
struct snd_timer_instance *master;
|
||||||
|
int err = 0;
|
||||||
|
|
||||||
/* FIXME: it's really dumb to look up all entries.. */
|
/* FIXME: it's really dumb to look up all entries.. */
|
||||||
list_for_each_entry(timer, &snd_timer_list, device_list) {
|
list_for_each_entry(timer, &snd_timer_list, device_list) {
|
||||||
list_for_each_entry(master, &timer->open_list_head, open_list) {
|
list_for_each_entry(master, &timer->open_list_head, open_list) {
|
||||||
if (slave->slave_class == master->slave_class &&
|
err = check_matching_master_slave(master, slave);
|
||||||
slave->slave_id == master->slave_id) {
|
if (err != 0) /* match found or error */
|
||||||
if (master->timer->num_instances >=
|
goto out;
|
||||||
master->timer->max_instances)
|
|
||||||
return -EBUSY;
|
|
||||||
list_move_tail(&slave->open_list,
|
|
||||||
&master->slave_list_head);
|
|
||||||
master->timer->num_instances++;
|
|
||||||
spin_lock_irq(&slave_active_lock);
|
|
||||||
slave->master = master;
|
|
||||||
slave->timer = master->timer;
|
|
||||||
spin_unlock_irq(&slave_active_lock);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
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)
|
static int snd_timer_check_master(struct snd_timer_instance *master)
|
||||||
{
|
{
|
||||||
struct snd_timer_instance *slave, *tmp;
|
struct snd_timer_instance *slave, *tmp;
|
||||||
|
int err = 0;
|
||||||
|
|
||||||
/* check all pending slaves */
|
/* check all pending slaves */
|
||||||
list_for_each_entry_safe(slave, tmp, &snd_timer_slave_list, open_list) {
|
list_for_each_entry_safe(slave, tmp, &snd_timer_slave_list, open_list) {
|
||||||
if (slave->slave_class == master->slave_class &&
|
err = check_matching_master_slave(master, slave);
|
||||||
slave->slave_id == master->slave_id) {
|
if (err < 0)
|
||||||
if (master->timer->num_instances >=
|
break;
|
||||||
master->timer->max_instances)
|
|
||||||
return -EBUSY;
|
|
||||||
list_move_tail(&slave->open_list, &master->slave_list_head);
|
|
||||||
master->timer->num_instances++;
|
|
||||||
spin_lock_irq(&slave_active_lock);
|
|
||||||
spin_lock(&master->timer->lock);
|
|
||||||
slave->master = master;
|
|
||||||
slave->timer = master->timer;
|
|
||||||
if (slave->flags & SNDRV_TIMER_IFLG_RUNNING)
|
|
||||||
list_add_tail(&slave->active_list,
|
|
||||||
&master->slave_active_head);
|
|
||||||
spin_unlock(&master->timer->lock);
|
|
||||||
spin_unlock_irq(&slave_active_lock);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return 0;
|
return err < 0 ? err : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int snd_timer_close_locked(struct snd_timer_instance *timeri,
|
static void snd_timer_close_locked(struct snd_timer_instance *timeri,
|
||||||
struct device **card_devp_to_put);
|
struct device **card_devp_to_put);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* open a timer instance
|
* open a timer instance
|
||||||
* when opening a master, the slave id must be here given.
|
* when opening a master, the slave id must be here given.
|
||||||
*/
|
*/
|
||||||
int snd_timer_open(struct snd_timer_instance **ti,
|
int snd_timer_open(struct snd_timer_instance *timeri,
|
||||||
char *owner, struct snd_timer_id *tid,
|
struct snd_timer_id *tid,
|
||||||
unsigned int slave_id)
|
unsigned int slave_id)
|
||||||
{
|
{
|
||||||
struct snd_timer *timer;
|
struct snd_timer *timer;
|
||||||
struct snd_timer_instance *timeri = NULL;
|
|
||||||
struct device *card_dev_to_put = NULL;
|
struct device *card_dev_to_put = NULL;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
@@ -252,21 +258,17 @@ int snd_timer_open(struct snd_timer_instance **ti,
|
|||||||
err = -EINVAL;
|
err = -EINVAL;
|
||||||
goto unlock;
|
goto unlock;
|
||||||
}
|
}
|
||||||
timeri = snd_timer_instance_new(owner, NULL);
|
if (num_slaves >= MAX_SLAVE_INSTANCES) {
|
||||||
if (!timeri) {
|
err = -EBUSY;
|
||||||
err = -ENOMEM;
|
|
||||||
goto unlock;
|
goto unlock;
|
||||||
}
|
}
|
||||||
timeri->slave_class = tid->dev_sclass;
|
timeri->slave_class = tid->dev_sclass;
|
||||||
timeri->slave_id = tid->device;
|
timeri->slave_id = tid->device;
|
||||||
timeri->flags |= SNDRV_TIMER_IFLG_SLAVE;
|
timeri->flags |= SNDRV_TIMER_IFLG_SLAVE;
|
||||||
list_add_tail(&timeri->open_list, &snd_timer_slave_list);
|
list_add_tail(&timeri->open_list, &snd_timer_slave_list);
|
||||||
|
num_slaves++;
|
||||||
err = snd_timer_check_slave(timeri);
|
err = snd_timer_check_slave(timeri);
|
||||||
if (err < 0) {
|
goto list_added;
|
||||||
snd_timer_close_locked(timeri, &card_dev_to_put);
|
|
||||||
timeri = NULL;
|
|
||||||
}
|
|
||||||
goto unlock;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* open a master instance */
|
/* open a master instance */
|
||||||
@@ -296,45 +298,40 @@ int snd_timer_open(struct snd_timer_instance **ti,
|
|||||||
err = -EBUSY;
|
err = -EBUSY;
|
||||||
goto unlock;
|
goto unlock;
|
||||||
}
|
}
|
||||||
timeri = snd_timer_instance_new(owner, timer);
|
if (!try_module_get(timer->module)) {
|
||||||
if (!timeri) {
|
err = -EBUSY;
|
||||||
err = -ENOMEM;
|
|
||||||
goto unlock;
|
goto unlock;
|
||||||
}
|
}
|
||||||
/* take a card refcount for safe disconnection */
|
/* take a card refcount for safe disconnection */
|
||||||
if (timer->card)
|
if (timer->card) {
|
||||||
get_device(&timer->card->card_dev);
|
get_device(&timer->card->card_dev);
|
||||||
timeri->slave_class = tid->dev_sclass;
|
card_dev_to_put = &timer->card->card_dev;
|
||||||
timeri->slave_id = slave_id;
|
}
|
||||||
|
|
||||||
if (list_empty(&timer->open_list_head) && timer->hw.open) {
|
if (list_empty(&timer->open_list_head) && timer->hw.open) {
|
||||||
err = timer->hw.open(timer);
|
err = timer->hw.open(timer);
|
||||||
if (err) {
|
if (err) {
|
||||||
kfree(timeri->owner);
|
|
||||||
kfree(timeri);
|
|
||||||
timeri = NULL;
|
|
||||||
|
|
||||||
if (timer->card)
|
|
||||||
card_dev_to_put = &timer->card->card_dev;
|
|
||||||
module_put(timer->module);
|
module_put(timer->module);
|
||||||
goto unlock;
|
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);
|
list_add_tail(&timeri->open_list, &timer->open_list_head);
|
||||||
timer->num_instances++;
|
timer->num_instances++;
|
||||||
err = snd_timer_check_master(timeri);
|
err = snd_timer_check_master(timeri);
|
||||||
if (err < 0) {
|
list_added:
|
||||||
|
if (err < 0)
|
||||||
snd_timer_close_locked(timeri, &card_dev_to_put);
|
snd_timer_close_locked(timeri, &card_dev_to_put);
|
||||||
timeri = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
unlock:
|
unlock:
|
||||||
mutex_unlock(®ister_mutex);
|
mutex_unlock(®ister_mutex);
|
||||||
/* put_device() is called after unlock for avoiding deadlock */
|
/* 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);
|
put_device(card_dev_to_put);
|
||||||
*ti = timeri;
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(snd_timer_open);
|
EXPORT_SYMBOL(snd_timer_open);
|
||||||
@@ -343,8 +340,8 @@ EXPORT_SYMBOL(snd_timer_open);
|
|||||||
* close a timer instance
|
* close a timer instance
|
||||||
* call this with register_mutex down.
|
* call this with register_mutex down.
|
||||||
*/
|
*/
|
||||||
static int snd_timer_close_locked(struct snd_timer_instance *timeri,
|
static void snd_timer_close_locked(struct snd_timer_instance *timeri,
|
||||||
struct device **card_devp_to_put)
|
struct device **card_devp_to_put)
|
||||||
{
|
{
|
||||||
struct snd_timer *timer = timeri->timer;
|
struct snd_timer *timer = timeri->timer;
|
||||||
struct snd_timer_instance *slave, *tmp;
|
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);
|
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 */
|
/* force to stop the timer */
|
||||||
snd_timer_stop(timeri);
|
snd_timer_stop(timeri);
|
||||||
@@ -374,6 +375,7 @@ static int snd_timer_close_locked(struct snd_timer_instance *timeri,
|
|||||||
/* remove slave links */
|
/* remove slave links */
|
||||||
spin_lock_irq(&slave_active_lock);
|
spin_lock_irq(&slave_active_lock);
|
||||||
spin_lock(&timer->lock);
|
spin_lock(&timer->lock);
|
||||||
|
timeri->timer = NULL;
|
||||||
list_for_each_entry_safe(slave, tmp, &timeri->slave_list_head,
|
list_for_each_entry_safe(slave, tmp, &timeri->slave_list_head,
|
||||||
open_list) {
|
open_list) {
|
||||||
list_move_tail(&slave->open_list, &snd_timer_slave_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;
|
timer = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (timeri->private_free)
|
|
||||||
timeri->private_free(timeri);
|
|
||||||
kfree(timeri->owner);
|
|
||||||
kfree(timeri);
|
|
||||||
|
|
||||||
if (timer) {
|
if (timer) {
|
||||||
if (list_empty(&timer->open_list_head) && timer->hw.close)
|
if (list_empty(&timer->open_list_head) && timer->hw.close)
|
||||||
timer->hw.close(timer);
|
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;
|
*card_devp_to_put = &timer->card->card_dev;
|
||||||
module_put(timer->module);
|
module_put(timer->module);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* close a timer instance
|
* 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;
|
struct device *card_dev_to_put = NULL;
|
||||||
int err;
|
|
||||||
|
|
||||||
if (snd_BUG_ON(!timeri))
|
if (snd_BUG_ON(!timeri))
|
||||||
return -ENXIO;
|
return;
|
||||||
|
|
||||||
mutex_lock(®ister_mutex);
|
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);
|
mutex_unlock(®ister_mutex);
|
||||||
/* put_device() is called after unlock for avoiding deadlock */
|
/* put_device() is called after unlock for avoiding deadlock */
|
||||||
if (card_dev_to_put)
|
if (card_dev_to_put)
|
||||||
put_device(card_dev_to_put);
|
put_device(card_dev_to_put);
|
||||||
return err;
|
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(snd_timer_close);
|
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;
|
tu = file->private_data;
|
||||||
file->private_data = NULL;
|
file->private_data = NULL;
|
||||||
mutex_lock(&tu->ioctl_lock);
|
mutex_lock(&tu->ioctl_lock);
|
||||||
if (tu->timeri)
|
if (tu->timeri) {
|
||||||
snd_timer_close(tu->timeri);
|
snd_timer_close(tu->timeri);
|
||||||
|
snd_timer_instance_free(tu->timeri);
|
||||||
|
}
|
||||||
mutex_unlock(&tu->ioctl_lock);
|
mutex_unlock(&tu->ioctl_lock);
|
||||||
kfree(tu->queue);
|
kfree(tu->queue);
|
||||||
kfree(tu->tqueue);
|
kfree(tu->tqueue);
|
||||||
@@ -1716,6 +1711,7 @@ static int snd_timer_user_tselect(struct file *file,
|
|||||||
tu = file->private_data;
|
tu = file->private_data;
|
||||||
if (tu->timeri) {
|
if (tu->timeri) {
|
||||||
snd_timer_close(tu->timeri);
|
snd_timer_close(tu->timeri);
|
||||||
|
snd_timer_instance_free(tu->timeri);
|
||||||
tu->timeri = NULL;
|
tu->timeri = NULL;
|
||||||
}
|
}
|
||||||
if (copy_from_user(&tselect, _tselect, sizeof(tselect))) {
|
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);
|
sprintf(str, "application %i", current->pid);
|
||||||
if (tselect.id.dev_class != SNDRV_TIMER_CLASS_SLAVE)
|
if (tselect.id.dev_class != SNDRV_TIMER_CLASS_SLAVE)
|
||||||
tselect.id.dev_sclass = SNDRV_TIMER_SCLASS_APPLICATION;
|
tselect.id.dev_sclass = SNDRV_TIMER_SCLASS_APPLICATION;
|
||||||
err = snd_timer_open(&tu->timeri, str, &tselect.id, current->pid);
|
tu->timeri = snd_timer_instance_new(str);
|
||||||
if (err < 0)
|
if (!tu->timeri) {
|
||||||
|
err = -ENOMEM;
|
||||||
goto __err;
|
goto __err;
|
||||||
|
}
|
||||||
|
|
||||||
tu->timeri->flags |= SNDRV_TIMER_IFLG_FAST;
|
tu->timeri->flags |= SNDRV_TIMER_IFLG_FAST;
|
||||||
tu->timeri->callback = tu->tread
|
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->callback_data = (void *)tu;
|
||||||
tu->timeri->disconnect = snd_timer_user_disconnect;
|
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:
|
__err:
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
# SPDX-License-Identifier: GPL-2.0-only
|
# SPDX-License-Identifier: GPL-2.0-only
|
||||||
config SND_MPU401_UART
|
config SND_MPU401_UART
|
||||||
tristate
|
tristate
|
||||||
select SND_RAWMIDI
|
select SND_RAWMIDI
|
||||||
|
|
||||||
config SND_OPL3_LIB
|
config SND_OPL3_LIB
|
||||||
tristate
|
tristate
|
||||||
@@ -90,16 +90,17 @@ config SND_DUMMY
|
|||||||
will be called snd-dummy.
|
will be called snd-dummy.
|
||||||
|
|
||||||
config SND_ALOOP
|
config SND_ALOOP
|
||||||
tristate "Generic loopback driver (PCM)"
|
tristate "Generic loopback driver (PCM)"
|
||||||
select SND_PCM
|
select SND_PCM
|
||||||
help
|
select SND_TIMER
|
||||||
Say 'Y' or 'M' to include support for the PCM loopback device.
|
help
|
||||||
|
Say 'Y' or 'M' to include support for the PCM loopback device.
|
||||||
This module returns played samples back to the user space using
|
This module returns played samples back to the user space using
|
||||||
the standard ALSA PCM device. The devices are routed 0->1 and
|
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
|
number is the capture device. Module creates two PCM devices and
|
||||||
configured number of substreams (see the pcm_substreams module
|
configured number of substreams (see the pcm_substreams module
|
||||||
parameter).
|
parameter).
|
||||||
|
|
||||||
The loopback device allows time sychronization with an external
|
The loopback device allows time sychronization with an external
|
||||||
timing source using the time shift universal control (+-20%
|
timing source using the time shift universal control (+-20%
|
||||||
@@ -142,12 +143,12 @@ config SND_MTS64
|
|||||||
select SND_RAWMIDI
|
select SND_RAWMIDI
|
||||||
help
|
help
|
||||||
The ESI Miditerminal 4140 is a 4 In 4 Out MIDI Interface with
|
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.
|
Say 'Y' to include support for this device.
|
||||||
|
|
||||||
To compile this driver as a module, chose 'M' here: the module
|
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
|
config SND_SERIAL_U16550
|
||||||
tristate "UART16550 serial MIDI driver"
|
tristate "UART16550 serial MIDI driver"
|
||||||
|
@@ -28,6 +28,7 @@
|
|||||||
#include <sound/pcm_params.h>
|
#include <sound/pcm_params.h>
|
||||||
#include <sound/info.h>
|
#include <sound/info.h>
|
||||||
#include <sound/initval.h>
|
#include <sound/initval.h>
|
||||||
|
#include <sound/timer.h>
|
||||||
|
|
||||||
MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
|
MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
|
||||||
MODULE_DESCRIPTION("A loopback soundcard");
|
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 bool enable[SNDRV_CARDS] = {1, [1 ... (SNDRV_CARDS - 1)] = 0};
|
||||||
static int pcm_substreams[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 8};
|
static int pcm_substreams[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 8};
|
||||||
static int pcm_notify[SNDRV_CARDS];
|
static int pcm_notify[SNDRV_CARDS];
|
||||||
|
static char *timer_source[SNDRV_CARDS];
|
||||||
|
|
||||||
module_param_array(index, int, NULL, 0444);
|
module_param_array(index, int, NULL, 0444);
|
||||||
MODULE_PARM_DESC(index, "Index value for loopback soundcard.");
|
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_PARM_DESC(pcm_substreams, "PCM substreams # (1-8) for loopback driver.");
|
||||||
module_param_array(pcm_notify, int, NULL, 0444);
|
module_param_array(pcm_notify, int, NULL, 0444);
|
||||||
MODULE_PARM_DESC(pcm_notify, "Break capture when PCM format/rate/channels changes.");
|
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 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_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 {
|
struct loopback_cable {
|
||||||
spinlock_t lock;
|
spinlock_t lock;
|
||||||
struct loopback_pcm *streams[2];
|
struct loopback_pcm *streams[2];
|
||||||
@@ -65,6 +104,15 @@ struct loopback_cable {
|
|||||||
unsigned int valid;
|
unsigned int valid;
|
||||||
unsigned int running;
|
unsigned int running;
|
||||||
unsigned int pause;
|
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 {
|
struct loopback_setup {
|
||||||
@@ -85,6 +133,7 @@ struct loopback {
|
|||||||
struct loopback_cable *cables[MAX_PCM_SUBSTREAMS][2];
|
struct loopback_cable *cables[MAX_PCM_SUBSTREAMS][2];
|
||||||
struct snd_pcm *pcm[2];
|
struct snd_pcm *pcm[2];
|
||||||
struct loopback_setup setup[MAX_PCM_SUBSTREAMS][2];
|
struct loopback_setup setup[MAX_PCM_SUBSTREAMS][2];
|
||||||
|
const char *timer_source;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct loopback_pcm {
|
struct loopback_pcm {
|
||||||
@@ -102,10 +151,13 @@ struct loopback_pcm {
|
|||||||
/* flags */
|
/* flags */
|
||||||
unsigned int period_update_pending :1;
|
unsigned int period_update_pending :1;
|
||||||
/* timer stuff */
|
/* timer stuff */
|
||||||
unsigned int irq_pos; /* fractional IRQ position */
|
unsigned int irq_pos; /* fractional IRQ position in jiffies
|
||||||
unsigned int period_size_frac;
|
* ticks
|
||||||
|
*/
|
||||||
|
unsigned int period_size_frac; /* period size in jiffies ticks */
|
||||||
unsigned int last_drift;
|
unsigned int last_drift;
|
||||||
unsigned long last_jiffies;
|
unsigned long last_jiffies;
|
||||||
|
/* If jiffies timer is used */
|
||||||
struct timer_list timer;
|
struct timer_list timer;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -153,7 +205,7 @@ static inline unsigned int get_rate_shift(struct loopback_pcm *dpcm)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* call in cable->lock */
|
/* 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 long tick;
|
||||||
unsigned int rate_shift = get_rate_shift(dpcm);
|
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 = dpcm->period_size_frac - dpcm->irq_pos;
|
||||||
tick = (tick + dpcm->pcm_bps - 1) / dpcm->pcm_bps;
|
tick = (tick + dpcm->pcm_bps - 1) / dpcm->pcm_bps;
|
||||||
mod_timer(&dpcm->timer, jiffies + tick);
|
mod_timer(&dpcm->timer, jiffies + tick);
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* call in cable->lock */
|
/* 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);
|
del_timer(&dpcm->timer);
|
||||||
dpcm->timer.expires = 0;
|
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);
|
del_timer_sync(&dpcm->timer);
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define CABLE_VALID_PLAYBACK (1 << SNDRV_PCM_STREAM_PLAYBACK)
|
/* call in loopback->cable_lock */
|
||||||
#define CABLE_VALID_CAPTURE (1 << SNDRV_PCM_STREAM_CAPTURE)
|
static int loopback_snd_timer_close_cable(struct loopback_pcm *dpcm)
|
||||||
#define CABLE_VALID_BOTH (CABLE_VALID_PLAYBACK|CABLE_VALID_CAPTURE)
|
{
|
||||||
|
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)
|
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 snd_pcm_runtime *runtime = substream->runtime;
|
||||||
struct loopback_pcm *dpcm = runtime->private_data;
|
struct loopback_pcm *dpcm = runtime->private_data;
|
||||||
struct loopback_cable *cable = dpcm->cable;
|
struct loopback_cable *cable = dpcm->cable;
|
||||||
int err, stream = 1 << substream->stream;
|
int err = 0, stream = 1 << substream->stream;
|
||||||
|
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
case SNDRV_PCM_TRIGGER_START:
|
case SNDRV_PCM_TRIGGER_START:
|
||||||
@@ -262,7 +393,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
|
|||||||
spin_lock(&cable->lock);
|
spin_lock(&cable->lock);
|
||||||
cable->running |= stream;
|
cable->running |= stream;
|
||||||
cable->pause &= ~stream;
|
cable->pause &= ~stream;
|
||||||
loopback_timer_start(dpcm);
|
err = cable->ops->start(dpcm);
|
||||||
spin_unlock(&cable->lock);
|
spin_unlock(&cable->lock);
|
||||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||||
loopback_active_notify(dpcm);
|
loopback_active_notify(dpcm);
|
||||||
@@ -271,7 +402,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
|
|||||||
spin_lock(&cable->lock);
|
spin_lock(&cable->lock);
|
||||||
cable->running &= ~stream;
|
cable->running &= ~stream;
|
||||||
cable->pause &= ~stream;
|
cable->pause &= ~stream;
|
||||||
loopback_timer_stop(dpcm);
|
err = cable->ops->stop(dpcm);
|
||||||
spin_unlock(&cable->lock);
|
spin_unlock(&cable->lock);
|
||||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||||
loopback_active_notify(dpcm);
|
loopback_active_notify(dpcm);
|
||||||
@@ -280,7 +411,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
|
|||||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||||
spin_lock(&cable->lock);
|
spin_lock(&cable->lock);
|
||||||
cable->pause |= stream;
|
cable->pause |= stream;
|
||||||
loopback_timer_stop(dpcm);
|
err = cable->ops->stop(dpcm);
|
||||||
spin_unlock(&cable->lock);
|
spin_unlock(&cable->lock);
|
||||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||||
loopback_active_notify(dpcm);
|
loopback_active_notify(dpcm);
|
||||||
@@ -290,7 +421,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
|
|||||||
spin_lock(&cable->lock);
|
spin_lock(&cable->lock);
|
||||||
dpcm->last_jiffies = jiffies;
|
dpcm->last_jiffies = jiffies;
|
||||||
cable->pause &= ~stream;
|
cable->pause &= ~stream;
|
||||||
loopback_timer_start(dpcm);
|
err = cable->ops->start(dpcm);
|
||||||
spin_unlock(&cable->lock);
|
spin_unlock(&cable->lock);
|
||||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||||
loopback_active_notify(dpcm);
|
loopback_active_notify(dpcm);
|
||||||
@@ -298,7 +429,7 @@ static int loopback_trigger(struct snd_pcm_substream *substream, int cmd)
|
|||||||
default:
|
default:
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
return 0;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void params_change(struct snd_pcm_substream *substream)
|
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.rate_max = runtime->rate;
|
||||||
cable->hw.channels_min = runtime->channels;
|
cable->hw.channels_min = runtime->channels;
|
||||||
cable->hw.channels_max = 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)
|
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 snd_pcm_runtime *runtime = substream->runtime;
|
||||||
struct loopback_pcm *dpcm = runtime->private_data;
|
struct loopback_pcm *dpcm = runtime->private_data;
|
||||||
struct loopback_cable *cable = dpcm->cable;
|
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) *
|
salign = (snd_pcm_format_physical_width(runtime->format) *
|
||||||
runtime->channels) / 8;
|
runtime->channels) / 8;
|
||||||
@@ -457,7 +599,8 @@ static inline void bytepos_finish(struct loopback_pcm *dpcm,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* call in cable->lock */
|
/* 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 =
|
struct loopback_pcm *dpcm_play =
|
||||||
cable->streams[SNDRV_PCM_STREAM_PLAYBACK];
|
cable->streams[SNDRV_PCM_STREAM_PLAYBACK];
|
||||||
@@ -510,14 +653,15 @@ static unsigned int loopback_pos_update(struct loopback_cable *cable)
|
|||||||
return running;
|
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);
|
struct loopback_pcm *dpcm = from_timer(dpcm, t, timer);
|
||||||
unsigned long flags;
|
unsigned long flags;
|
||||||
|
|
||||||
spin_lock_irqsave(&dpcm->cable->lock, flags);
|
spin_lock_irqsave(&dpcm->cable->lock, flags);
|
||||||
if (loopback_pos_update(dpcm->cable) & (1 << dpcm->substream->stream)) {
|
if (loopback_jiffies_timer_pos_update(dpcm->cable) &
|
||||||
loopback_timer_start(dpcm);
|
(1 << dpcm->substream->stream)) {
|
||||||
|
loopback_jiffies_timer_start(dpcm);
|
||||||
if (dpcm->period_update_pending) {
|
if (dpcm->period_update_pending) {
|
||||||
dpcm->period_update_pending = 0;
|
dpcm->period_update_pending = 0;
|
||||||
spin_unlock_irqrestore(&dpcm->cable->lock, flags);
|
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);
|
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)
|
static snd_pcm_uframes_t loopback_pointer(struct snd_pcm_substream *substream)
|
||||||
{
|
{
|
||||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
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;
|
snd_pcm_uframes_t pos;
|
||||||
|
|
||||||
spin_lock(&dpcm->cable->lock);
|
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;
|
pos = dpcm->buf_pos;
|
||||||
spin_unlock(&dpcm->cable->lock);
|
spin_unlock(&dpcm->cable->lock);
|
||||||
return bytes_to_frames(runtime, pos);
|
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,
|
static int loopback_hw_params(struct snd_pcm_substream *substream,
|
||||||
struct snd_pcm_hw_params *params)
|
struct snd_pcm_hw_params *params)
|
||||||
{
|
{
|
||||||
return snd_pcm_lib_alloc_vmalloc_buffer(substream,
|
return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
|
||||||
params_buffer_bytes(params));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int loopback_hw_free(struct snd_pcm_substream *substream)
|
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);
|
mutex_lock(&dpcm->loopback->cable_lock);
|
||||||
cable->valid &= ~(1 << substream->stream);
|
cable->valid &= ~(1 << substream->stream);
|
||||||
mutex_unlock(&dpcm->loopback->cable_lock);
|
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)
|
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);
|
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)
|
static void free_cable(struct snd_pcm_substream *substream)
|
||||||
{
|
{
|
||||||
struct loopback *loopback = substream->private_data;
|
struct loopback *loopback = substream->private_data;
|
||||||
@@ -662,12 +1010,183 @@ static void free_cable(struct snd_pcm_substream *substream)
|
|||||||
cable->streams[substream->stream] = NULL;
|
cable->streams[substream->stream] = NULL;
|
||||||
spin_unlock_irq(&cable->lock);
|
spin_unlock_irq(&cable->lock);
|
||||||
} else {
|
} 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 */
|
/* free the cable */
|
||||||
loopback->cables[substream->number][dev] = NULL;
|
loopback->cables[substream->number][dev] = NULL;
|
||||||
kfree(cable);
|
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)
|
static int loopback_open(struct snd_pcm_substream *substream)
|
||||||
{
|
{
|
||||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||||
@@ -685,7 +1204,6 @@ static int loopback_open(struct snd_pcm_substream *substream)
|
|||||||
}
|
}
|
||||||
dpcm->loopback = loopback;
|
dpcm->loopback = loopback;
|
||||||
dpcm->substream = substream;
|
dpcm->substream = substream;
|
||||||
timer_setup(&dpcm->timer, loopback_timer_function, 0);
|
|
||||||
|
|
||||||
cable = loopback->cables[substream->number][dev];
|
cable = loopback->cables[substream->number][dev];
|
||||||
if (!cable) {
|
if (!cable) {
|
||||||
@@ -696,9 +1214,20 @@ static int loopback_open(struct snd_pcm_substream *substream)
|
|||||||
}
|
}
|
||||||
spin_lock_init(&cable->lock);
|
spin_lock_init(&cable->lock);
|
||||||
cable->hw = loopback_pcm_hardware;
|
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;
|
loopback->cables[substream->number][dev] = cable;
|
||||||
}
|
}
|
||||||
dpcm->cable = 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);
|
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)
|
if (err < 0)
|
||||||
goto unlock;
|
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;
|
runtime->private_free = loopback_runtime_free;
|
||||||
if (get_notify(dpcm))
|
if (get_notify(dpcm))
|
||||||
runtime->hw = loopback_pcm_hardware;
|
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 *loopback = substream->private_data;
|
||||||
struct loopback_pcm *dpcm = substream->runtime->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);
|
mutex_lock(&loopback->cable_lock);
|
||||||
free_cable(substream);
|
free_cable(substream);
|
||||||
mutex_unlock(&loopback->cable_lock);
|
mutex_unlock(&loopback->cable_lock);
|
||||||
return 0;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct snd_pcm_ops loopback_pcm_ops = {
|
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,
|
.prepare = loopback_prepare,
|
||||||
.trigger = loopback_trigger,
|
.trigger = loopback_trigger,
|
||||||
.pointer = loopback_pointer,
|
.pointer = loopback_pointer,
|
||||||
.page = snd_pcm_lib_get_vmalloc_page,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static int loopback_pcm_new(struct loopback *loopback,
|
static int loopback_pcm_new(struct loopback *loopback,
|
||||||
@@ -780,6 +1325,8 @@ static int loopback_pcm_new(struct loopback *loopback,
|
|||||||
return err;
|
return err;
|
||||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &loopback_pcm_ops);
|
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_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->private_data = loopback;
|
||||||
pcm->info_flags = 0;
|
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, " bytes_per_sec:\t%u\n", dpcm->pcm_bps);
|
||||||
snd_iprintf(buffer, " sample_align:\t%u\n", dpcm->pcm_salign);
|
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, " rate_shift:\t\t%u\n", dpcm->pcm_rate_shift);
|
||||||
snd_iprintf(buffer, " update_pending:\t%u\n",
|
if (dpcm->cable->ops->dpcm_info)
|
||||||
dpcm->period_update_pending);
|
dpcm->cable->ops->dpcm_info(dpcm, buffer);
|
||||||
snd_iprintf(buffer, " irq_pos:\t\t%u\n", dpcm->irq_pos);
|
|
||||||
snd_iprintf(buffer, " period_frac:\t%u\n", dpcm->period_size_frac);
|
|
||||||
snd_iprintf(buffer, " last_jiffies:\t%lu (%lu)\n",
|
|
||||||
dpcm->last_jiffies, jiffies);
|
|
||||||
snd_iprintf(buffer, " timer_expires:\t%lu\n", dpcm->timer.expires);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void print_substream_info(struct snd_info_buffer *buffer,
|
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);
|
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];
|
char name[32];
|
||||||
|
|
||||||
@@ -1127,6 +1669,48 @@ static int loopback_proc_new(struct loopback *loopback, int cidx)
|
|||||||
print_cable_info);
|
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)
|
static int loopback_probe(struct platform_device *devptr)
|
||||||
{
|
{
|
||||||
struct snd_card *card;
|
struct snd_card *card;
|
||||||
@@ -1146,6 +1730,8 @@ static int loopback_probe(struct platform_device *devptr)
|
|||||||
pcm_substreams[dev] = MAX_PCM_SUBSTREAMS;
|
pcm_substreams[dev] = MAX_PCM_SUBSTREAMS;
|
||||||
|
|
||||||
loopback->card = card;
|
loopback->card = card;
|
||||||
|
loopback_set_timer_source(loopback, timer_source[dev]);
|
||||||
|
|
||||||
mutex_init(&loopback->cable_lock);
|
mutex_init(&loopback->cable_lock);
|
||||||
|
|
||||||
err = loopback_pcm_new(loopback, 0, pcm_substreams[dev]);
|
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);
|
err = loopback_mixer_new(loopback, pcm_notify[dev] ? 1 : 0);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto __nodev;
|
goto __nodev;
|
||||||
loopback_proc_new(loopback, 0);
|
loopback_cable_proc_new(loopback, 0);
|
||||||
loopback_proc_new(loopback, 1);
|
loopback_cable_proc_new(loopback, 1);
|
||||||
|
loopback_timer_source_proc_new(loopback);
|
||||||
strcpy(card->driver, "Loopback");
|
strcpy(card->driver, "Loopback");
|
||||||
strcpy(card->shortname, "Loopback");
|
strcpy(card->shortname, "Loopback");
|
||||||
sprintf(card->longname, "Loopback %i", dev + 1);
|
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) {
|
if (!fake_buffer) {
|
||||||
snd_pcm_lib_preallocate_pages_for_all(pcm,
|
snd_pcm_lib_preallocate_pages_for_all(pcm,
|
||||||
SNDRV_DMA_TYPE_CONTINUOUS,
|
SNDRV_DMA_TYPE_CONTINUOUS,
|
||||||
snd_dma_continuous_data(GFP_KERNEL),
|
NULL,
|
||||||
0, 64*1024);
|
0, 64*1024);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
@@ -1242,7 +1242,7 @@ snd_ml403_ac97cr_pcm(struct snd_ml403_ac97cr *ml403_ac97cr, int device)
|
|||||||
ml403_ac97cr->pcm = pcm;
|
ml403_ac97cr->pcm = pcm;
|
||||||
|
|
||||||
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
|
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
|
||||||
snd_dma_continuous_data(GFP_KERNEL),
|
NULL,
|
||||||
64 * 1024,
|
64 * 1024,
|
||||||
128 * 1024);
|
128 * 1024);
|
||||||
return 0;
|
return 0;
|
||||||
|
@@ -352,8 +352,8 @@ int snd_pcsp_new_pcm(struct snd_pcsp *chip)
|
|||||||
|
|
||||||
snd_pcm_lib_preallocate_pages_for_all(chip->pcm,
|
snd_pcm_lib_preallocate_pages_for_all(chip->pcm,
|
||||||
SNDRV_DMA_TYPE_CONTINUOUS,
|
SNDRV_DMA_TYPE_CONTINUOUS,
|
||||||
snd_dma_continuous_data
|
NULL,
|
||||||
(GFP_KERNEL), PCSP_BUFFER_SIZE,
|
PCSP_BUFFER_SIZE,
|
||||||
PCSP_BUFFER_SIZE);
|
PCSP_BUFFER_SIZE);
|
||||||
|
|
||||||
return 0;
|
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,
|
static int vx_pcm_hw_params(struct snd_pcm_substream *subs,
|
||||||
struct snd_pcm_hw_params *hw_params)
|
struct snd_pcm_hw_params *hw_params)
|
||||||
{
|
{
|
||||||
return snd_pcm_lib_alloc_vmalloc_32_buffer
|
return snd_pcm_lib_malloc_pages(subs, params_buffer_bytes(hw_params));
|
||||||
(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)
|
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,
|
.prepare = vx_pcm_prepare,
|
||||||
.trigger = vx_pcm_trigger,
|
.trigger = vx_pcm_trigger,
|
||||||
.pointer = vx_pcm_playback_pointer,
|
.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,
|
.prepare = vx_pcm_prepare,
|
||||||
.trigger = vx_pcm_trigger,
|
.trigger = vx_pcm_trigger,
|
||||||
.pointer = vx_pcm_capture_pointer,
|
.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);
|
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &vx_pcm_playback_ops);
|
||||||
if (ins)
|
if (ins)
|
||||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &vx_pcm_capture_ops);
|
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_data = chip;
|
||||||
pcm->private_free = snd_vx_pcm_free;
|
pcm->private_free = snd_vx_pcm_free;
|
||||||
|
@@ -77,7 +77,7 @@ config SND_BEBOB
|
|||||||
tristate "BridgeCo DM1000/DM1100/DM1500 with BeBoB firmware"
|
tristate "BridgeCo DM1000/DM1100/DM1500 with BeBoB firmware"
|
||||||
select SND_FIREWIRE_LIB
|
select SND_FIREWIRE_LIB
|
||||||
select SND_HWDEP
|
select SND_HWDEP
|
||||||
help
|
help
|
||||||
Say Y here to include support for FireWire devices based
|
Say Y here to include support for FireWire devices based
|
||||||
on BridgeCo DM1000/DM1100/DM1500 with BeBoB firmware:
|
on BridgeCo DM1000/DM1100/DM1500 with BeBoB firmware:
|
||||||
* Edirol FA-66/FA-101
|
* Edirol FA-66/FA-101
|
||||||
@@ -111,8 +111,8 @@ config SND_BEBOB
|
|||||||
* M-Audio FireWire 1814/ProjectMix IO
|
* M-Audio FireWire 1814/ProjectMix IO
|
||||||
* Digidesign Mbox 2 Pro
|
* Digidesign Mbox 2 Pro
|
||||||
|
|
||||||
To compile this driver as a module, choose M here: the module
|
To compile this driver as a module, choose M here: the module
|
||||||
will be called snd-bebob.
|
will be called snd-bebob.
|
||||||
|
|
||||||
config SND_FIREWIRE_DIGI00X
|
config SND_FIREWIRE_DIGI00X
|
||||||
tristate "Digidesign Digi 002/003 family support"
|
tristate "Digidesign Digi 002/003 family support"
|
||||||
|
@@ -9,6 +9,7 @@
|
|||||||
#include <linux/device.h>
|
#include <linux/device.h>
|
||||||
#include <linux/err.h>
|
#include <linux/err.h>
|
||||||
#include <linux/firewire.h>
|
#include <linux/firewire.h>
|
||||||
|
#include <linux/firewire-constants.h>
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <sound/pcm.h>
|
#include <sound/pcm.h>
|
||||||
@@ -52,10 +53,6 @@
|
|||||||
#define CIP_FMT_AM 0x10
|
#define CIP_FMT_AM 0x10
|
||||||
#define AMDTP_FDF_NO_DATA 0xff
|
#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.
|
// For iso header, tstamp and 2 CIP header.
|
||||||
#define IR_CTX_HEADER_SIZE_CIP 16
|
#define IR_CTX_HEADER_SIZE_CIP 16
|
||||||
// For iso header and tstamp.
|
// 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_runtime *runtime)
|
||||||
{
|
{
|
||||||
struct snd_pcm_hardware *hw = &runtime->hw;
|
struct snd_pcm_hardware *hw = &runtime->hw;
|
||||||
|
unsigned int ctx_header_size;
|
||||||
|
unsigned int maximum_usec_per_period;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
hw->info = SNDRV_PCM_INFO_BATCH |
|
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->period_bytes_max = hw->period_bytes_min * 2048;
|
||||||
hw->buffer_bytes_max = hw->period_bytes_max * hw->periods_min;
|
hw->buffer_bytes_max = hw->period_bytes_max * hw->periods_min;
|
||||||
|
|
||||||
/*
|
// Linux driver for 1394 OHCI controller voluntarily flushes isoc
|
||||||
* Currently firewire-lib processes 16 packets in one software
|
// context when total size of accumulated context header reaches
|
||||||
* interrupt callback. This equals to 2msec but actually the
|
// PAGE_SIZE. This kicks tasklet for the isoc context and brings
|
||||||
* interval of the interrupts has a jitter.
|
// callback in the middle of scheduled interrupts.
|
||||||
* Additionally, even if adding a constraint to fit period size to
|
// Although AMDTP streams in the same domain use the same events per
|
||||||
* 2msec, actual calculated frames per period doesn't equal to 2msec,
|
// IRQ, use the largest size of context header between IT/IR contexts.
|
||||||
* depending on sampling rate.
|
// Here, use the value of context header in IR context is for both
|
||||||
* Anyway, the interval to call snd_pcm_period_elapsed() cannot 2msec.
|
// contexts.
|
||||||
* Here let us use 5msec for safe period interrupt.
|
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,
|
err = snd_pcm_hw_constraint_minmax(runtime,
|
||||||
SNDRV_PCM_HW_PARAM_PERIOD_TIME,
|
SNDRV_PCM_HW_PARAM_PERIOD_TIME,
|
||||||
5000, UINT_MAX);
|
250, maximum_usec_per_period);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto end;
|
goto end;
|
||||||
|
|
||||||
@@ -436,11 +452,12 @@ static void pcm_period_tasklet(unsigned long data)
|
|||||||
snd_pcm_period_elapsed(pcm);
|
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;
|
int err;
|
||||||
|
|
||||||
params->interrupt = IS_ALIGNED(s->packet_index + 1, INTERRUPT_INTERVAL);
|
params->interrupt = sched_irq;
|
||||||
params->tag = s->tag;
|
params->tag = s->tag;
|
||||||
params->sy = 0;
|
params->sy = 0;
|
||||||
|
|
||||||
@@ -451,18 +468,18 @@ static int queue_packet(struct amdtp_stream *s, struct fw_iso_packet *params)
|
|||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (++s->packet_index >= QUEUE_LENGTH)
|
if (++s->packet_index >= s->queue_size)
|
||||||
s->packet_index = 0;
|
s->packet_index = 0;
|
||||||
end:
|
end:
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int queue_out_packet(struct amdtp_stream *s,
|
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->skip =
|
||||||
!!(params->header_length == 0 && params->payload_length == 0);
|
!!(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,
|
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->header_length = s->ctx_data.tx.ctx_header_size;
|
||||||
params->payload_length = s->ctx_data.tx.max_ctx_payload_length;
|
params->payload_length = s->ctx_data.tx.max_ctx_payload_length;
|
||||||
params->skip = false;
|
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],
|
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.
|
// 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
|
// This module queued the same number of isochronous cycle as the size of queue
|
||||||
// skip isochronous cycle, therefore it's OK to just increment the cycle by
|
// to kip isochronous cycle, therefore it's OK to just increment the cycle by
|
||||||
// QUEUE_LENGTH for scheduled cycle.
|
// the size of queue for scheduled cycle.
|
||||||
static inline u32 compute_it_cycle(const __be32 ctx_header_tstamp)
|
static inline u32 compute_it_cycle(const __be32 ctx_header_tstamp,
|
||||||
|
unsigned int queue_size)
|
||||||
{
|
{
|
||||||
u32 cycle = compute_cycle_count(ctx_header_tstamp);
|
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,
|
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) {
|
for (i = 0; i < packets; ++i) {
|
||||||
struct pkt_desc *desc = descs + 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 cycle;
|
||||||
unsigned int payload_length;
|
unsigned int payload_length;
|
||||||
unsigned int data_blocks;
|
unsigned int data_blocks;
|
||||||
@@ -730,9 +748,9 @@ static void generate_ideal_pkt_descs(struct amdtp_stream *s,
|
|||||||
|
|
||||||
for (i = 0; i < packets; ++i) {
|
for (i = 0; i < packets; ++i) {
|
||||||
struct pkt_desc *desc = descs + 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->syt = calculate_syt(s, desc->cycle);
|
||||||
desc->data_blocks = calculate_data_blocks(s, desc->syt);
|
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);
|
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,
|
static void out_stream_callback(struct fw_iso_context *context, u32 tstamp,
|
||||||
size_t header_length, void *header,
|
size_t header_length, void *header,
|
||||||
void *private_data)
|
void *private_data)
|
||||||
{
|
{
|
||||||
struct amdtp_stream *s = private_data;
|
struct amdtp_stream *s = private_data;
|
||||||
const __be32 *ctx_header = header;
|
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;
|
int i;
|
||||||
|
|
||||||
if (s->packet_index < 0)
|
if (s->packet_index < 0)
|
||||||
return;
|
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);
|
generate_ideal_pkt_descs(s, s->pkt_descs, ctx_header, packets);
|
||||||
|
|
||||||
process_ctx_payloads(s, s->pkt_descs, 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) {
|
for (i = 0; i < packets; ++i) {
|
||||||
const struct pkt_desc *desc = s->pkt_descs + i;
|
const struct pkt_desc *desc = s->pkt_descs + i;
|
||||||
unsigned int syt;
|
unsigned int syt;
|
||||||
@@ -796,6 +832,7 @@ static void out_stream_callback(struct fw_iso_context *context, u32 tstamp,
|
|||||||
struct fw_iso_packet params;
|
struct fw_iso_packet params;
|
||||||
__be32 header[IT_PKT_HEADER_SIZE_CIP / sizeof(__be32)];
|
__be32 header[IT_PKT_HEADER_SIZE_CIP / sizeof(__be32)];
|
||||||
} template = { {0}, {0} };
|
} template = { {0}, {0} };
|
||||||
|
bool sched_irq = false;
|
||||||
|
|
||||||
if (s->ctx_data.rx.syt_override < 0)
|
if (s->ctx_data.rx.syt_override < 0)
|
||||||
syt = desc->syt;
|
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,
|
desc->data_blocks, desc->data_block_counter,
|
||||||
syt, i);
|
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);
|
cancel_stream(s);
|
||||||
return;
|
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,
|
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)
|
void *private_data)
|
||||||
{
|
{
|
||||||
struct amdtp_stream *s = private_data;
|
struct amdtp_stream *s = private_data;
|
||||||
unsigned int packets;
|
|
||||||
__be32 *ctx_header = header;
|
__be32 *ctx_header = header;
|
||||||
|
unsigned int packets;
|
||||||
int i;
|
int i;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
if (s->packet_index < 0)
|
if (s->packet_index < 0)
|
||||||
return;
|
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;
|
packets = header_length / s->ctx_data.tx.ctx_header_size;
|
||||||
|
|
||||||
err = generate_device_pkt_descs(s, s->pkt_descs, ctx_header, packets);
|
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;
|
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,
|
static void amdtp_stream_first_callback(struct fw_iso_context *context,
|
||||||
u32 tstamp, size_t header_length,
|
u32 tstamp, size_t header_length,
|
||||||
void *header, void *private_data)
|
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;
|
context->callback.sc = in_stream_callback;
|
||||||
} else {
|
} else {
|
||||||
cycle = compute_it_cycle(*ctx_header);
|
cycle = compute_it_cycle(*ctx_header, s->queue_size);
|
||||||
|
|
||||||
context->callback.sc = out_stream_callback;
|
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);
|
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
|
* amdtp_stream_start - start transferring packets
|
||||||
* @s: the AMDTP stream to start
|
* @s: the AMDTP stream to start
|
||||||
* @channel: the isochronous channel on the bus
|
* @channel: the isochronous channel on the bus
|
||||||
* @speed: firewire speed code
|
* @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
|
* 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
|
* amdtp_stream_set_parameters() and it must be started before any PCM or MIDI
|
||||||
* device can be started.
|
* 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 {
|
static const struct {
|
||||||
unsigned int data_block;
|
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_88200] = { 0, 67 },
|
||||||
[CIP_SFC_176400] = { 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 ctx_header_size;
|
||||||
unsigned int max_ctx_payload_size;
|
unsigned int max_ctx_payload_size;
|
||||||
enum dma_data_direction dir;
|
enum dma_data_direction dir;
|
||||||
int type, tag, err;
|
int type, tag, err;
|
||||||
|
fw_iso_callback_t ctx_cb;
|
||||||
|
void *ctx_data;
|
||||||
|
|
||||||
mutex_lock(&s->mutex);
|
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) {
|
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;
|
s->data_block_counter = UINT_MAX;
|
||||||
} else {
|
} else {
|
||||||
entry = &initial_state[s->sfc];
|
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;
|
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);
|
max_ctx_payload_size, dir);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto err_unlock;
|
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,
|
s->context = fw_iso_context_create(fw_parent_device(s->unit)->card,
|
||||||
type, channel, speed, ctx_header_size,
|
type, channel, speed, ctx_header_size,
|
||||||
amdtp_stream_first_callback, s);
|
ctx_cb, ctx_data);
|
||||||
if (IS_ERR(s->context)) {
|
if (IS_ERR(s->context)) {
|
||||||
err = PTR_ERR(s->context);
|
err = PTR_ERR(s->context);
|
||||||
if (err == -EBUSY)
|
if (err == -EBUSY)
|
||||||
@@ -981,7 +1114,7 @@ static int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed)
|
|||||||
else
|
else
|
||||||
s->tag = TAG_CIP;
|
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);
|
GFP_KERNEL);
|
||||||
if (!s->pkt_descs) {
|
if (!s->pkt_descs) {
|
||||||
err = -ENOMEM;
|
err = -ENOMEM;
|
||||||
@@ -991,12 +1124,21 @@ static int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed)
|
|||||||
s->packet_index = 0;
|
s->packet_index = 0;
|
||||||
do {
|
do {
|
||||||
struct fw_iso_packet params;
|
struct fw_iso_packet params;
|
||||||
|
|
||||||
if (s->direction == AMDTP_IN_STREAM) {
|
if (s->direction == AMDTP_IN_STREAM) {
|
||||||
err = queue_in_packet(s, ¶ms);
|
err = queue_in_packet(s, ¶ms);
|
||||||
} else {
|
} else {
|
||||||
|
bool sched_irq = false;
|
||||||
|
|
||||||
params.header_length = 0;
|
params.header_length = 0;
|
||||||
params.payload_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)
|
if (err < 0)
|
||||||
goto err_pkt_descs;
|
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;
|
tag |= FW_ISO_CONTEXT_MATCH_TAG0;
|
||||||
|
|
||||||
s->callbacked = false;
|
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)
|
if (err < 0)
|
||||||
goto err_pkt_descs;
|
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
|
* @s: the AMDTP stream that transports the PCM data
|
||||||
*
|
*
|
||||||
* Returns the current buffer position, in frames.
|
* 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)
|
||||||
{
|
{
|
||||||
/*
|
struct amdtp_stream *irq_target = d->irq_target;
|
||||||
* This function is called in software IRQ context of period_tasklet or
|
|
||||||
* process context.
|
if (irq_target && amdtp_stream_running(irq_target)) {
|
||||||
*
|
// This function is called in software IRQ context of
|
||||||
* When the software IRQ context was scheduled by software IRQ context
|
// period_tasklet or process context.
|
||||||
* of IR/IT contexts, queued packets were already handled. Therefore,
|
//
|
||||||
* no need to flush the queue in buffer anymore.
|
// When the software IRQ context was scheduled by software IRQ
|
||||||
*
|
// context of IT contexts, queued packets were already handled.
|
||||||
* When the process context reach here, some packets will be already
|
// Therefore, no need to flush the queue in buffer furthermore.
|
||||||
* queued in the buffer. These packets should be handled immediately
|
//
|
||||||
* to keep better granularity of PCM pointer.
|
// When the process context reach here, some packets will be
|
||||||
*
|
// already queued in the buffer. These packets should be handled
|
||||||
* Later, the process context will sometimes schedules software IRQ
|
// immediately to keep better granularity of PCM pointer.
|
||||||
* context of the period_tasklet. Then, no need to flush the queue by
|
//
|
||||||
* the same reason as described for IR/IT contexts.
|
// Later, the process context will sometimes schedules software
|
||||||
*/
|
// IRQ context of the period_tasklet. Then, no need to flush the
|
||||||
if (!in_interrupt() && amdtp_stream_running(s))
|
// queue by the same reason as described in the above
|
||||||
fw_iso_context_flush_completions(s->context);
|
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);
|
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
|
* @s: the AMDTP stream that transfers the PCM frames
|
||||||
*
|
*
|
||||||
* Returns zero always.
|
* 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)
|
||||||
{
|
{
|
||||||
/*
|
struct amdtp_stream *irq_target = d->irq_target;
|
||||||
* Process isochronous packets for recent isochronous cycle to handle
|
|
||||||
* queued PCM frames.
|
// Process isochronous packets for recent isochronous cycle to handle
|
||||||
*/
|
// queued PCM frames.
|
||||||
if (amdtp_stream_running(s))
|
if (irq_target && amdtp_stream_running(irq_target)) {
|
||||||
fw_iso_context_flush_completions(s->context);
|
// 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;
|
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
|
* 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);
|
INIT_LIST_HEAD(&d->streams);
|
||||||
|
|
||||||
|
d->events_per_period = 0;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(amdtp_domain_init);
|
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);
|
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.
|
* amdtp_domain_start - start sending packets for isoc context in the domain.
|
||||||
* @d: the AMDTP 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;
|
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) {
|
list_for_each_entry(s, &d->streams, list) {
|
||||||
err = amdtp_stream_start(s, s->channel, s->speed);
|
if (s->direction == AMDTP_OUT_STREAM)
|
||||||
if (err < 0)
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (!s)
|
||||||
|
return -ENXIO;
|
||||||
|
d->irq_target = s;
|
||||||
|
|
||||||
if (err < 0) {
|
if (ir_delay_cycle > 0) {
|
||||||
list_for_each_entry(s, &d->streams, list)
|
struct fw_card *fw_card = fw_parent_device(s->unit)->card;
|
||||||
amdtp_stream_stop(s);
|
|
||||||
|
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;
|
return err;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(amdtp_domain_start);
|
EXPORT_SYMBOL_GPL(amdtp_domain_start);
|
||||||
@@ -1216,10 +1454,17 @@ void amdtp_domain_stop(struct amdtp_domain *d)
|
|||||||
{
|
{
|
||||||
struct amdtp_stream *s, *next;
|
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_for_each_entry_safe(s, next, &d->streams, list) {
|
||||||
list_del(&s->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);
|
EXPORT_SYMBOL_GPL(amdtp_domain_stop);
|
||||||
|
@@ -117,6 +117,7 @@ struct amdtp_stream {
|
|||||||
/* For packet processing. */
|
/* For packet processing. */
|
||||||
struct fw_iso_context *context;
|
struct fw_iso_context *context;
|
||||||
struct iso_packets_buffer buffer;
|
struct iso_packets_buffer buffer;
|
||||||
|
unsigned int queue_size;
|
||||||
int packet_index;
|
int packet_index;
|
||||||
struct pkt_desc *pkt_descs;
|
struct pkt_desc *pkt_descs;
|
||||||
int tag;
|
int tag;
|
||||||
@@ -142,6 +143,10 @@ struct amdtp_stream {
|
|||||||
// To generate CIP header.
|
// To generate CIP header.
|
||||||
unsigned int fdf;
|
unsigned int fdf;
|
||||||
int syt_override;
|
int syt_override;
|
||||||
|
|
||||||
|
// To generate constant hardware IRQ.
|
||||||
|
unsigned int event_count;
|
||||||
|
unsigned int events_per_period;
|
||||||
} rx;
|
} rx;
|
||||||
} ctx_data;
|
} ctx_data;
|
||||||
|
|
||||||
@@ -194,8 +199,6 @@ int amdtp_stream_add_pcm_hw_constraints(struct amdtp_stream *s,
|
|||||||
struct snd_pcm_runtime *runtime);
|
struct snd_pcm_runtime *runtime);
|
||||||
|
|
||||||
void amdtp_stream_pcm_prepare(struct amdtp_stream *s);
|
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);
|
void amdtp_stream_pcm_abort(struct amdtp_stream *s);
|
||||||
|
|
||||||
extern const unsigned int amdtp_syt_intervals[CIP_SFC_COUNT];
|
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 amdtp_domain {
|
||||||
struct list_head streams;
|
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);
|
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 amdtp_domain_add_stream(struct amdtp_domain *d, struct amdtp_stream *s,
|
||||||
int channel, int speed);
|
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);
|
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
|
#endif
|
||||||
|
@@ -217,7 +217,9 @@ int snd_bebob_stream_get_clock_src(struct snd_bebob *bebob,
|
|||||||
enum snd_bebob_clock_type *src);
|
enum snd_bebob_clock_type *src);
|
||||||
int snd_bebob_stream_discover(struct snd_bebob *bebob);
|
int snd_bebob_stream_discover(struct snd_bebob *bebob);
|
||||||
int snd_bebob_stream_init_duplex(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);
|
int snd_bebob_stream_start_duplex(struct snd_bebob *bebob);
|
||||||
void snd_bebob_stream_stop_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);
|
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;
|
return err;
|
||||||
|
|
||||||
mutex_lock(&bebob->mutex);
|
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) {
|
if (err >= 0) {
|
||||||
++bebob->substreams_counter;
|
++bebob->substreams_counter;
|
||||||
err = snd_bebob_stream_start_duplex(bebob);
|
err = snd_bebob_stream_start_duplex(bebob);
|
||||||
|
@@ -129,18 +129,17 @@ end:
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int pcm_open(struct snd_pcm_substream *substream)
|
||||||
pcm_open(struct snd_pcm_substream *substream)
|
|
||||||
{
|
{
|
||||||
struct snd_bebob *bebob = substream->private_data;
|
struct snd_bebob *bebob = substream->private_data;
|
||||||
const struct snd_bebob_rate_spec *spec = bebob->spec->rate;
|
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;
|
enum snd_bebob_clock_type src;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
err = snd_bebob_stream_lock_try(bebob);
|
err = snd_bebob_stream_lock_try(bebob);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto end;
|
return err;
|
||||||
|
|
||||||
err = pcm_init_hw_params(bebob, substream);
|
err = pcm_init_hw_params(bebob, substream);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
@@ -150,15 +149,20 @@ pcm_open(struct snd_pcm_substream *substream)
|
|||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto err_locked;
|
goto err_locked;
|
||||||
|
|
||||||
/*
|
mutex_lock(&bebob->mutex);
|
||||||
* When source of clock is internal or any PCM stream are running,
|
|
||||||
* the available sampling rate is limited at current sampling rate.
|
// 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 ||
|
if (src == SND_BEBOB_CLOCK_TYPE_EXTERNAL ||
|
||||||
amdtp_stream_pcm_running(&bebob->tx_stream) ||
|
(bebob->substreams_counter > 0 && d->events_per_period > 0)) {
|
||||||
amdtp_stream_pcm_running(&bebob->rx_stream)) {
|
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);
|
err = spec->get(bebob, &sampling_rate);
|
||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
|
mutex_unlock(&bebob->mutex);
|
||||||
dev_err(&bebob->unit->device,
|
dev_err(&bebob->unit->device,
|
||||||
"fail to get sampling rate: %d\n", err);
|
"fail to get sampling rate: %d\n", err);
|
||||||
goto err_locked;
|
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_min = sampling_rate;
|
||||||
substream->runtime->hw.rate_max = 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);
|
snd_pcm_set_sync(substream);
|
||||||
end:
|
|
||||||
return err;
|
return 0;
|
||||||
err_locked:
|
err_locked:
|
||||||
snd_bebob_stream_lock_release(bebob);
|
snd_bebob_stream_lock_release(bebob);
|
||||||
return err;
|
return err;
|
||||||
@@ -190,16 +214,18 @@ static int pcm_hw_params(struct snd_pcm_substream *substream,
|
|||||||
struct snd_bebob *bebob = substream->private_data;
|
struct snd_bebob *bebob = substream->private_data;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
|
err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
|
||||||
params_buffer_bytes(hw_params));
|
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
|
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
|
||||||
unsigned int rate = params_rate(hw_params);
|
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);
|
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)
|
if (err >= 0)
|
||||||
++bebob->substreams_counter;
|
++bebob->substreams_counter;
|
||||||
mutex_unlock(&bebob->mutex);
|
mutex_unlock(&bebob->mutex);
|
||||||
@@ -221,7 +247,7 @@ static int pcm_hw_free(struct snd_pcm_substream *substream)
|
|||||||
|
|
||||||
mutex_unlock(&bebob->mutex);
|
mutex_unlock(&bebob->mutex);
|
||||||
|
|
||||||
return snd_pcm_lib_free_vmalloc_buffer(substream);
|
return snd_pcm_lib_free_pages(substream);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@@ -286,31 +312,33 @@ pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static snd_pcm_uframes_t
|
static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstrm)
|
||||||
pcm_capture_pointer(struct snd_pcm_substream *sbstrm)
|
|
||||||
{
|
{
|
||||||
struct snd_bebob *bebob = sbstrm->private_data;
|
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
|
static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstrm)
|
||||||
pcm_playback_pointer(struct snd_pcm_substream *sbstrm)
|
|
||||||
{
|
{
|
||||||
struct snd_bebob *bebob = sbstrm->private_data;
|
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)
|
static int pcm_capture_ack(struct snd_pcm_substream *substream)
|
||||||
{
|
{
|
||||||
struct snd_bebob *bebob = substream->private_data;
|
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)
|
static int pcm_playback_ack(struct snd_pcm_substream *substream)
|
||||||
{
|
{
|
||||||
struct snd_bebob *bebob = substream->private_data;
|
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)
|
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,
|
.trigger = pcm_capture_trigger,
|
||||||
.pointer = pcm_capture_pointer,
|
.pointer = pcm_capture_pointer,
|
||||||
.ack = pcm_capture_ack,
|
.ack = pcm_capture_ack,
|
||||||
.page = snd_pcm_lib_get_vmalloc_page,
|
|
||||||
};
|
};
|
||||||
static const struct snd_pcm_ops playback_ops = {
|
static const struct snd_pcm_ops playback_ops = {
|
||||||
.open = pcm_open,
|
.open = pcm_open,
|
||||||
@@ -337,7 +364,6 @@ int snd_bebob_create_pcm_devices(struct snd_bebob *bebob)
|
|||||||
.trigger = pcm_playback_trigger,
|
.trigger = pcm_playback_trigger,
|
||||||
.pointer = pcm_playback_pointer,
|
.pointer = pcm_playback_pointer,
|
||||||
.ack = pcm_playback_ack,
|
.ack = pcm_playback_ack,
|
||||||
.page = snd_pcm_lib_get_vmalloc_page,
|
|
||||||
};
|
};
|
||||||
struct snd_pcm *pcm;
|
struct snd_pcm *pcm;
|
||||||
int err;
|
int err;
|
||||||
@@ -351,6 +377,8 @@ int snd_bebob_create_pcm_devices(struct snd_bebob *bebob)
|
|||||||
"%s PCM", bebob->card->shortname);
|
"%s PCM", bebob->card->shortname);
|
||||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops);
|
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops);
|
||||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_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:
|
end:
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
#include "./bebob.h"
|
#include "./bebob.h"
|
||||||
|
|
||||||
#define CALLBACK_TIMEOUT 2000
|
#define CALLBACK_TIMEOUT 2500
|
||||||
#define FW_ISO_RESOURCE_DELAY 1000
|
#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;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int make_both_connections(struct snd_bebob *bebob)
|
static void break_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)
|
|
||||||
{
|
{
|
||||||
cmp_connection_break(&bebob->in_conn);
|
cmp_connection_break(&bebob->in_conn);
|
||||||
cmp_connection_break(&bebob->out_conn);
|
cmp_connection_break(&bebob->out_conn);
|
||||||
|
|
||||||
/* These models seems to be in transition state for a longer time. */
|
// These models seem to be in transition state for a longer time. When
|
||||||
if (bebob->maudio_special_quirk != NULL)
|
// accessing in the state, any transactions is corrupted. In the worst
|
||||||
msleep(200);
|
// case, the device is going to reboot.
|
||||||
|
if (bebob->version < 2)
|
||||||
|
msleep(600);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int start_stream(struct snd_bebob *bebob, struct amdtp_stream *stream)
|
||||||
start_stream(struct snd_bebob *bebob, struct amdtp_stream *stream)
|
|
||||||
{
|
{
|
||||||
struct cmp_connection *conn;
|
struct cmp_connection *conn;
|
||||||
int err = 0;
|
int err = 0;
|
||||||
@@ -437,18 +420,19 @@ start_stream(struct snd_bebob *bebob, struct amdtp_stream *stream)
|
|||||||
else
|
else
|
||||||
conn = &bebob->out_conn;
|
conn = &bebob->out_conn;
|
||||||
|
|
||||||
/* channel mapping */
|
// channel mapping.
|
||||||
if (bebob->maudio_special_quirk == NULL) {
|
if (bebob->maudio_special_quirk == NULL) {
|
||||||
err = map_data_channels(bebob, stream);
|
err = map_data_channels(bebob, stream);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto end;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
// start amdtp stream.
|
err = cmp_connection_establish(conn);
|
||||||
err = amdtp_domain_add_stream(&bebob->domain, stream,
|
if (err < 0)
|
||||||
conn->resources.channel, conn->speed);
|
return err;
|
||||||
end:
|
|
||||||
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)
|
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));
|
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;
|
unsigned int curr_rate;
|
||||||
int err;
|
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);
|
cmp_connection_release(&bebob->out_conn);
|
||||||
return err;
|
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;
|
return 0;
|
||||||
@@ -627,7 +621,10 @@ int snd_bebob_stream_start_duplex(struct snd_bebob *bebob)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!amdtp_stream_running(&bebob->rx_stream)) {
|
if (!amdtp_stream_running(&bebob->rx_stream)) {
|
||||||
|
enum snd_bebob_clock_type src;
|
||||||
|
struct amdtp_stream *master, *slave;
|
||||||
unsigned int curr_rate;
|
unsigned int curr_rate;
|
||||||
|
unsigned int ir_delay_cycle;
|
||||||
|
|
||||||
if (bebob->maudio_special_quirk) {
|
if (bebob->maudio_special_quirk) {
|
||||||
err = bebob->spec->rate->get(bebob, &curr_rate);
|
err = bebob->spec->rate->get(bebob, &curr_rate);
|
||||||
@@ -635,19 +632,40 @@ int snd_bebob_stream_start_duplex(struct snd_bebob *bebob)
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = make_both_connections(bebob);
|
err = snd_bebob_stream_get_clock_src(bebob, &src);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
return err;
|
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)
|
if (err < 0)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
err = start_stream(bebob, &bebob->tx_stream);
|
err = start_stream(bebob, slave);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto error;
|
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)
|
if (err < 0)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
|
@@ -17,7 +17,7 @@ static int midi_open(struct snd_rawmidi_substream *substream)
|
|||||||
|
|
||||||
mutex_lock(&dice->mutex);
|
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) {
|
if (err >= 0) {
|
||||||
++dice->substreams_counter;
|
++dice->substreams_counter;
|
||||||
err = snd_dice_stream_start_duplex(dice);
|
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)
|
static int pcm_open(struct snd_pcm_substream *substream)
|
||||||
{
|
{
|
||||||
struct snd_dice *dice = substream->private_data;
|
struct snd_dice *dice = substream->private_data;
|
||||||
|
struct amdtp_domain *d = &dice->domain;
|
||||||
unsigned int source;
|
unsigned int source;
|
||||||
bool internal;
|
bool internal;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
err = snd_dice_stream_lock_try(dice);
|
err = snd_dice_stream_lock_try(dice);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto end;
|
return err;
|
||||||
|
|
||||||
err = init_hw_info(dice, substream);
|
err = init_hw_info(dice, substream);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
@@ -195,27 +196,56 @@ static int pcm_open(struct snd_pcm_substream *substream)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
mutex_lock(&dice->mutex);
|
||||||
* When source of clock is not internal or any PCM streams are running,
|
|
||||||
* available sampling rate is limited at current sampling rate.
|
// 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 ||
|
if (!internal ||
|
||||||
amdtp_stream_pcm_running(&dice->tx_stream[0]) ||
|
(dice->substreams_counter > 0 && d->events_per_period > 0)) {
|
||||||
amdtp_stream_pcm_running(&dice->tx_stream[1]) ||
|
unsigned int frames_per_period = d->events_per_period;
|
||||||
amdtp_stream_pcm_running(&dice->rx_stream[0]) ||
|
unsigned int frames_per_buffer = d->events_per_buffer;
|
||||||
amdtp_stream_pcm_running(&dice->rx_stream[1])) {
|
|
||||||
unsigned int rate;
|
unsigned int rate;
|
||||||
|
|
||||||
err = snd_dice_transaction_get_rate(dice, &rate);
|
err = snd_dice_transaction_get_rate(dice, &rate);
|
||||||
if (err < 0)
|
if (err < 0) {
|
||||||
|
mutex_unlock(&dice->mutex);
|
||||||
goto err_locked;
|
goto err_locked;
|
||||||
|
}
|
||||||
|
|
||||||
substream->runtime->hw.rate_min = rate;
|
substream->runtime->hw.rate_min = rate;
|
||||||
substream->runtime->hw.rate_max = 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);
|
snd_pcm_set_sync(substream);
|
||||||
end:
|
|
||||||
return err;
|
return 0;
|
||||||
err_locked:
|
err_locked:
|
||||||
snd_dice_stream_lock_release(dice);
|
snd_dice_stream_lock_release(dice);
|
||||||
return err;
|
return err;
|
||||||
@@ -236,16 +266,23 @@ static int pcm_hw_params(struct snd_pcm_substream *substream,
|
|||||||
struct snd_dice *dice = substream->private_data;
|
struct snd_dice *dice = substream->private_data;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
|
err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
|
||||||
params_buffer_bytes(hw_params));
|
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
|
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
|
||||||
unsigned int rate = params_rate(hw_params);
|
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);
|
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)
|
if (err >= 0)
|
||||||
++dice->substreams_counter;
|
++dice->substreams_counter;
|
||||||
mutex_unlock(&dice->mutex);
|
mutex_unlock(&dice->mutex);
|
||||||
@@ -267,7 +304,7 @@ static int pcm_hw_free(struct snd_pcm_substream *substream)
|
|||||||
|
|
||||||
mutex_unlock(&dice->mutex);
|
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)
|
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 snd_dice *dice = substream->private_data;
|
||||||
struct amdtp_stream *stream = &dice->tx_stream[substream->pcm->device];
|
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)
|
static snd_pcm_uframes_t playback_pointer(struct snd_pcm_substream *substream)
|
||||||
{
|
{
|
||||||
struct snd_dice *dice = substream->private_data;
|
struct snd_dice *dice = substream->private_data;
|
||||||
struct amdtp_stream *stream = &dice->rx_stream[substream->pcm->device];
|
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)
|
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 snd_dice *dice = substream->private_data;
|
||||||
struct amdtp_stream *stream = &dice->tx_stream[substream->pcm->device];
|
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)
|
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 snd_dice *dice = substream->private_data;
|
||||||
struct amdtp_stream *stream = &dice->rx_stream[substream->pcm->device];
|
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)
|
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,
|
.trigger = capture_trigger,
|
||||||
.pointer = capture_pointer,
|
.pointer = capture_pointer,
|
||||||
.ack = capture_ack,
|
.ack = capture_ack,
|
||||||
.page = snd_pcm_lib_get_vmalloc_page,
|
|
||||||
};
|
};
|
||||||
static const struct snd_pcm_ops playback_ops = {
|
static const struct snd_pcm_ops playback_ops = {
|
||||||
.open = pcm_open,
|
.open = pcm_open,
|
||||||
@@ -391,7 +427,6 @@ int snd_dice_create_pcm(struct snd_dice *dice)
|
|||||||
.trigger = playback_trigger,
|
.trigger = playback_trigger,
|
||||||
.pointer = playback_pointer,
|
.pointer = playback_pointer,
|
||||||
.ack = playback_ack,
|
.ack = playback_ack,
|
||||||
.page = snd_pcm_lib_get_vmalloc_page,
|
|
||||||
};
|
};
|
||||||
struct snd_pcm *pcm;
|
struct snd_pcm *pcm;
|
||||||
unsigned int capture, playback;
|
unsigned int capture, playback;
|
||||||
@@ -421,6 +456,10 @@ int snd_dice_create_pcm(struct snd_dice *dice)
|
|||||||
if (playback > 0)
|
if (playback > 0)
|
||||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
|
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
|
||||||
&playback_ops);
|
&playback_ops);
|
||||||
|
|
||||||
|
snd_pcm_lib_preallocate_pages_for_all(pcm,
|
||||||
|
SNDRV_DMA_TYPE_VMALLOC,
|
||||||
|
NULL, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 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);
|
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;
|
unsigned int curr_rate;
|
||||||
int err;
|
int err;
|
||||||
@@ -324,6 +326,11 @@ int snd_dice_stream_reserve_duplex(struct snd_dice *dice, unsigned int rate)
|
|||||||
&rx_params);
|
&rx_params);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
|
err = amdtp_domain_set_events_per_period(&dice->domain,
|
||||||
|
events_per_period, events_per_buffer);
|
||||||
|
if (err < 0)
|
||||||
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@@ -455,7 +462,7 @@ int snd_dice_stream_start_duplex(struct snd_dice *dice)
|
|||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = amdtp_domain_start(&dice->domain);
|
err = amdtp_domain_start(&dice->domain, 0);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto error;
|
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);
|
void snd_dice_stream_stop_duplex(struct snd_dice *dice);
|
||||||
int snd_dice_stream_init_duplex(struct snd_dice *dice);
|
int snd_dice_stream_init_duplex(struct snd_dice *dice);
|
||||||
void snd_dice_stream_destroy_duplex(struct snd_dice *dice);
|
void snd_dice_stream_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);
|
void snd_dice_stream_update_duplex(struct snd_dice *dice);
|
||||||
int snd_dice_stream_detect_current_formats(struct snd_dice *dice);
|
int snd_dice_stream_detect_current_formats(struct snd_dice *dice);
|
||||||
|
|
||||||
|
@@ -17,7 +17,7 @@ static int midi_open(struct snd_rawmidi_substream *substream)
|
|||||||
return err;
|
return err;
|
||||||
|
|
||||||
mutex_lock(&dg00x->mutex);
|
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) {
|
if (err >= 0) {
|
||||||
++dg00x->substreams_counter;
|
++dg00x->substreams_counter;
|
||||||
err = snd_dg00x_stream_start_duplex(dg00x);
|
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)
|
static int pcm_open(struct snd_pcm_substream *substream)
|
||||||
{
|
{
|
||||||
struct snd_dg00x *dg00x = substream->private_data;
|
struct snd_dg00x *dg00x = substream->private_data;
|
||||||
|
struct amdtp_domain *d = &dg00x->domain;
|
||||||
enum snd_dg00x_clock clock;
|
enum snd_dg00x_clock clock;
|
||||||
bool detect;
|
bool detect;
|
||||||
unsigned int rate;
|
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
err = snd_dg00x_stream_lock_try(dg00x);
|
err = snd_dg00x_stream_lock_try(dg00x);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto end;
|
return err;
|
||||||
|
|
||||||
err = pcm_init_hw_params(dg00x, substream);
|
err = pcm_init_hw_params(dg00x, substream);
|
||||||
if (err < 0)
|
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) ||
|
if ((clock != SND_DG00X_CLOCK_INTERNAL) ||
|
||||||
amdtp_stream_pcm_running(&dg00x->rx_stream) ||
|
(dg00x->substreams_counter > 0 && d->events_per_period > 0)) {
|
||||||
amdtp_stream_pcm_running(&dg00x->tx_stream)) {
|
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);
|
err = snd_dg00x_stream_get_external_rate(dg00x, &rate);
|
||||||
if (err < 0)
|
if (err < 0) {
|
||||||
|
mutex_unlock(&dg00x->mutex);
|
||||||
goto err_locked;
|
goto err_locked;
|
||||||
|
}
|
||||||
substream->runtime->hw.rate_min = rate;
|
substream->runtime->hw.rate_min = rate;
|
||||||
substream->runtime->hw.rate_max = 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);
|
snd_pcm_set_sync(substream);
|
||||||
end:
|
|
||||||
return err;
|
return 0;
|
||||||
err_locked:
|
err_locked:
|
||||||
snd_dg00x_stream_lock_release(dg00x);
|
snd_dg00x_stream_lock_release(dg00x);
|
||||||
return err;
|
return err;
|
||||||
@@ -160,16 +190,18 @@ static int pcm_hw_params(struct snd_pcm_substream *substream,
|
|||||||
struct snd_dg00x *dg00x = substream->private_data;
|
struct snd_dg00x *dg00x = substream->private_data;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
|
err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
|
||||||
params_buffer_bytes(hw_params));
|
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
|
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
|
||||||
unsigned int rate = params_rate(hw_params);
|
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);
|
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)
|
if (err >= 0)
|
||||||
++dg00x->substreams_counter;
|
++dg00x->substreams_counter;
|
||||||
mutex_unlock(&dg00x->mutex);
|
mutex_unlock(&dg00x->mutex);
|
||||||
@@ -191,7 +223,7 @@ static int pcm_hw_free(struct snd_pcm_substream *substream)
|
|||||||
|
|
||||||
mutex_unlock(&dg00x->mutex);
|
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)
|
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;
|
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)
|
static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstrm)
|
||||||
{
|
{
|
||||||
struct snd_dg00x *dg00x = sbstrm->private_data;
|
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)
|
static int pcm_capture_ack(struct snd_pcm_substream *substream)
|
||||||
{
|
{
|
||||||
struct snd_dg00x *dg00x = substream->private_data;
|
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)
|
static int pcm_playback_ack(struct snd_pcm_substream *substream)
|
||||||
{
|
{
|
||||||
struct snd_dg00x *dg00x = substream->private_data;
|
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)
|
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,
|
.trigger = pcm_capture_trigger,
|
||||||
.pointer = pcm_capture_pointer,
|
.pointer = pcm_capture_pointer,
|
||||||
.ack = pcm_capture_ack,
|
.ack = pcm_capture_ack,
|
||||||
.page = snd_pcm_lib_get_vmalloc_page,
|
|
||||||
};
|
};
|
||||||
static const struct snd_pcm_ops playback_ops = {
|
static const struct snd_pcm_ops playback_ops = {
|
||||||
.open = pcm_open,
|
.open = pcm_open,
|
||||||
@@ -316,7 +347,6 @@ int snd_dg00x_create_pcm_devices(struct snd_dg00x *dg00x)
|
|||||||
.trigger = pcm_playback_trigger,
|
.trigger = pcm_playback_trigger,
|
||||||
.pointer = pcm_playback_pointer,
|
.pointer = pcm_playback_pointer,
|
||||||
.ack = pcm_playback_ack,
|
.ack = pcm_playback_ack,
|
||||||
.page = snd_pcm_lib_get_vmalloc_page,
|
|
||||||
};
|
};
|
||||||
struct snd_pcm *pcm;
|
struct snd_pcm *pcm;
|
||||||
int err;
|
int err;
|
||||||
@@ -330,6 +360,8 @@ int snd_dg00x_create_pcm_devices(struct snd_dg00x *dg00x)
|
|||||||
"%s PCM", dg00x->card->shortname);
|
"%s PCM", dg00x->card->shortname);
|
||||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops);
|
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops);
|
||||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@@ -283,7 +283,9 @@ void snd_dg00x_stream_destroy_duplex(struct snd_dg00x *dg00x)
|
|||||||
destroy_stream(dg00x, &dg00x->tx_stream);
|
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;
|
unsigned int curr_rate;
|
||||||
int err;
|
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);
|
fw_iso_resources_free(&dg00x->rx_resources);
|
||||||
return err;
|
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;
|
return 0;
|
||||||
@@ -365,7 +375,7 @@ int snd_dg00x_stream_start_duplex(struct snd_dg00x *dg00x)
|
|||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
err = amdtp_domain_start(&dg00x->domain);
|
err = amdtp_domain_start(&dg00x->domain, 0);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto error;
|
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,
|
int snd_dg00x_stream_check_external_clock(struct snd_dg00x *dg00x,
|
||||||
bool *detect);
|
bool *detect);
|
||||||
int snd_dg00x_stream_init_duplex(struct snd_dg00x *dg00x);
|
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);
|
int snd_dg00x_stream_start_duplex(struct snd_dg00x *dg00x);
|
||||||
void snd_dg00x_stream_stop_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);
|
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)
|
static int pcm_open(struct snd_pcm_substream *substream)
|
||||||
{
|
{
|
||||||
struct snd_ff *ff = substream->private_data;
|
struct snd_ff *ff = substream->private_data;
|
||||||
|
struct amdtp_domain *d = &ff->domain;
|
||||||
unsigned int rate;
|
unsigned int rate;
|
||||||
enum snd_ff_clock_src src;
|
enum snd_ff_clock_src src;
|
||||||
int i, err;
|
int i, err;
|
||||||
@@ -155,16 +156,21 @@ static int pcm_open(struct snd_pcm_substream *substream)
|
|||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto release_lock;
|
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) {
|
if (src != SND_FF_CLOCK_SRC_INTERNAL) {
|
||||||
for (i = 0; i < CIP_SFC_COUNT; ++i) {
|
for (i = 0; i < CIP_SFC_COUNT; ++i) {
|
||||||
if (amdtp_rate_table[i] == rate)
|
if (amdtp_rate_table[i] == rate)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
* The unit is configured at sampling frequency which packet
|
// The unit is configured at sampling frequency which packet
|
||||||
* streaming engine can't support.
|
// streaming engine can't support.
|
||||||
*/
|
|
||||||
if (i >= CIP_SFC_COUNT) {
|
if (i >= CIP_SFC_COUNT) {
|
||||||
|
mutex_unlock(&ff->mutex);
|
||||||
err = -EIO;
|
err = -EIO;
|
||||||
goto release_lock;
|
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_min = rate;
|
||||||
substream->runtime->hw.rate_max = rate;
|
substream->runtime->hw.rate_max = rate;
|
||||||
} else {
|
} else {
|
||||||
if (amdtp_stream_pcm_running(&ff->rx_stream) ||
|
if (ff->substreams_counter > 0) {
|
||||||
amdtp_stream_pcm_running(&ff->tx_stream)) {
|
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];
|
rate = amdtp_rate_table[ff->rx_stream.sfc];
|
||||||
substream->runtime->hw.rate_min = rate;
|
substream->runtime->hw.rate_min = rate;
|
||||||
substream->runtime->hw.rate_max = 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);
|
snd_pcm_set_sync(substream);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@@ -204,16 +230,18 @@ static int pcm_hw_params(struct snd_pcm_substream *substream,
|
|||||||
struct snd_ff *ff = substream->private_data;
|
struct snd_ff *ff = substream->private_data;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
|
err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
|
||||||
params_buffer_bytes(hw_params));
|
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
|
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
|
||||||
unsigned int rate = params_rate(hw_params);
|
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);
|
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)
|
if (err >= 0)
|
||||||
++ff->substreams_counter;
|
++ff->substreams_counter;
|
||||||
mutex_unlock(&ff->mutex);
|
mutex_unlock(&ff->mutex);
|
||||||
@@ -235,7 +263,7 @@ static int pcm_hw_free(struct snd_pcm_substream *substream)
|
|||||||
|
|
||||||
mutex_unlock(&ff->mutex);
|
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)
|
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;
|
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)
|
static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstrm)
|
||||||
{
|
{
|
||||||
struct snd_ff *ff = sbstrm->private_data;
|
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)
|
static int pcm_capture_ack(struct snd_pcm_substream *substream)
|
||||||
{
|
{
|
||||||
struct snd_ff *ff = substream->private_data;
|
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)
|
static int pcm_playback_ack(struct snd_pcm_substream *substream)
|
||||||
{
|
{
|
||||||
struct snd_ff *ff = substream->private_data;
|
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)
|
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,
|
.trigger = pcm_capture_trigger,
|
||||||
.pointer = pcm_capture_pointer,
|
.pointer = pcm_capture_pointer,
|
||||||
.ack = pcm_capture_ack,
|
.ack = pcm_capture_ack,
|
||||||
.page = snd_pcm_lib_get_vmalloc_page,
|
|
||||||
};
|
};
|
||||||
static const struct snd_pcm_ops pcm_playback_ops = {
|
static const struct snd_pcm_ops pcm_playback_ops = {
|
||||||
.open = pcm_open,
|
.open = pcm_open,
|
||||||
@@ -360,7 +387,6 @@ int snd_ff_create_pcm_devices(struct snd_ff *ff)
|
|||||||
.trigger = pcm_playback_trigger,
|
.trigger = pcm_playback_trigger,
|
||||||
.pointer = pcm_playback_pointer,
|
.pointer = pcm_playback_pointer,
|
||||||
.ack = pcm_playback_ack,
|
.ack = pcm_playback_ack,
|
||||||
.page = snd_pcm_lib_get_vmalloc_page,
|
|
||||||
};
|
};
|
||||||
struct snd_pcm *pcm;
|
struct snd_pcm *pcm;
|
||||||
int err;
|
int err;
|
||||||
@@ -374,6 +400,8 @@ int snd_ff_create_pcm_devices(struct snd_ff *ff)
|
|||||||
"%s PCM", ff->card->shortname);
|
"%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_PLAYBACK, &pcm_playback_ops);
|
||||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pcm_capture_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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@@ -106,7 +106,9 @@ void snd_ff_stream_destroy_duplex(struct snd_ff *ff)
|
|||||||
destroy_stream(ff, &ff->tx_stream);
|
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;
|
unsigned int curr_rate;
|
||||||
enum snd_ff_clock_src src;
|
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);
|
err = ff->spec->protocol->allocate_resources(ff, rate);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
return err;
|
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;
|
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)) {
|
if (!amdtp_stream_running(&ff->rx_stream)) {
|
||||||
int spd = fw_parent_device(ff->unit)->max_speed;
|
int spd = fw_parent_device(ff->unit)->max_speed;
|
||||||
|
unsigned int ir_delay_cycle;
|
||||||
|
|
||||||
err = ff->spec->protocol->begin_session(ff, rate);
|
err = ff->spec->protocol->begin_session(ff, rate);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
@@ -189,7 +200,14 @@ int snd_ff_stream_start_duplex(struct snd_ff *ff, unsigned int rate)
|
|||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto error;
|
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)
|
if (err < 0)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
|
@@ -139,7 +139,9 @@ int snd_ff_stream_get_multiplier_mode(enum cip_sfc sfc,
|
|||||||
enum snd_ff_stream_mode *mode);
|
enum snd_ff_stream_mode *mode);
|
||||||
int snd_ff_stream_init_duplex(struct snd_ff *ff);
|
int snd_ff_stream_init_duplex(struct snd_ff *ff);
|
||||||
void snd_ff_stream_destroy_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);
|
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_stop_duplex(struct snd_ff *ff);
|
||||||
void snd_ff_stream_update_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
Reference in New Issue
Block a user