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:
Linus Torvalds
2019-11-26 20:04:35 -08:00
395 changed files with 16000 additions and 5276 deletions

View 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>;
};

View File

@@ -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";
};
...

View File

@@ -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>;
};
...

View File

@@ -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

View 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";
};

View File

@@ -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>;
}; };
}; };

View File

@@ -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>,

View File

@@ -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>;
}; };

View File

@@ -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;
};

View 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;
};

View File

@@ -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)

View File

@@ -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>;
};

View File

@@ -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>;
}; };

View File

@@ -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>;
}; };

View File

@@ -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>;
};
};

View 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>;
};
};

View File

@@ -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>;
};

View 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>;
};

View File

@@ -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";
};

View File

@@ -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";
};
};

View 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>;
};

View 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>;
};

View File

@@ -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>;

View File

@@ -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:

View File

@@ -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

View File

@@ -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
============== ==============

View File

@@ -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

View File

@@ -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)

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;

View File

@@ -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)

View File

@@ -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

View File

@@ -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), \

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);

View File

@@ -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 */

View File

@@ -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

View File

@@ -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 */

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View 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

View File

@@ -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

View File

@@ -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);
/** /**

View File

@@ -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 */

View File

@@ -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

View File

@@ -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) \

View File

@@ -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

View File

@@ -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;

View File

@@ -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 */

View File

@@ -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,

View File

@@ -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

View File

@@ -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;

View 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

View File

@@ -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;

View File

@@ -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)

View File

@@ -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

View File

@@ -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;

View File

@@ -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);

View File

@@ -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/

View File

@@ -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

View File

@@ -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 */

View File

@@ -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

View File

@@ -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;

View File

@@ -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");

View File

@@ -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.

View File

@@ -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;

View File

@@ -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:

View File

@@ -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");

View File

@@ -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 */

View File

@@ -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 */
/** /**

View File

@@ -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,

View File

@@ -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;
} }

View File

@@ -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(&register_mutex); mutex_unlock(&register_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(&register_mutex); mutex_lock(&register_mutex);
err = snd_timer_close_locked(timeri, &card_dev_to_put); snd_timer_close_locked(timeri, &card_dev_to_put);
mutex_unlock(&register_mutex); mutex_unlock(&register_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;
} }

View File

@@ -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"

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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"

View File

@@ -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, &params); err = queue_in_packet(s, &params);
} 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, &params);
if (is_irq_target) {
sched_irq = !((s->packet_index + 1) %
idle_irq_interval);
}
err = queue_out_packet(s, &params, 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,
&reg, 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);

View File

@@ -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

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;
} }

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;
} }

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;
} }

View File

@@ -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;

View File

@@ -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