sound updates for 5.1
We had again a busy development cycle with many new drivers as well as lots of core improvements / cleanups. Let's go for highlights: ALSA core: - PCM locking scheme was refactored for reducing a global rwlock - PCM suspend is handled in the device type PM ops now; lots of explicit calls were reduced by this action - Cleanups about PCM buffer preallocation calls - Kill NULL device object in memory allocations - Lots of procfs API cleanups ASoC core: - Support for only powering up channels that are actively being used - Cleanups / fixes of topology API ASoC drivers: - MediaTek BTCVSD for a Bluetooth radio chip, which is the first such driver we've had upstream! - Quite a few improvements to simplify the generic card drivers, especially the merge of the SCU cards into the main generic drivers - Lots of fixes for probing on Intel systems to follow more standard styles - A big refresh and cleanup of the Samsung drivers - New drivers: Asahi Kasei Microdevices AK4497, Cirrus Logic CS4341 and CS35L26, Google ChromeOS embedded controllers, Ingenic JZ4725B, MediaTek BTCVSD, MT8183 and MT6358, NXP MICFIL, Rockchip RK3328, Spreadtrum DMA controllers, Qualcomm WCD9335, Xilinx S/PDIF and PCM formatters ALSA drivers: - Improvements of Tegra HD-audio controller driver for supporting new chips - HD-audio codec quirks for ALC294 S4 resume, ASUS laptop, Chrome headset button support and Dell workstations - Improved DSD support on USB-audio - Quirk for MOTU MicroBook II USB-audio - Support for Fireface UCX support and Solid State Logic Duende Classic/Mini -----BEGIN PGP SIGNATURE----- iQJCBAABCAAsFiEEIXTw5fNLNI7mMiVaLtJE4w1nLE8FAlx5GAUOHHRpd2FpQHN1 c2UuZGUACgkQLtJE4w1nLE9yVA/+LYkM9fkd8BkRvnUxDPQTupBtr5tXjVPC7NHX WEZU+My5bxFvmF0tkV0IeBjzTNhR+ND1HHa4EcJPaV26UX+BBSyM2q8EhPxXJ3Ly o2d6bRYIvyDMEShyzZAlDegZ45LiP9Re7xwfVtA1gFVwCxXeWRXyz8Jj9FjUACVx 1dd74wsVvPwt3uvGnJTEDbtpxA7lLCnVmco0PcXmkBIE0f99EzF/9xGs56UUKrlJ UM+KRH6H5fskESSUdR9MSoOi6Vw794rMNrFQIx9yb6/JHfq1Q5cyxQSx/E+7AMaU 0QrQbKYDRVhrw8P2mVYXvbaZe2SdLMJdOKGXQMdYXpcUgSD0MBnc/O4HCn9HnT89 M4QjoqSWYgipnLHf+O6JfrojF39+TrQ8LQWQZSNDYijOcQvaXAgK+vXmYOSQSQs2 H8fljEHXAIjDOhlBd0J4b9gAn21ijjZUSRBAWkNS0Sp0qBu0WcbBWLph/zHqjd7J UvTILjpKegd28yrsudLXRL73v3fJoWXM7wwrjnGdmi93FhqCNdJ9co/03JNRSlQ9 sFM6YBj6pyg4QkNTpPwZw5nn48uBsbHZRU8la7Vfp0c+XYPkHKR5EwQQ0YoajZhW e2SSeMUVCHGmIAoqweAxakU4zCDvv4wLbnjJRi/BXIh17XrhG4AAiRrYZOYaOCwO Ku/x7dI= =Vh8O -----END PGP SIGNATURE----- Merge tag 'sound-5.1-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound Pull sound updates from Takashi Iwai: "We had again a busy development cycle with many new drivers as well as lots of core improvements / cleanups. Let's go for highlights: ALSA core: - PCM locking scheme was refactored for reducing a global rwlock - PCM suspend is handled in the device type PM ops now; lots of explicit calls were reduced by this action - Cleanups about PCM buffer preallocation calls - Kill NULL device object in memory allocations - Lots of procfs API cleanups ASoC core: - Support for only powering up channels that are actively being used - Cleanups / fixes of topology API ASoC drivers: - MediaTek BTCVSD for a Bluetooth radio chip, which is the first such driver we've had upstream! - Quite a few improvements to simplify the generic card drivers, especially the merge of the SCU cards into the main generic drivers - Lots of fixes for probing on Intel systems to follow more standard styles - A big refresh and cleanup of the Samsung drivers - New drivers: Asahi Kasei Microdevices AK4497, Cirrus Logic CS4341 and CS35L26, Google ChromeOS embedded controllers, Ingenic JZ4725B, MediaTek BTCVSD, MT8183 and MT6358, NXP MICFIL, Rockchip RK3328, Spreadtrum DMA controllers, Qualcomm WCD9335, Xilinx S/PDIF and PCM formatters ALSA drivers: - Improvements of Tegra HD-audio controller driver for supporting new chips - HD-audio codec quirks for ALC294 S4 resume, ASUS laptop, Chrome headset button support and Dell workstations - Improved DSD support on USB-audio - Quirk for MOTU MicroBook II USB-audio - Support for Fireface UCX support and Solid State Logic Duende Classic/Mini" * tag 'sound-5.1-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound: (461 commits) ALSA: usb-audio: Add quirk for MOTU MicroBook II ASoC: stm32: i2s: skip useless write in slave mode ASoC: stm32: i2s: fix race condition in irq handler ASoC: stm32: i2s: remove useless callback ASoC: stm32: i2s: fix dma configuration ASoC: stm32: i2s: fix stream count management ASoC: stm32: i2s: fix 16 bit format support ASoC: stm32: i2s: fix IRQ clearing ASoC: qcom: Kconfig: fix dependency for sdm845 ASoC: Intel: Boards: Add Maxim98373 support ASoC: rsnd: gen: fix SSI9 4/5/6/7 busif related register address ALSA: firewire-motu: fix construction of PCM frame for capture direction ALSA: bebob: use more identical mod_alias for Saffire Pro 10 I/O against Liquid Saffire 56 ALSA: hda: Extend i915 component bind timeout ASoC: wm_adsp: Improve logging messages ASoC: wm_adsp: Add support for multiple compressed buffers ASoC: wm_adsp: Refactor compress stream initialisation ASoC: wm_adsp: Reorder some functions for improved clarity ASoC: wm_adsp: Factor out stripping padding from ADSP data ASoC: cs35l36: Fix an IS_ERR() vs NULL checking bug ...
This commit is contained in:
commit
da2577fe63
|
@ -17,12 +17,18 @@ Required properties:
|
|||
Documentation/devicetree/bindings/regulator/regulator.txt
|
||||
|
||||
Optional properties:
|
||||
- reset-gpio: the reset pin for the chip, for more details consult
|
||||
- reset-gpios: the reset pin for the chip, for more details consult
|
||||
Documentation/devicetree/bindings/gpio/gpio.txt
|
||||
|
||||
- DVDD-supply: supply voltage for the digital core, please consult
|
||||
Documentation/devicetree/bindings/regulator/regulator.txt
|
||||
|
||||
- adi,micbias: configures the voltage setting for the MICBIAS pin.
|
||||
Select 0/1/2/3/4/5/6/7/8 to specify MICBIAS voltage
|
||||
5V/5.5V/6V/6.5V/7V/7.5V/8V/8.5V/9V
|
||||
If not specified the default value will be "7" meaning 8.5 Volts.
|
||||
This property is only valid for the ADAU1977
|
||||
|
||||
For required properties on SPI, please consult
|
||||
Documentation/devicetree/bindings/spi/spi-bus.txt
|
||||
|
||||
|
@ -40,7 +46,8 @@ Examples:
|
|||
AVDD-supply = <®ulator>;
|
||||
DVDD-supply = <®ulator_digital>;
|
||||
|
||||
reset_gpio = <&gpio 10 GPIO_ACTIVE_LOW>;
|
||||
adi,micbias = <3>;
|
||||
reset-gpios = <&gpio 10 GPIO_ACTIVE_LOW>;
|
||||
};
|
||||
|
||||
adau1977_i2c: adau1977@11 {
|
||||
|
@ -50,5 +57,5 @@ Examples:
|
|||
AVDD-supply = <®ulator>;
|
||||
DVDD-supply = <®ulator_digital>;
|
||||
|
||||
reset_gpio = <&gpio 10 GPIO_ACTIVE_LOW>;
|
||||
reset-gpios = <&gpio 10 GPIO_ACTIVE_LOW>;
|
||||
};
|
||||
|
|
|
@ -4,7 +4,7 @@ This device supports I2C mode.
|
|||
|
||||
Required properties:
|
||||
|
||||
- compatible : "asahi-kasei,ak4458"
|
||||
- compatible : "asahi-kasei,ak4458" or "asahi-kasei,ak4497"
|
||||
- reg : The I2C address of the device for I2C
|
||||
|
||||
Optional properties:
|
||||
|
|
|
@ -1,123 +0,0 @@
|
|||
Audio-Graph-SCU-Card:
|
||||
|
||||
Audio-Graph-SCU-Card is "Audio-Graph-Card" + "ALSA DPCM".
|
||||
|
||||
It is based on common bindings for device graphs.
|
||||
see ${LINUX}/Documentation/devicetree/bindings/graph.txt
|
||||
|
||||
Basically, Audio-Graph-SCU-Card property is same as
|
||||
Simple-Card / Simple-SCU-Card / Audio-Graph-Card.
|
||||
see ${LINUX}/Documentation/devicetree/bindings/sound/simple-card.txt
|
||||
${LINUX}/Documentation/devicetree/bindings/sound/simple-scu-card.txt
|
||||
${LINUX}/Documentation/devicetree/bindings/sound/audio-graph-card.txt
|
||||
|
||||
Below are same as Simple-Card / Audio-Graph-Card.
|
||||
|
||||
- label
|
||||
- dai-format
|
||||
- frame-master
|
||||
- bitclock-master
|
||||
- bitclock-inversion
|
||||
- frame-inversion
|
||||
- dai-tdm-slot-num
|
||||
- dai-tdm-slot-width
|
||||
- clocks / system-clock-frequency
|
||||
|
||||
Below are same as Simple-SCU-Card.
|
||||
|
||||
- convert-rate
|
||||
- convert-channels
|
||||
- prefix
|
||||
- routing
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible : "audio-graph-scu-card";
|
||||
- dais : list of CPU DAI port{s}
|
||||
|
||||
Example 1. Sampling Rate Conversion
|
||||
|
||||
sound_card {
|
||||
compatible = "audio-graph-scu-card";
|
||||
|
||||
label = "sound-card";
|
||||
prefix = "codec";
|
||||
routing = "codec Playback", "DAI0 Playback",
|
||||
"DAI0 Capture", "codec Capture";
|
||||
convert-rate = <48000>;
|
||||
|
||||
dais = <&cpu_port>;
|
||||
};
|
||||
|
||||
audio-codec {
|
||||
...
|
||||
|
||||
port {
|
||||
codec_endpoint: endpoint {
|
||||
remote-endpoint = <&cpu_endpoint>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
dai-controller {
|
||||
...
|
||||
cpu_port: port {
|
||||
cpu_endpoint: endpoint {
|
||||
remote-endpoint = <&codec_endpoint>;
|
||||
|
||||
dai-format = "left_j";
|
||||
...
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
Example 2. 2 CPU 1 Codec (Mixing)
|
||||
|
||||
sound_card {
|
||||
compatible = "audio-graph-scu-card";
|
||||
|
||||
label = "sound-card";
|
||||
routing = "codec Playback", "DAI0 Playback",
|
||||
"codec Playback", "DAI1 Playback",
|
||||
"DAI0 Capture", "codec Capture";
|
||||
|
||||
dais = <&cpu_port0
|
||||
&cpu_port1>;
|
||||
};
|
||||
|
||||
audio-codec {
|
||||
...
|
||||
|
||||
audio-graph-card,prefix = "codec";
|
||||
audio-graph-card,convert-rate = <48000>;
|
||||
port {
|
||||
codec_endpoint0: endpoint {
|
||||
remote-endpoint = <&cpu_endpoint0>;
|
||||
};
|
||||
codec_endpoint1: endpoint {
|
||||
remote-endpoint = <&cpu_endpoint1>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
dai-controller {
|
||||
...
|
||||
ports {
|
||||
cpu_port0: port {
|
||||
cpu_endpoint0: endpoint {
|
||||
remote-endpoint = <&codec_endpoint0>;
|
||||
|
||||
dai-format = "left_j";
|
||||
...
|
||||
};
|
||||
};
|
||||
cpu_port1: port {
|
||||
cpu_endpoint1: endpoint {
|
||||
remote-endpoint = <&codec_endpoint1>;
|
||||
|
||||
dai-format = "left_j";
|
||||
...
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
|
@ -0,0 +1,168 @@
|
|||
CS35L36 Speaker Amplifier
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible : "cirrus,cs35l36"
|
||||
|
||||
- reg : the I2C address of the device for I2C
|
||||
|
||||
- VA-supply, VP-supply : power supplies for the device,
|
||||
as covered in
|
||||
Documentation/devicetree/bindings/regulator/regulator.txt.
|
||||
|
||||
- cirrus,boost-ctl-millivolt : Boost Voltage Value. Configures the boost
|
||||
converter's output voltage in mV. The range is from 2550mV to 12000mV with
|
||||
increments of 50mV.
|
||||
(Default) VP
|
||||
|
||||
- cirrus,boost-peak-milliamp : Boost-converter peak current limit in mA.
|
||||
Configures the peak current by monitoring the current through the boost FET.
|
||||
Range starts at 1600mA and goes to a maximum of 4500mA with increments of
|
||||
50mA.
|
||||
(Default) 4.50 Amps
|
||||
|
||||
- cirrus,boost-ind-nanohenry : Inductor estimation LBST reference value.
|
||||
Seeds the digital boost converter's inductor estimation block with the initial
|
||||
inductance value to reference.
|
||||
|
||||
1000 = 1uH (Default)
|
||||
1200 = 1.2uH
|
||||
|
||||
Optional properties:
|
||||
- cirrus,multi-amp-mode : Boolean to determine if there are more than
|
||||
one amplifier in the system. If more than one it is best to Hi-Z the ASP
|
||||
port to prevent bus contention on the output signal
|
||||
|
||||
- cirrus,boost-ctl-select : Boost conerter control source selection.
|
||||
Selects the source of the BST_CTL target VBST voltage for the boost
|
||||
converter to generate.
|
||||
0x00 - Control Port Value
|
||||
0x01 - Class H Tracking (Default)
|
||||
0x10 - MultiDevice Sync Value
|
||||
|
||||
- cirrus,amp-pcm-inv : Boolean to determine Amplifier will invert incoming
|
||||
PCM data
|
||||
|
||||
- cirrus,imon-pol-inv : Boolean to determine Amplifier will invert the
|
||||
polarity of outbound IMON feedback data
|
||||
|
||||
- cirrus,vmon-pol-inv : Boolean to determine Amplifier will invert the
|
||||
polarity of outbound VMON feedback data
|
||||
|
||||
- cirrus,dcm-mode-enable : Boost converter automatic DCM Mode enable.
|
||||
This enables the digital boost converter to operate in a low power
|
||||
(Discontinuous Conduction) mode during low loading conditions.
|
||||
|
||||
- cirrus,weak-fet-disable : Boolean : The strength of the output drivers is
|
||||
reduced when operating in a Weak-FET Drive Mode and must not be used to drive
|
||||
a large load.
|
||||
|
||||
- cirrus,classh-wk-fet-delay : Weak-FET entry delay. Controls the delay
|
||||
(in ms) before the Class H algorithm switches to the weak-FET voltage
|
||||
(after the audio falls and remains below the value specified in WKFET_AMP_THLD).
|
||||
|
||||
0 = 0ms
|
||||
1 = 5ms
|
||||
2 = 10ms
|
||||
3 = 50ms
|
||||
4 = 100ms (Default)
|
||||
5 = 200ms
|
||||
6 = 500ms
|
||||
7 = 1000ms
|
||||
|
||||
- cirrus,classh-weak-fet-thld-millivolt : Weak-FET amplifier drive threshold.
|
||||
Configures the signal threshold at which the PWM output stage enters
|
||||
weak-FET operation. The range is 50mV to 700mV in 50mV increments.
|
||||
|
||||
- cirrus,temp-warn-threshold : Amplifier overtemperature warning threshold.
|
||||
Configures the threshold at which the overtemperature warning condition occurs.
|
||||
When the threshold is met, the overtemperature warning attenuation is applied
|
||||
and the TEMP_WARN_EINT interrupt status bit is set.
|
||||
If TEMP_WARN_MASK = 0, INTb is asserted.
|
||||
|
||||
0 = 105C
|
||||
1 = 115C
|
||||
2 = 125C (Default)
|
||||
3 = 135C
|
||||
|
||||
- cirrus,irq-drive-select : Selects the driver type of the selected interrupt
|
||||
output.
|
||||
|
||||
0 = Open-drain
|
||||
1 = Push-pull (Default)
|
||||
|
||||
- cirrus,irq-gpio-select : Selects the pin to serve as the programmable
|
||||
interrupt output.
|
||||
|
||||
0 = PDM_DATA / SWIRE_SD / INT (Default)
|
||||
1 = GPIO
|
||||
|
||||
Optional properties for the "cirrus,vpbr-config" Sub-node
|
||||
|
||||
- cirrus,vpbr-en : VBST brownout prevention enable. Configures whether the
|
||||
VBST brownout prevention algorithm is enabled or disabled.
|
||||
|
||||
0 = VBST brownout prevention disabled (default)
|
||||
1 = VBST brownout prevention enabled
|
||||
|
||||
See Section 7.31.1 VPBR Config for configuration options & further details
|
||||
|
||||
- cirrus,vpbr-thld : Initial VPBR threshold. Configures the VP brownout
|
||||
threshold voltage
|
||||
|
||||
- cirrus,cirrus,vpbr-atk-rate : Attenuation attack step rate. Configures the
|
||||
amount delay between consecutive volume attenuation steps when a brownout
|
||||
condition is present and the VP brownout condition is in an attacking state.
|
||||
|
||||
- cirrus,vpbr-atk-vol : VP brownout prevention step size. Configures the VP
|
||||
brownout prevention attacking attenuation step size when operating in either
|
||||
digital volume or analog gain modes.
|
||||
|
||||
- cirrus,vpbr-max-attn : Maximum attenuation that the VP brownout prevention
|
||||
can apply to the audio signal.
|
||||
|
||||
- cirrus,vpbr-wait : Configures the delay time between a brownout condition
|
||||
no longer being present and the VP brownout prevention entering an attenuation
|
||||
release state.
|
||||
|
||||
- cirrus,vpbr-rel-rate : Attenuation release step rate. Configures the delay
|
||||
between consecutive volume attenuation release steps when a brownout condition
|
||||
is not longer present and the VP brownout is in an attenuation release state.
|
||||
|
||||
- cirrus,vpbr-mute-en : During the attack state, if the vpbr-max-attn value
|
||||
is reached, the error condition still remains, and this bit is set, the audio
|
||||
is muted.
|
||||
|
||||
Example:
|
||||
|
||||
cs35l36: cs35l36@40 {
|
||||
compatible = "cirrus,cs35l36";
|
||||
reg = <0x40>;
|
||||
VA-supply = <&dummy_vreg>;
|
||||
VP-supply = <&dummy_vreg>;
|
||||
reset-gpios = <&gpio0 54 0>;
|
||||
interrupt-parent = <&gpio8>;
|
||||
interrupts = <3 IRQ_TYPE_LEVEL_LOW>;
|
||||
|
||||
cirrus,boost-ind-nanohenry = <1000>;
|
||||
cirrus,boost-ctl-millivolt = <10000>;
|
||||
cirrus,boost-peak-milliamp = <4500>;
|
||||
cirrus,boost-ctl-select = <0x00>;
|
||||
cirrus,weak-fet-delay = <0x04>;
|
||||
cirrus,weak-fet-thld = <0x01>;
|
||||
cirrus,temp-warn-threshold = <0x01>;
|
||||
cirrus,multi-amp-mode;
|
||||
cirrus,irq-drive-select = <0x01>;
|
||||
cirrus,irq-gpio-select = <0x01>;
|
||||
|
||||
cirrus,vpbr-config {
|
||||
cirrus,vpbr-en = <0x00>;
|
||||
cirrus,vpbr-thld = <0x05>;
|
||||
cirrus,vpbr-atk-rate = <0x02>;
|
||||
cirrus,vpbr-atk-vol = <0x01>;
|
||||
cirrus,vpbr-max-attn = <0x09>;
|
||||
cirrus,vpbr-wait = <0x01>;
|
||||
cirrus,vpbr-rel-rate = <0x05>;
|
||||
cirrus,vpbr-mute-en = <0x00>;
|
||||
};
|
||||
};
|
|
@ -0,0 +1,22 @@
|
|||
Cirrus Logic CS4341 audio DAC
|
||||
|
||||
This device supports both I2C and SPI (configured with pin strapping
|
||||
on the board).
|
||||
|
||||
Required properties:
|
||||
- compatible: "cirrus,cs4341a"
|
||||
- reg : the I2C address of the device for I2C, the chip select
|
||||
number for SPI.
|
||||
|
||||
For required properties on I2C-bus, please consult
|
||||
Documentation/devicetree/bindings/i2c/i2c.txt
|
||||
For required properties on SPI-bus, please consult
|
||||
Documentation/devicetree/bindings/spi/spi-bus.txt
|
||||
|
||||
Example:
|
||||
codec: cs4341@0 {
|
||||
#sound-dai-cells = <0>;
|
||||
compatible = "cirrus,cs4341a";
|
||||
reg = <0>;
|
||||
spi-max-frequency = <6000000>;
|
||||
};
|
|
@ -45,6 +45,23 @@ Optional properties:
|
|||
- fck_parent : Should contain a valid clock name which will be used as parent
|
||||
for the McASP fck
|
||||
|
||||
Optional GPIO support:
|
||||
If any McASP pin need to be used as GPIO then the McASP node must have:
|
||||
...
|
||||
gpio-controller
|
||||
#gpio-cells = <2>;
|
||||
...
|
||||
|
||||
When requesting a GPIO, the first parameter is the PIN index in McASP_P*
|
||||
registers.
|
||||
For example to request the AXR2 pin of mcasp8:
|
||||
function-gpios = <&mcasp8 2 0>;
|
||||
|
||||
Or to request the ACLKR pin of mcasp8:
|
||||
function-gpios = <&mcasp8 29 0>;
|
||||
|
||||
For generic gpio information, please refer to bindings/gpio/gpio.txt
|
||||
|
||||
Example:
|
||||
|
||||
mcasp0: mcasp0@1d00000 {
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
NXP MICFIL Digital Audio Interface (MICFIL).
|
||||
|
||||
The MICFIL digital interface provides a 16-bit audio signal from a PDM
|
||||
microphone bitstream in a configurable output sampling rate.
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible : Compatible list, contains "fsl,imx8mm-micfil"
|
||||
|
||||
- reg : Offset and length of the register set for the device.
|
||||
|
||||
- interrupts : Contains the micfil interrupts.
|
||||
|
||||
- clocks : Must contain an entry for each entry in clock-names.
|
||||
|
||||
- clock-names : Must include the "ipg_clk" for register access and
|
||||
"ipg_clk_app" for internal micfil clock.
|
||||
|
||||
- dmas : Generic dma devicetree binding as described in
|
||||
Documentation/devicetree/bindings/dma/dma.txt.
|
||||
|
||||
Example:
|
||||
micfil: micfil@30080000 {
|
||||
compatible = "fsl,imx8mm-micfil";
|
||||
reg = <0x0 0x30080000 0x0 0x10000>;
|
||||
interrupts = <GIC_SPI 109 IRQ_TYPE_LEVEL_HIGH>,
|
||||
<GIC_SPI 110 IRQ_TYPE_LEVEL_HIGH>;
|
||||
clocks = <&clk IMX8MM_CLK_PDM_IPG>,
|
||||
<&clk IMX8MM_CLK_PDM_ROOT>;
|
||||
clock-names = "ipg_clk", "ipg_clk_app";
|
||||
dmas = <&sdma2 24 26 0x80000000>;
|
||||
};
|
|
@ -0,0 +1,26 @@
|
|||
* Audio codec controlled by ChromeOS EC
|
||||
|
||||
Google's ChromeOS EC codec is a digital mic codec provided by the
|
||||
Embedded Controller (EC) and is controlled via a host-command interface.
|
||||
|
||||
An EC codec node should only be found as a sub-node of the EC node (see
|
||||
Documentation/devicetree/bindings/mfd/cros-ec.txt).
|
||||
|
||||
Required properties:
|
||||
- compatible: Must contain "google,cros-ec-codec"
|
||||
- #sound-dai-cells: Should be 1. The cell specifies number of DAIs.
|
||||
- max-dmic-gain: A number for maximum gain in dB on digital microphone.
|
||||
|
||||
Example:
|
||||
|
||||
cros-ec@0 {
|
||||
compatible = "google,cros-ec-spi";
|
||||
|
||||
...
|
||||
|
||||
cros_ec_codec: ec-codec {
|
||||
compatible = "google,cros-ec-codec";
|
||||
#sound-dai-cells = <1>;
|
||||
max-dmic-gain = <43>;
|
||||
};
|
||||
};
|
|
@ -0,0 +1,20 @@
|
|||
Ingenic JZ4725B codec controller
|
||||
|
||||
Required properties:
|
||||
- compatible : "ingenic,jz4725b-codec"
|
||||
- reg : codec registers location and length
|
||||
- clocks : phandle to the AIC clock.
|
||||
- clock-names: must be set to "aic".
|
||||
- #sound-dai-cells: Must be set to 0.
|
||||
|
||||
Example:
|
||||
|
||||
codec: audio-codec@100200a4 {
|
||||
compatible = "ingenic,jz4725b-codec";
|
||||
reg = <0x100200a4 0x8>;
|
||||
|
||||
#sound-dai-cells = <0>;
|
||||
|
||||
clocks = <&cgu JZ4725B_CLK_AIC>;
|
||||
clock-names = "aic";
|
||||
};
|
|
@ -0,0 +1,20 @@
|
|||
Ingenic JZ4740 codec controller
|
||||
|
||||
Required properties:
|
||||
- compatible : "ingenic,jz4740-codec"
|
||||
- reg : codec registers location and length
|
||||
- clocks : phandle to the AIC clock.
|
||||
- clock-names: must be set to "aic".
|
||||
- #sound-dai-cells: Must be set to 0.
|
||||
|
||||
Example:
|
||||
|
||||
codec: audio-codec@10020080 {
|
||||
compatible = "ingenic,jz4740-codec";
|
||||
reg = <0x10020080 0x8>;
|
||||
|
||||
#sound-dai-cells = <0>;
|
||||
|
||||
clocks = <&cgu JZ4740_CLK_AIC>;
|
||||
clock-names = "aic";
|
||||
};
|
|
@ -0,0 +1,18 @@
|
|||
Mediatek MT6358 Audio Codec
|
||||
|
||||
The communication between MT6358 and SoC is through Mediatek PMIC wrapper.
|
||||
For more detail, please visit Mediatek PMIC wrapper documentation.
|
||||
|
||||
Must be a child node of PMIC wrapper.
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible : "mediatek,mt6358-sound".
|
||||
- Avdd-supply : power source of AVDD
|
||||
|
||||
Example:
|
||||
|
||||
mt6358_snd {
|
||||
compatible = "mediatek,mt6358-sound";
|
||||
Avdd-supply = <&mt6358_vaud28_reg>;
|
||||
};
|
|
@ -0,0 +1,36 @@
|
|||
Mediatek AFE PCM controller for mt8183
|
||||
|
||||
Required properties:
|
||||
- compatible = "mediatek,mt68183-audio";
|
||||
- reg: register location and size
|
||||
- interrupts: should contain AFE interrupt
|
||||
- power-domains: should define the power domain
|
||||
- clocks: Must contain an entry for each entry in clock-names
|
||||
- clock-names: should have these clock names:
|
||||
"infra_sys_audio_clk",
|
||||
"mtkaif_26m_clk",
|
||||
"top_mux_audio",
|
||||
"top_mux_aud_intbus",
|
||||
"top_sys_pll3_d4",
|
||||
"top_clk26m_clk";
|
||||
|
||||
Example:
|
||||
|
||||
afe: mt8183-afe-pcm@11220000 {
|
||||
compatible = "mediatek,mt8183-audio";
|
||||
reg = <0 0x11220000 0 0x1000>;
|
||||
interrupts = <GIC_SPI 161 IRQ_TYPE_LEVEL_LOW>;
|
||||
power-domains = <&scpsys MT8183_POWER_DOMAIN_AUDIO>;
|
||||
clocks = <&infrasys CLK_INFRA_AUDIO>,
|
||||
<&infrasys CLK_INFRA_AUDIO_26M_BCLK>,
|
||||
<&topckgen CLK_TOP_MUX_AUDIO>,
|
||||
<&topckgen CLK_TOP_MUX_AUD_INTBUS>,
|
||||
<&topckgen CLK_TOP_SYSPLL_D2_D4>,
|
||||
<&clk26m>;
|
||||
clock-names = "infra_sys_audio_clk",
|
||||
"mtkaif_26m_clk",
|
||||
"top_mux_audio",
|
||||
"top_mux_aud_intbus",
|
||||
"top_sys_pll_d2_d4",
|
||||
"top_clk26m_clk";
|
||||
};
|
|
@ -0,0 +1,24 @@
|
|||
Mediatek ALSA BT SCO CVSD/MSBC Driver
|
||||
|
||||
Required properties:
|
||||
- compatible = "mediatek,mtk-btcvsd-snd";
|
||||
- reg: register location and size of PKV and SRAM_BANK2
|
||||
- interrupts: should contain BTSCO interrupt
|
||||
- mediatek,infracfg: the phandles of INFRASYS
|
||||
- mediatek,offset: Array contains of register offset and mask
|
||||
infra_misc_offset,
|
||||
infra_conn_bt_cvsd_mask,
|
||||
cvsd_mcu_read_offset,
|
||||
cvsd_mcu_write_offset,
|
||||
cvsd_packet_indicator_offset
|
||||
|
||||
Example:
|
||||
|
||||
mtk-btcvsd-snd@18000000 {
|
||||
compatible = "mediatek,mtk-btcvsd-snd";
|
||||
reg=<0 0x18000000 0 0x1000>,
|
||||
<0 0x18080000 0 0x8000>;
|
||||
interrupts = <GIC_SPI 286 IRQ_TYPE_LEVEL_LOW>;
|
||||
mediatek,infracfg = <&infrasys>;
|
||||
mediatek,offset = <0xf00 0x800 0xfd0 0xfd4 0xfd8>;
|
||||
};
|
|
@ -13,6 +13,10 @@ Required properties:
|
|||
See ../reset/reset.txt for details.
|
||||
- reset-names : Must include the following entries: hda, hda2hdmi, hda2codec_2x
|
||||
|
||||
Optional properties:
|
||||
- nvidia,model : The user-visible name of this sound complex. Since the property
|
||||
is optional, legacy boards can use default name provided in hda driver.
|
||||
|
||||
Example:
|
||||
|
||||
hda@70030000 {
|
||||
|
@ -27,4 +31,5 @@ hda@70030000 {
|
|||
<&tegra_car 128>, /* hda2hdmi */
|
||||
<&tegra_car 111>; /* hda2codec_2x */
|
||||
reset-names = "hda", "hda2hdmi", "hda2codec_2x";
|
||||
nvidia,model = "jetson-tk1-hda";
|
||||
};
|
||||
|
|
|
@ -30,6 +30,7 @@ Required properties
|
|||
- vdd-cdc-io-supply: phandle to VDD_CDC_IO regulator DT node.
|
||||
- vdd-cdc-tx-rx-cx-supply: phandle to VDD_CDC_TX/RX/CX regulator DT node.
|
||||
- vdd-micbias-supply: phandle of VDD_MICBIAS supply's regulator DT node.
|
||||
|
||||
Optional Properties:
|
||||
- qcom,mbhc-vthreshold-low: Array of 5 threshold voltages in mV for 5 buttons
|
||||
detection on headset when the mbhc is powered up
|
||||
|
@ -92,9 +93,9 @@ spmi_bus {
|
|||
"cdc_ear_cnp_int",
|
||||
"cdc_hphr_cnp_int",
|
||||
"cdc_hphl_cnp_int";
|
||||
VDD-CDC-IO-supply = <&pm8916_l5>;
|
||||
VDD-CDC-TX-RX-CX-supply = <&pm8916_l5>;
|
||||
VDD-MICBIAS-supply = <&pm8916_l13>;
|
||||
vdd-cdc-io-supply = <&pm8916_l5>;
|
||||
vdd-cdc-tx-rx-cx-supply = <&pm8916_l5>;
|
||||
vdd-micbias-supply = <&pm8916_l13>;
|
||||
#sound-dai-cells = <1>;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -34,12 +34,12 @@ Required properties with SLIMbus Interface:
|
|||
Definition: Interrupt names of WCD INTR1 and INTR2
|
||||
Should be: "intr1", "intr2"
|
||||
|
||||
- reset-gpio:
|
||||
- reset-gpios:
|
||||
Usage: required
|
||||
Value type: <String Array>
|
||||
Definition: Reset gpio line
|
||||
|
||||
- qcom,ifd:
|
||||
- slim-ifc-dev:
|
||||
Usage: required
|
||||
Value type: <phandle>
|
||||
Definition: SLIM interface device
|
||||
|
@ -104,13 +104,13 @@ Required properties with SLIMbus Interface:
|
|||
Value type: <u32>
|
||||
Definition: Must be 1
|
||||
|
||||
codec@1{
|
||||
audio-codec@1{
|
||||
compatible = "slim217,1a0";
|
||||
reg = <1 0>;
|
||||
interrupts = <&msmgpio 54 IRQ_TYPE_LEVEL_HIGH>;
|
||||
interrupt-names = "intr2"
|
||||
reset-gpio = <&msmgpio 64 0>;
|
||||
qcom,ifd = <&wc9335_ifd>;
|
||||
reset-gpios = <&msmgpio 64 0>;
|
||||
slim-ifc-dev = <&wc9335_ifd>;
|
||||
clock-names = "mclk", "native";
|
||||
clocks = <&rpmcc RPM_SMD_DIV_CLK1>,
|
||||
<&rpmcc RPM_SMD_BB_CLK1>;
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
* Rockchip Rk3328 internal codec
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible: "rockchip,rk3328-codec"
|
||||
- reg: physical base address of the controller and length of memory mapped
|
||||
region.
|
||||
- rockchip,grf: the phandle of the syscon node for GRF register.
|
||||
- clocks: a list of phandle + clock-specifer pairs, one for each entry in clock-names.
|
||||
- clock-names: should be "pclk".
|
||||
- spk-depop-time-ms: speak depop time msec.
|
||||
|
||||
Example for rk3328 internal codec:
|
||||
|
||||
codec: codec@ff410000 {
|
||||
compatible = "rockchip,rk3328-codec";
|
||||
reg = <0x0 0xff410000 0x0 0x1000>;
|
||||
rockchip,grf = <&grf>;
|
||||
clocks = <&cru PCLK_ACODEC>;
|
||||
clock-names = "pclk";
|
||||
spk-depop-time-ms = 100;
|
||||
status = "disabled";
|
||||
};
|
|
@ -37,6 +37,15 @@ VDDIO 1.8V 2.5V 3.3V
|
|||
2 = 3.33 mA 5.74 mA 8.03 mA
|
||||
3 = 4.99 mA 8.61 mA 12.05 mA
|
||||
|
||||
- sclk-strength: the SCLK pad strength. Possible values are:
|
||||
0, 1, 2 and 3 as per the table below:
|
||||
|
||||
VDDIO 1.8V 2.5V 3.3V
|
||||
0 = Disable
|
||||
1 = 1.66 mA 2.87 mA 4.02 mA
|
||||
2 = 3.33 mA 5.74 mA 8.03 mA
|
||||
3 = 4.99 mA 8.61 mA 12.05 mA
|
||||
|
||||
Example:
|
||||
|
||||
sgtl5000: codec@a {
|
||||
|
|
|
@ -1,94 +0,0 @@
|
|||
ASoC Simple SCU Sound Card
|
||||
|
||||
Simple SCU Sound Card is "Simple Sound Card" + "ALSA DPCM".
|
||||
For example, you can use this driver if you want to exchange sampling rate convert,
|
||||
Mixing, etc...
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible : "simple-scu-audio-card"
|
||||
"renesas,rsrc-card"
|
||||
Optional properties:
|
||||
|
||||
- simple-audio-card,name : see simple-audio-card.txt
|
||||
- simple-audio-card,cpu : see simple-audio-card.txt
|
||||
- simple-audio-card,codec : see simple-audio-card.txt
|
||||
|
||||
Optional subnode properties:
|
||||
|
||||
- simple-audio-card,format : see simple-audio-card.txt
|
||||
- simple-audio-card,frame-master : see simple-audio-card.txt
|
||||
- simple-audio-card,bitclock-master : see simple-audio-card.txt
|
||||
- simple-audio-card,bitclock-inversion : see simple-audio-card.txt
|
||||
- simple-audio-card,frame-inversion : see simple-audio-card.txt
|
||||
- simple-audio-card,convert-rate : platform specified sampling rate convert
|
||||
- simple-audio-card,convert-channels : platform specified converted channel size (2 - 8 ch)
|
||||
- simple-audio-card,prefix : see routing
|
||||
- simple-audio-card,widgets : Please refer to widgets.txt.
|
||||
- simple-audio-card,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.
|
||||
use audio-prefix if some components is using same sink/sources naming.
|
||||
it can be used if compatible was "renesas,rsrc-card";
|
||||
|
||||
Required CPU/CODEC subnodes properties:
|
||||
|
||||
- sound-dai : see simple-audio-card.txt
|
||||
|
||||
Optional CPU/CODEC subnodes properties:
|
||||
|
||||
- clocks / system-clock-frequency : see simple-audio-card.txt
|
||||
|
||||
Example 1. Sampling Rate Conversion
|
||||
|
||||
sound {
|
||||
compatible = "simple-scu-audio-card";
|
||||
|
||||
simple-audio-card,name = "rsnd-ak4643";
|
||||
simple-audio-card,format = "left_j";
|
||||
simple-audio-card,bitclock-master = <&sndcodec>;
|
||||
simple-audio-card,frame-master = <&sndcodec>;
|
||||
|
||||
simple-audio-card,convert-rate = <48000>;
|
||||
|
||||
simple-audio-card,prefix = "ak4642";
|
||||
simple-audio-card,routing = "ak4642 Playback", "DAI0 Playback",
|
||||
"DAI0 Capture", "ak4642 Capture";
|
||||
|
||||
sndcpu: simple-audio-card,cpu {
|
||||
sound-dai = <&rcar_sound>;
|
||||
};
|
||||
|
||||
sndcodec: simple-audio-card,codec {
|
||||
sound-dai = <&ak4643>;
|
||||
system-clock-frequency = <11289600>;
|
||||
};
|
||||
};
|
||||
|
||||
Example 2. 2 CPU 1 Codec (Mixing)
|
||||
|
||||
sound {
|
||||
compatible = "simple-scu-audio-card";
|
||||
|
||||
simple-audio-card,name = "rsnd-ak4643";
|
||||
simple-audio-card,format = "left_j";
|
||||
simple-audio-card,bitclock-master = <&dpcmcpu>;
|
||||
simple-audio-card,frame-master = <&dpcmcpu>;
|
||||
|
||||
simple-audio-card,routing = "ak4642 Playback", "DAI0 Playback",
|
||||
"ak4642 Playback", "DAI1 Playback";
|
||||
|
||||
dpcmcpu: cpu@0 {
|
||||
sound-dai = <&rcar_sound 0>;
|
||||
};
|
||||
|
||||
cpu@1 {
|
||||
sound-dai = <&rcar_sound 1>;
|
||||
};
|
||||
|
||||
codec {
|
||||
prefix = "ak4642";
|
||||
sound-dai = <&ak4643>;
|
||||
clocks = <&audio_clock>;
|
||||
};
|
||||
};
|
|
@ -0,0 +1,23 @@
|
|||
* Spreadtrum DMA platfrom bindings
|
||||
|
||||
Required properties:
|
||||
- compatible: Should be "sprd,pcm-platform".
|
||||
- dmas: Specify the 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.
|
||||
|
||||
Example:
|
||||
|
||||
audio_platform:platform@0 {
|
||||
compatible = "sprd,pcm-platform";
|
||||
dmas = <&agcp_dma 1 1>, <&agcp_dma 2 2>,
|
||||
<&agcp_dma 3 3>, <&agcp_dma 4 4>,
|
||||
<&agcp_dma 5 5>, <&agcp_dma 6 6>,
|
||||
<&agcp_dma 7 7>, <&agcp_dma 8 8>,
|
||||
<&agcp_dma 9 9>, <&agcp_dma 10 10>;
|
||||
dma-names = "normal_p_l", "normal_p_r",
|
||||
"normal_c_l", "normal_c_r",
|
||||
"voice_c", "fast_p",
|
||||
"loop_c", "loop_p",
|
||||
"voip_c", "voip_p";
|
||||
};
|
|
@ -0,0 +1,29 @@
|
|||
Device-Tree bindings for Xilinx PL audio formatter
|
||||
|
||||
The IP core supports DMA, data formatting(AES<->PCM conversion)
|
||||
of audio samples.
|
||||
|
||||
Required properties:
|
||||
- compatible: "xlnx,audio-formatter-1.0"
|
||||
- interrupt-names: Names specified to list of interrupts in same
|
||||
order mentioned under "interrupts".
|
||||
List of supported interrupt names are:
|
||||
"irq_mm2s" : interrupt from MM2S block
|
||||
"irq_s2mm" : interrupt from S2MM block
|
||||
- interrupts-parent: Phandle for interrupt controller.
|
||||
- interrupts: List of Interrupt numbers.
|
||||
- reg: Base address and size of the IP core instance.
|
||||
- clock-names: List of input clocks.
|
||||
Required elements: "s_axi_lite_aclk", "aud_mclk"
|
||||
- clocks: Input clock specifier. Refer to common clock bindings.
|
||||
|
||||
Example:
|
||||
audio_ss_0_audio_formatter_0: audio_formatter@80010000 {
|
||||
compatible = "xlnx,audio-formatter-1.0";
|
||||
interrupt-names = "irq_mm2s", "irq_s2mm";
|
||||
interrupt-parent = <&gic>;
|
||||
interrupts = <0 104 4>, <0 105 4>;
|
||||
reg = <0x0 0x80010000 0x0 0x1000>;
|
||||
clock-names = "s_axi_lite_aclk", "aud_mclk";
|
||||
clocks = <&clk 71>, <&clk_wiz_1 0>;
|
||||
};
|
|
@ -0,0 +1,28 @@
|
|||
Device-Tree bindings for Xilinx SPDIF IP
|
||||
|
||||
The IP supports playback and capture of SPDIF audio
|
||||
|
||||
Required properties:
|
||||
- compatible: "xlnx,spdif-2.0"
|
||||
- clock-names: List of input clocks.
|
||||
Required elements: "s_axi_aclk", "aud_clk_i"
|
||||
- clocks: Input clock specifier. Refer to common clock bindings.
|
||||
- reg: Base address and address length of the IP core instance.
|
||||
- interrupts-parent: Phandle for interrupt controller.
|
||||
- interrupts: List of Interrupt numbers.
|
||||
- xlnx,spdif-mode: 0 :- receiver mode
|
||||
1 :- transmitter mode
|
||||
- xlnx,aud_clk_i: input audio clock value.
|
||||
|
||||
Example:
|
||||
spdif_0: spdif@80010000 {
|
||||
clock-names = "aud_clk_i", "s_axi_aclk";
|
||||
clocks = <&misc_clk_0>, <&clk 71>;
|
||||
compatible = "xlnx,spdif-2.0";
|
||||
interrupt-names = "spdif_interrupt";
|
||||
interrupt-parent = <&gic>;
|
||||
interrupts = <0 91 4>;
|
||||
reg = <0x0 0x80010000 0x0 0x10000>;
|
||||
xlnx,spdif-mode = <1>;
|
||||
xlnx,aud_clk_i = <49152913>;
|
||||
};
|
|
@ -254,10 +254,12 @@ alc274-dell-aio
|
|||
ALC274 fixups on Dell AIO machines
|
||||
alc255-dummy-lineout
|
||||
Dell Precision 3930 fixups
|
||||
alc255-dell-headset"},
|
||||
alc255-dell-headset
|
||||
Dell Precision 3630 fixups
|
||||
alc295-hp-x360
|
||||
HP Spectre X360 fixups
|
||||
alc-sense-combo
|
||||
Headset button support for Chrome platform
|
||||
|
||||
ALC66x/67x/892
|
||||
==============
|
||||
|
|
|
@ -3520,14 +3520,14 @@ allocator will try to get an area as large as possible within the
|
|||
given size.
|
||||
|
||||
The second argument (type) and the third argument (device pointer) are
|
||||
dependent on the bus. In the case of the ISA bus, pass
|
||||
:c:func:`snd_dma_isa_data()` as the third argument with
|
||||
dependent on the bus. For normal devices, pass the device pointer
|
||||
(typically identical as ``card->dev``) to the third argument with
|
||||
``SNDRV_DMA_TYPE_DEV`` type. For the continuous buffer unrelated to the
|
||||
bus can be pre-allocated with ``SNDRV_DMA_TYPE_CONTINUOUS`` type and the
|
||||
``snd_dma_continuous_data(GFP_KERNEL)`` device pointer, where
|
||||
``GFP_KERNEL`` is the kernel allocation flag to use. For the PCI
|
||||
scatter-gather buffers, use ``SNDRV_DMA_TYPE_DEV_SG`` with
|
||||
``snd_dma_pci_data(pci)`` (see the `Non-Contiguous Buffers`_
|
||||
``GFP_KERNEL`` is the kernel allocation flag to use. For the
|
||||
scatter-gather buffers, use ``SNDRV_DMA_TYPE_DEV_SG`` with the device
|
||||
pointer (see the `Non-Contiguous Buffers`_
|
||||
section).
|
||||
|
||||
Once the buffer is pre-allocated, you can use the allocator in the
|
||||
|
@ -3924,15 +3924,12 @@ The scheme of the real suspend job is as follows.
|
|||
2. Call :c:func:`snd_power_change_state()` with
|
||||
``SNDRV_CTL_POWER_D3hot`` to change the power status.
|
||||
|
||||
3. Call :c:func:`snd_pcm_suspend_all()` to suspend the running
|
||||
PCM streams.
|
||||
|
||||
4. If AC97 codecs are used, call :c:func:`snd_ac97_suspend()` for
|
||||
3. If AC97 codecs are used, call :c:func:`snd_ac97_suspend()` for
|
||||
each codec.
|
||||
|
||||
5. Save the register values if necessary.
|
||||
4. Save the register values if necessary.
|
||||
|
||||
6. Stop the hardware if necessary.
|
||||
5. Stop the hardware if necessary.
|
||||
|
||||
A typical code would be like:
|
||||
|
||||
|
@ -3946,12 +3943,10 @@ A typical code would be like:
|
|||
/* (2) */
|
||||
snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
|
||||
/* (3) */
|
||||
snd_pcm_suspend_all(chip->pcm);
|
||||
/* (4) */
|
||||
snd_ac97_suspend(chip->ac97);
|
||||
/* (5) */
|
||||
/* (4) */
|
||||
snd_mychip_save_registers(chip);
|
||||
/* (6) */
|
||||
/* (5) */
|
||||
snd_mychip_stop_hardware(chip);
|
||||
return 0;
|
||||
}
|
||||
|
@ -3994,13 +3989,9 @@ A typical code would be like:
|
|||
return 0;
|
||||
}
|
||||
|
||||
As shown in the above, it's better to save registers after suspending
|
||||
the PCM operations via :c:func:`snd_pcm_suspend_all()` or
|
||||
:c:func:`snd_pcm_suspend()`. It means that the PCM streams are
|
||||
already stopped when the register snapshot is taken. But, remember that
|
||||
you don't have to restart the PCM stream in the resume callback. It'll
|
||||
be restarted via trigger call with ``SNDRV_PCM_TRIGGER_RESUME`` when
|
||||
necessary.
|
||||
Note that, at the time this callback gets called, the PCM stream has
|
||||
been already suspended via its own PM ops calling
|
||||
:c:func:`snd_pcm_suspend_all()` internally.
|
||||
|
||||
OK, we have all callbacks now. Let's set them up. In the initialization
|
||||
of the card, make sure that you can get the chip data from the card
|
||||
|
|
|
@ -13,7 +13,7 @@ drivers that expose several ALSA PCMs and can route to multiple DAIs.
|
|||
The DPCM runtime routing is determined by the ALSA mixer settings in the same
|
||||
way as the analog signal is routed in an ASoC codec driver. DPCM uses a DAPM
|
||||
graph representing the DSP internal audio paths and uses the mixer settings to
|
||||
determine the patch used by each ALSA PCM.
|
||||
determine the path used by each ALSA PCM.
|
||||
|
||||
DPCM re-uses all the existing component codec, platform and DAI drivers without
|
||||
any modifications.
|
||||
|
@ -101,7 +101,7 @@ The audio driver processes this as follows :-
|
|||
|
||||
4. Machine driver or audio HAL enables the speaker path.
|
||||
|
||||
5. DPCM runs the PCM ops for startup(), hw_params(), prepapre() and
|
||||
5. DPCM runs the PCM ops for startup(), hw_params(), prepare() and
|
||||
trigger(start) for DAI1 Speakers since the path is enabled.
|
||||
|
||||
In this example, the machine driver or userspace audio HAL can alter the routing
|
||||
|
@ -221,7 +221,7 @@ like a BT phone call :-
|
|||
This allows the host CPU to sleep while the DSP, MODEM DAI and the BT DAI are
|
||||
still in operation.
|
||||
|
||||
A BE DAI link can also set the codec to a dummy device if the code is a device
|
||||
A BE DAI link can also set the codec to a dummy device if the codec is a device
|
||||
that is managed externally.
|
||||
|
||||
Likewise a BE DAI can also set a dummy cpu DAI if the CPU DAI is managed by the
|
||||
|
@ -249,7 +249,7 @@ configuration.
|
|||
struct snd_interval *channels = hw_param_interval(params,
|
||||
SNDRV_PCM_HW_PARAM_CHANNELS);
|
||||
|
||||
/* The DSP will covert the FE rate to 48k, stereo */
|
||||
/* The DSP will convert the FE rate to 48k, stereo */
|
||||
rate->min = rate->max = 48000;
|
||||
channels->min = channels->max = 2;
|
||||
|
||||
|
@ -386,5 +386,3 @@ This means creating a new FE that is connected with a virtual path to both
|
|||
DAI links. The DAI links will be started when the FE PCM is started and stopped
|
||||
when the FE PCM is stopped. Note that the FE PCM cannot read or write data in
|
||||
this configuration.
|
||||
|
||||
|
||||
|
|
|
@ -3727,6 +3727,14 @@ N: cros_ec
|
|||
N: cros-ec
|
||||
F: drivers/power/supply/cros_usbpd-charger.c
|
||||
|
||||
CHROMEOS EC CODEC DRIVER
|
||||
M: Cheng-Yi Chiang <cychiang@chromium.org>
|
||||
S: Maintained
|
||||
R: Enric Balletbo i Serra <enric.balletbo@collabora.com>
|
||||
R: Guenter Roeck <groeck@chromium.org>
|
||||
F: Documentation/devicetree/bindings/sound/google,cros-ec-codec.txt
|
||||
F: sound/soc/codecs/cros_ec_codec.*
|
||||
|
||||
CIRRUS LOGIC AUDIO CODEC DRIVERS
|
||||
M: Brian Austin <brian.austin@cirrus.com>
|
||||
M: Paul Handrigan <Paul.Handrigan@cirrus.com>
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
};
|
||||
|
||||
hda@3510000 {
|
||||
nvidia,model = "jetson-tx2-hda";
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
};
|
||||
|
||||
hda@3510000 {
|
||||
nvidia,model = "jetson-xavier-hda";
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
|
|
|
@ -1323,6 +1323,7 @@
|
|||
};
|
||||
|
||||
hda@70030000 {
|
||||
nvidia,model = "jetson-tx1-hda";
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
|
|
|
@ -530,17 +530,24 @@ static int acpi_populate_gpio_lookup(struct acpi_resource *ares, void *data)
|
|||
if (ares->type != ACPI_RESOURCE_TYPE_GPIO)
|
||||
return 1;
|
||||
|
||||
if (lookup->n++ == lookup->index && !lookup->desc) {
|
||||
if (!lookup->desc) {
|
||||
const struct acpi_resource_gpio *agpio = &ares->data.gpio;
|
||||
int pin_index = lookup->pin_index;
|
||||
bool gpioint = agpio->connection_type == ACPI_RESOURCE_GPIO_TYPE_INT;
|
||||
int pin_index;
|
||||
|
||||
if (lookup->info.quirks & ACPI_GPIO_QUIRK_ONLY_GPIOIO && gpioint)
|
||||
lookup->index++;
|
||||
|
||||
if (lookup->n++ != lookup->index)
|
||||
return 1;
|
||||
|
||||
pin_index = lookup->pin_index;
|
||||
if (pin_index >= agpio->pin_table_length)
|
||||
return 1;
|
||||
|
||||
lookup->desc = acpi_get_gpiod(agpio->resource_source.string_ptr,
|
||||
agpio->pin_table[pin_index]);
|
||||
lookup->info.gpioint =
|
||||
agpio->connection_type == ACPI_RESOURCE_GPIO_TYPE_INT;
|
||||
lookup->info.gpioint = gpioint;
|
||||
|
||||
/*
|
||||
* Polarity and triggering are only specified for GpioInt
|
||||
|
|
|
@ -614,7 +614,6 @@ static int snd_dw_hdmi_suspend(struct device *dev)
|
|||
struct snd_dw_hdmi *dw = dev_get_drvdata(dev);
|
||||
|
||||
snd_power_change_state(dw->card, SNDRV_CTL_POWER_D3cold);
|
||||
snd_pcm_suspend_all(dw->pcm);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -360,13 +360,11 @@ static int solo_snd_pcm_init(struct solo_dev *solo_dev)
|
|||
ss; ss = ss->next, i++)
|
||||
sprintf(ss->name, "Camera #%d Audio", i);
|
||||
|
||||
ret = snd_pcm_lib_preallocate_pages_for_all(pcm,
|
||||
snd_pcm_lib_preallocate_pages_for_all(pcm,
|
||||
SNDRV_DMA_TYPE_CONTINUOUS,
|
||||
snd_dma_continuous_data(GFP_KERNEL),
|
||||
G723_PERIOD_BYTES * PERIODS,
|
||||
G723_PERIOD_BYTES * PERIODS);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
solo_dev->snd_pcm = pcm;
|
||||
|
||||
|
|
|
@ -301,11 +301,12 @@ static int tw686x_snd_pcm_init(struct tw686x_dev *dev)
|
|||
ss; ss = ss->next, i++)
|
||||
snprintf(ss->name, sizeof(ss->name), "vch%u audio", i);
|
||||
|
||||
return snd_pcm_lib_preallocate_pages_for_all(pcm,
|
||||
snd_pcm_lib_preallocate_pages_for_all(pcm,
|
||||
SNDRV_DMA_TYPE_DEV,
|
||||
snd_dma_pci_data(dev->pci_dev),
|
||||
TW686X_AUDIO_PAGE_MAX * AUDIO_DMA_SIZE_MAX,
|
||||
TW686X_AUDIO_PAGE_MAX * AUDIO_DMA_SIZE_MAX);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void tw686x_audio_dma_free(struct tw686x_dev *dev,
|
||||
|
|
|
@ -1014,6 +1014,13 @@ struct acpi_gpio_mapping {
|
|||
|
||||
/* Ignore IoRestriction field */
|
||||
#define ACPI_GPIO_QUIRK_NO_IO_RESTRICTION BIT(0)
|
||||
/*
|
||||
* When ACPI GPIO mapping table is in use the index parameter inside it
|
||||
* refers to the GPIO resource in _CRS method. That index has no
|
||||
* distinction of actual type of the resource. When consumer wants to
|
||||
* get GpioIo type explicitly, this quirk may be used.
|
||||
*/
|
||||
#define ACPI_GPIO_QUIRK_ONLY_GPIOIO BIT(1)
|
||||
|
||||
unsigned int quirks;
|
||||
};
|
||||
|
|
|
@ -120,7 +120,6 @@ struct snd_card {
|
|||
struct list_head ctl_files; /* active control files */
|
||||
|
||||
struct snd_info_entry *proc_root; /* root for soundcard specific files */
|
||||
struct snd_info_entry *proc_id; /* the card id */
|
||||
struct proc_dir_entry *proc_root_link; /* number link to real id */
|
||||
|
||||
struct list_head files_list; /* all files associated to this card */
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* linux/sound/cs35l36.h -- Platform data for CS35L36
|
||||
*
|
||||
* Copyright 2018 Cirrus Logic, Inc.
|
||||
*
|
||||
* Author: James Schulman <james.schulman@cirrus.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __CS35L36_H
|
||||
#define __CS35L36_H
|
||||
|
||||
struct cs35l36_vpbr_cfg {
|
||||
bool is_present;
|
||||
bool vpbr_en;
|
||||
int vpbr_thld;
|
||||
int vpbr_atk_rate;
|
||||
int vpbr_atk_vol;
|
||||
int vpbr_max_attn;
|
||||
int vpbr_wait;
|
||||
int vpbr_rel_rate;
|
||||
int vpbr_mute_en;
|
||||
};
|
||||
|
||||
struct cs35l36_platform_data {
|
||||
bool multi_amp_mode;
|
||||
bool dcm_mode;
|
||||
bool amp_pcm_inv;
|
||||
bool imon_pol_inv;
|
||||
bool vmon_pol_inv;
|
||||
int boost_ind;
|
||||
int bst_vctl;
|
||||
int bst_vctl_sel;
|
||||
int bst_ipk;
|
||||
bool extern_boost;
|
||||
int temp_warn_thld;
|
||||
int irq_drv_sel;
|
||||
int irq_gpio_sel;
|
||||
struct cs35l36_vpbr_cfg vpbr_config;
|
||||
};
|
||||
|
||||
#endif /* __CS35L36_H */
|
|
@ -99,10 +99,6 @@ void snd_dmaengine_pcm_set_config_from_dai_data(
|
|||
* playback.
|
||||
*/
|
||||
#define SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX BIT(3)
|
||||
/*
|
||||
* The PCM streams have custom channel names specified.
|
||||
*/
|
||||
#define SND_DMAENGINE_PCM_FLAG_CUSTOM_CHANNEL_NAME BIT(4)
|
||||
|
||||
/**
|
||||
* struct snd_dmaengine_pcm_config - Configuration data for dmaengine based PCM
|
||||
|
|
|
@ -79,6 +79,7 @@ enum { SDI0, SDI1, SDI2, SDI3, SDO0, SDO1, SDO2, SDO3 };
|
|||
|
||||
/* stream register offsets from stream base */
|
||||
#define AZX_REG_SD_CTL 0x00
|
||||
#define AZX_REG_SD_CTL_3B 0x02 /* 3rd byte of SD_CTL register */
|
||||
#define AZX_REG_SD_STS 0x03
|
||||
#define AZX_REG_SD_LPIB 0x04
|
||||
#define AZX_REG_SD_CBL 0x08
|
||||
|
@ -165,6 +166,7 @@ enum { SDI0, SDI1, SDI2, SDI3, SDO0, SDO1, SDO2, SDO3 };
|
|||
#define SD_INT_COMPLETE 0x04 /* completion interrupt */
|
||||
#define SD_INT_MASK (SD_INT_DESC_ERR|SD_INT_FIFO_ERR|\
|
||||
SD_INT_COMPLETE)
|
||||
#define SD_CTL_STRIPE_MASK 0x3 /* stripe control mask */
|
||||
|
||||
/* SD_STS */
|
||||
#define SD_STS_FIFO_READY 0x20 /* FIFO ready */
|
||||
|
|
|
@ -66,6 +66,7 @@ enum {
|
|||
#define AC_VERB_GET_CONFIG_DEFAULT 0x0f1c
|
||||
/* f20: AFG/MFG */
|
||||
#define AC_VERB_GET_SUBSYSTEM_ID 0x0f20
|
||||
#define AC_VERB_GET_STRIPE_CONTROL 0x0f24
|
||||
#define AC_VERB_GET_CVT_CHAN_COUNT 0x0f2d
|
||||
#define AC_VERB_GET_HDMI_DIP_SIZE 0x0f2e
|
||||
#define AC_VERB_GET_HDMI_ELDD 0x0f2f
|
||||
|
@ -110,6 +111,7 @@ enum {
|
|||
#define AC_VERB_SET_CONFIG_DEFAULT_BYTES_3 0x71f
|
||||
#define AC_VERB_SET_EAPD 0x788
|
||||
#define AC_VERB_SET_CODEC_RESET 0x7ff
|
||||
#define AC_VERB_SET_STRIPE_CONTROL 0x724
|
||||
#define AC_VERB_SET_CVT_CHAN_COUNT 0x72d
|
||||
#define AC_VERB_SET_HDMI_DIP_INDEX 0x730
|
||||
#define AC_VERB_SET_HDMI_DIP_DATA 0x731
|
||||
|
|
|
@ -539,6 +539,9 @@ void snd_hdac_stream_sync(struct hdac_stream *azx_dev, bool start,
|
|||
unsigned int streams);
|
||||
void snd_hdac_stream_timecounter_init(struct hdac_stream *azx_dev,
|
||||
unsigned int streams);
|
||||
int snd_hdac_get_stream_stripe_ctl(struct hdac_bus *bus,
|
||||
struct snd_pcm_substream *substream);
|
||||
|
||||
/*
|
||||
* macros for easy use
|
||||
*/
|
||||
|
|
|
@ -82,7 +82,6 @@ struct snd_info_entry {
|
|||
struct snd_info_entry_ops *ops;
|
||||
} c;
|
||||
struct snd_info_entry *parent;
|
||||
struct snd_card *card;
|
||||
struct module *module;
|
||||
void *private_data;
|
||||
void (*private_free)(struct snd_info_entry *entry);
|
||||
|
@ -160,6 +159,13 @@ static inline void snd_info_set_text_ops(struct snd_info_entry *entry,
|
|||
entry->c.text.read = read;
|
||||
}
|
||||
|
||||
int snd_card_rw_proc_new(struct snd_card *card, const char *name,
|
||||
void *private_data,
|
||||
void (*read)(struct snd_info_entry *,
|
||||
struct snd_info_buffer *),
|
||||
void (*write)(struct snd_info_entry *entry,
|
||||
struct snd_info_buffer *buffer));
|
||||
|
||||
int snd_info_check_reserved_words(const char *str);
|
||||
|
||||
#else
|
||||
|
@ -189,10 +195,38 @@ static inline int snd_card_proc_new(struct snd_card *card, const char *name,
|
|||
static inline void snd_info_set_text_ops(struct snd_info_entry *entry __attribute__((unused)),
|
||||
void *private_data,
|
||||
void (*read)(struct snd_info_entry *, struct snd_info_buffer *)) {}
|
||||
static inline int snd_card_rw_proc_new(struct snd_card *card, const char *name,
|
||||
void *private_data,
|
||||
void (*read)(struct snd_info_entry *,
|
||||
struct snd_info_buffer *),
|
||||
void (*write)(struct snd_info_entry *entry,
|
||||
struct snd_info_buffer *buffer))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static inline int snd_info_check_reserved_words(const char *str) { return 1; }
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* snd_card_ro_proc_new - Create a read-only text proc file entry for the card
|
||||
* @card: the card instance
|
||||
* @name: the file name
|
||||
* @private_data: the arbitrary private data
|
||||
* @read: the read callback
|
||||
*
|
||||
* This proc file entry will be registered via snd_card_register() call, and
|
||||
* it will be removed automatically at the card removal, too.
|
||||
*/
|
||||
static inline int
|
||||
snd_card_ro_proc_new(struct snd_card *card, const char *name,
|
||||
void *private_data,
|
||||
void (*read)(struct snd_info_entry *,
|
||||
struct snd_info_buffer *))
|
||||
{
|
||||
return snd_card_rw_proc_new(card, name, private_data, read, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* OSS info part
|
||||
*/
|
||||
|
|
|
@ -37,7 +37,6 @@ struct snd_dma_device {
|
|||
};
|
||||
|
||||
#define snd_dma_pci_data(pci) (&(pci)->dev)
|
||||
#define snd_dma_isa_data() NULL
|
||||
#define snd_dma_continuous_data(x) ((struct device *)(__force unsigned long)(x))
|
||||
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include <linux/mm.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/pm_qos.h>
|
||||
#include <linux/refcount.h>
|
||||
|
||||
#define snd_pcm_substream_chip(substream) ((substream)->private_data)
|
||||
#define snd_pcm_chip(pcm) ((pcm)->private_data)
|
||||
|
@ -439,7 +440,7 @@ struct snd_pcm_group { /* keep linked substreams */
|
|||
spinlock_t lock;
|
||||
struct mutex mutex;
|
||||
struct list_head substreams;
|
||||
int count;
|
||||
refcount_t refs;
|
||||
};
|
||||
|
||||
struct pid;
|
||||
|
@ -470,7 +471,6 @@ struct snd_pcm_substream {
|
|||
struct snd_pcm_group self_group; /* fake group for non linked substream (with substream lock inside) */
|
||||
struct snd_pcm_group *group; /* pointer to current group */
|
||||
/* -- assigned files -- */
|
||||
void *file;
|
||||
int ref_count;
|
||||
atomic_t mmap_count;
|
||||
unsigned int f_flags;
|
||||
|
@ -482,15 +482,6 @@ struct snd_pcm_substream {
|
|||
#endif
|
||||
#ifdef CONFIG_SND_VERBOSE_PROCFS
|
||||
struct snd_info_entry *proc_root;
|
||||
struct snd_info_entry *proc_info_entry;
|
||||
struct snd_info_entry *proc_hw_params_entry;
|
||||
struct snd_info_entry *proc_sw_params_entry;
|
||||
struct snd_info_entry *proc_status_entry;
|
||||
struct snd_info_entry *proc_prealloc_entry;
|
||||
struct snd_info_entry *proc_prealloc_max_entry;
|
||||
#ifdef CONFIG_SND_PCM_XRUN_DEBUG
|
||||
struct snd_info_entry *proc_xrun_injection_entry;
|
||||
#endif
|
||||
#endif /* CONFIG_SND_VERBOSE_PROCFS */
|
||||
/* misc flags */
|
||||
unsigned int hw_opened: 1;
|
||||
|
@ -512,10 +503,8 @@ struct snd_pcm_str {
|
|||
#endif
|
||||
#ifdef CONFIG_SND_VERBOSE_PROCFS
|
||||
struct snd_info_entry *proc_root;
|
||||
struct snd_info_entry *proc_info_entry;
|
||||
#ifdef CONFIG_SND_PCM_XRUN_DEBUG
|
||||
unsigned int xrun_debug; /* 0 = disabled, 1 = verbose, 2 = stacktrace */
|
||||
struct snd_info_entry *proc_xrun_debug_entry;
|
||||
#endif
|
||||
#endif
|
||||
struct snd_kcontrol *chmap_kctl; /* channel-mapping controls */
|
||||
|
@ -538,6 +527,7 @@ struct snd_pcm {
|
|||
void (*private_free) (struct snd_pcm *pcm);
|
||||
bool internal; /* pcm is for internal use only */
|
||||
bool nonatomic; /* whole PCM operations are in non-atomic context */
|
||||
bool no_device_suspend; /* don't invoke device PM suspend */
|
||||
#if IS_ENABLED(CONFIG_SND_PCM_OSS)
|
||||
struct snd_pcm_oss oss;
|
||||
#endif
|
||||
|
@ -581,13 +571,8 @@ int snd_pcm_stop(struct snd_pcm_substream *substream, snd_pcm_state_t status);
|
|||
int snd_pcm_drain_done(struct snd_pcm_substream *substream);
|
||||
int snd_pcm_stop_xrun(struct snd_pcm_substream *substream);
|
||||
#ifdef CONFIG_PM
|
||||
int snd_pcm_suspend(struct snd_pcm_substream *substream);
|
||||
int snd_pcm_suspend_all(struct snd_pcm *pcm);
|
||||
#else
|
||||
static inline int snd_pcm_suspend(struct snd_pcm_substream *substream)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static inline int snd_pcm_suspend_all(struct snd_pcm *pcm)
|
||||
{
|
||||
return 0;
|
||||
|
@ -1200,12 +1185,12 @@ static inline void snd_pcm_gettime(struct snd_pcm_runtime *runtime,
|
|||
* Memory
|
||||
*/
|
||||
|
||||
int snd_pcm_lib_preallocate_free(struct snd_pcm_substream *substream);
|
||||
int snd_pcm_lib_preallocate_free_for_all(struct snd_pcm *pcm);
|
||||
int snd_pcm_lib_preallocate_pages(struct snd_pcm_substream *substream,
|
||||
void snd_pcm_lib_preallocate_free(struct snd_pcm_substream *substream);
|
||||
void snd_pcm_lib_preallocate_free_for_all(struct snd_pcm *pcm);
|
||||
void snd_pcm_lib_preallocate_pages(struct snd_pcm_substream *substream,
|
||||
int type, struct device *data,
|
||||
size_t size, size_t max);
|
||||
int snd_pcm_lib_preallocate_pages_for_all(struct snd_pcm *pcm,
|
||||
void snd_pcm_lib_preallocate_pages_for_all(struct snd_pcm *pcm,
|
||||
int type, void *data,
|
||||
size_t size, size_t max);
|
||||
int snd_pcm_lib_malloc_pages(struct snd_pcm_substream *substream, size_t size);
|
||||
|
|
|
@ -75,7 +75,7 @@ void asoc_simple_card_clk_disable(struct asoc_simple_dai *dai);
|
|||
&dai_link->codec_dai_name, \
|
||||
list_name, cells_name, NULL)
|
||||
#define asoc_simple_card_parse_platform(node, dai_link, list_name, cells_name) \
|
||||
asoc_simple_card_parse_dai(node, dai_link->platform, \
|
||||
asoc_simple_card_parse_dai(node, dai_link->platforms, \
|
||||
&dai_link->platform_of_node, \
|
||||
NULL, list_name, cells_name, NULL)
|
||||
int asoc_simple_card_parse_dai(struct device_node *node,
|
||||
|
@ -108,7 +108,7 @@ int asoc_simple_card_parse_graph_dai(struct device_node *ep,
|
|||
int asoc_simple_card_init_dai(struct snd_soc_dai *dai,
|
||||
struct asoc_simple_dai *simple_dai);
|
||||
|
||||
int asoc_simple_card_canonicalize_dailink(struct snd_soc_dai_link *dai_link);
|
||||
void asoc_simple_card_canonicalize_platform(struct snd_soc_dai_link *dai_link);
|
||||
void asoc_simple_card_canonicalize_cpu(struct snd_soc_dai_link *dai_link,
|
||||
int is_single_links);
|
||||
|
||||
|
|
|
@ -22,20 +22,37 @@ struct snd_soc_acpi_package_context {
|
|||
#define SND_ACPI_I2C_ID_LEN (4 + ACPI_ID_LEN + 3 + 1)
|
||||
|
||||
#if IS_ENABLED(CONFIG_ACPI)
|
||||
/* acpi match */
|
||||
struct snd_soc_acpi_mach *
|
||||
snd_soc_acpi_find_machine(struct snd_soc_acpi_mach *machines);
|
||||
|
||||
bool snd_soc_acpi_find_package_from_hid(const u8 hid[ACPI_ID_LEN],
|
||||
struct snd_soc_acpi_package_context *ctx);
|
||||
|
||||
/* check all codecs */
|
||||
struct snd_soc_acpi_mach *snd_soc_acpi_codec_list(void *arg);
|
||||
|
||||
#else
|
||||
/* acpi match */
|
||||
static inline struct snd_soc_acpi_mach *
|
||||
snd_soc_acpi_find_machine(struct snd_soc_acpi_mach *machines)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
snd_soc_acpi_find_package_from_hid(const u8 hid[ACPI_ID_LEN],
|
||||
struct snd_soc_acpi_package_context *ctx)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* acpi match */
|
||||
struct snd_soc_acpi_mach *
|
||||
snd_soc_acpi_find_machine(struct snd_soc_acpi_mach *machines);
|
||||
/* check all codecs */
|
||||
static inline struct snd_soc_acpi_mach *snd_soc_acpi_codec_list(void *arg)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* snd_soc_acpi_mach_params: interface for machine driver configuration
|
||||
|
@ -69,9 +86,6 @@ struct snd_soc_acpi_mach_params {
|
|||
* is not constant since this field may be updated at run-time
|
||||
* @sof_fw_filename: Sound Open Firmware file name, if enabled
|
||||
* @sof_tplg_filename: Sound Open Firmware topology file name, if enabled
|
||||
* @asoc_plat_name: ASoC platform name, used for binding machine drivers
|
||||
* if non NULL
|
||||
* @new_mach_data: machine driver private data fixup
|
||||
*/
|
||||
/* Descriptor for SST ASoC machine driver */
|
||||
struct snd_soc_acpi_mach {
|
||||
|
@ -85,8 +99,6 @@ struct snd_soc_acpi_mach {
|
|||
struct snd_soc_acpi_mach_params mach_params;
|
||||
const char *sof_fw_filename;
|
||||
const char *sof_tplg_filename;
|
||||
const char *asoc_plat_name;
|
||||
struct platform_device * (*new_mach_data)(void *pdata);
|
||||
};
|
||||
|
||||
#define SND_SOC_ACPI_MAX_CODECS 3
|
||||
|
@ -105,7 +117,4 @@ struct snd_soc_acpi_codecs {
|
|||
u8 codecs[SND_SOC_ACPI_MAX_CODECS][ACPI_ID_LEN];
|
||||
};
|
||||
|
||||
/* check all codecs */
|
||||
struct snd_soc_acpi_mach *snd_soc_acpi_codec_list(void *arg);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -214,21 +214,21 @@ struct device;
|
|||
.event_flags = SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD}
|
||||
|
||||
/* stream domain */
|
||||
#define SND_SOC_DAPM_AIF_IN(wname, stname, wslot, wreg, wshift, winvert) \
|
||||
#define SND_SOC_DAPM_AIF_IN(wname, stname, wchan, wreg, wshift, winvert) \
|
||||
{ .id = snd_soc_dapm_aif_in, .name = wname, .sname = stname, \
|
||||
SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), }
|
||||
#define SND_SOC_DAPM_AIF_IN_E(wname, stname, wslot, wreg, wshift, winvert, \
|
||||
.channel = wchan, SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), }
|
||||
#define SND_SOC_DAPM_AIF_IN_E(wname, stname, wchan, wreg, wshift, winvert, \
|
||||
wevent, wflags) \
|
||||
{ .id = snd_soc_dapm_aif_in, .name = wname, .sname = stname, \
|
||||
SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
|
||||
.channel = wchan, SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
|
||||
.event = wevent, .event_flags = wflags }
|
||||
#define SND_SOC_DAPM_AIF_OUT(wname, stname, wslot, wreg, wshift, winvert) \
|
||||
#define SND_SOC_DAPM_AIF_OUT(wname, stname, wchan, wreg, wshift, winvert) \
|
||||
{ .id = snd_soc_dapm_aif_out, .name = wname, .sname = stname, \
|
||||
SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), }
|
||||
#define SND_SOC_DAPM_AIF_OUT_E(wname, stname, wslot, wreg, wshift, winvert, \
|
||||
.channel = wchan, SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), }
|
||||
#define SND_SOC_DAPM_AIF_OUT_E(wname, stname, wchan, wreg, wshift, winvert, \
|
||||
wevent, wflags) \
|
||||
{ .id = snd_soc_dapm_aif_out, .name = wname, .sname = stname, \
|
||||
SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
|
||||
.channel = wchan, SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
|
||||
.event = wevent, .event_flags = wflags }
|
||||
#define SND_SOC_DAPM_DAC(wname, stname, wreg, wshift, winvert) \
|
||||
{ .id = snd_soc_dapm_dac, .name = wname, .sname = stname, \
|
||||
|
@ -407,6 +407,10 @@ int snd_soc_dapm_new_dai_widgets(struct snd_soc_dapm_context *dapm,
|
|||
int snd_soc_dapm_link_dai_widgets(struct snd_soc_card *card);
|
||||
void snd_soc_dapm_connect_dai_link_widgets(struct snd_soc_card *card);
|
||||
|
||||
int snd_soc_dapm_update_dai(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *dai);
|
||||
|
||||
/* dapm path setup */
|
||||
int snd_soc_dapm_new_widgets(struct snd_soc_card *card);
|
||||
void snd_soc_dapm_free(struct snd_soc_dapm_context *dapm);
|
||||
|
@ -519,6 +523,9 @@ enum snd_soc_dapm_type {
|
|||
snd_soc_dapm_asrc, /* DSP/CODEC ASRC component */
|
||||
snd_soc_dapm_encoder, /* FW/SW audio encoder component */
|
||||
snd_soc_dapm_decoder, /* FW/SW audio decoder component */
|
||||
|
||||
/* Don't edit below this line */
|
||||
SND_SOC_DAPM_TYPE_COUNT
|
||||
};
|
||||
|
||||
enum snd_soc_dapm_subclass {
|
||||
|
@ -540,6 +547,8 @@ struct snd_soc_dapm_route {
|
|||
/* Note: currently only supported for links where source is a supply */
|
||||
int (*connected)(struct snd_soc_dapm_widget *source,
|
||||
struct snd_soc_dapm_widget *sink);
|
||||
|
||||
struct snd_soc_dobj dobj;
|
||||
};
|
||||
|
||||
/* dapm audio path between two widgets */
|
||||
|
@ -625,6 +634,8 @@ struct snd_soc_dapm_widget {
|
|||
int endpoints[2];
|
||||
|
||||
struct clk *clk;
|
||||
|
||||
int channel;
|
||||
};
|
||||
|
||||
struct snd_soc_dapm_update {
|
||||
|
|
|
@ -38,12 +38,14 @@ struct snd_soc_dapm_route;
|
|||
enum snd_soc_dobj_type {
|
||||
SND_SOC_DOBJ_NONE = 0, /* object is not dynamic */
|
||||
SND_SOC_DOBJ_MIXER,
|
||||
SND_SOC_DOBJ_ENUM,
|
||||
SND_SOC_DOBJ_BYTES,
|
||||
SND_SOC_DOBJ_PCM,
|
||||
SND_SOC_DOBJ_DAI_LINK,
|
||||
SND_SOC_DOBJ_CODEC_LINK,
|
||||
SND_SOC_DOBJ_ENUM,
|
||||
SND_SOC_DOBJ_GRAPH,
|
||||
SND_SOC_DOBJ_WIDGET,
|
||||
SND_SOC_DOBJ_DAI_LINK,
|
||||
SND_SOC_DOBJ_PCM,
|
||||
SND_SOC_DOBJ_CODEC_LINK,
|
||||
SND_SOC_DOBJ_BACKEND_LINK,
|
||||
};
|
||||
|
||||
/* dynamic control object */
|
||||
|
|
|
@ -802,6 +802,9 @@ struct snd_soc_component_driver {
|
|||
int probe_order;
|
||||
int remove_order;
|
||||
|
||||
/* signal if the module handling the component cannot be removed */
|
||||
unsigned int ignore_module_refcount:1;
|
||||
|
||||
/* bits */
|
||||
unsigned int idle_bias_on:1;
|
||||
unsigned int suspend_bias_off:1;
|
||||
|
@ -891,6 +894,18 @@ struct snd_soc_dai_link {
|
|||
/* config - must be set by machine driver */
|
||||
const char *name; /* Codec name */
|
||||
const char *stream_name; /* Stream name */
|
||||
|
||||
/*
|
||||
* cpu_name
|
||||
* cpu_of_node
|
||||
* cpu_dai_name
|
||||
*
|
||||
* These are legacy style, and will be replaced to
|
||||
* modern style (= snd_soc_dai_link_component) in the future,
|
||||
* but, not yet supported so far.
|
||||
* If modern style was supported for CPU, all driver will switch
|
||||
* to use it, and, legacy style code will be removed from ALSA SoC.
|
||||
*/
|
||||
/*
|
||||
* You MAY specify the link's CPU-side device, either by device name,
|
||||
* or by DT/OF node, but not both. If this information is omitted,
|
||||
|
@ -906,6 +921,19 @@ struct snd_soc_dai_link {
|
|||
* only, which only works well when that device exposes a single DAI.
|
||||
*/
|
||||
const char *cpu_dai_name;
|
||||
|
||||
/*
|
||||
* codec_name
|
||||
* codec_of_node
|
||||
* codec_dai_name
|
||||
*
|
||||
* These are legacy style, it will be converted to modern style
|
||||
* (= snd_soc_dai_link_component) automatically in soc-core
|
||||
* if driver is using legacy style.
|
||||
* Driver shouldn't use both legacy and modern style in the same time.
|
||||
* If modern style was supported for CPU, all driver will switch
|
||||
* to use it, and, legacy style code will be removed from ALSA SoC.
|
||||
*/
|
||||
/*
|
||||
* You MUST specify the link's codec, either by device name, or by
|
||||
* DT/OF node, but not both.
|
||||
|
@ -918,6 +946,17 @@ struct snd_soc_dai_link {
|
|||
struct snd_soc_dai_link_component *codecs;
|
||||
unsigned int num_codecs;
|
||||
|
||||
/*
|
||||
* platform_name
|
||||
* platform_of_node
|
||||
*
|
||||
* These are legacy style, it will be converted to modern style
|
||||
* (= snd_soc_dai_link_component) automatically in soc-core
|
||||
* if driver is using legacy style.
|
||||
* Driver shouldn't use both legacy and modern style in the same time.
|
||||
* If modern style was supported for CPU, all driver will switch
|
||||
* to use it, and, legacy style code will be removed from ALSA SoC.
|
||||
*/
|
||||
/*
|
||||
* You MAY specify the link's platform/PCM/DMA driver, either by
|
||||
* device name, or by DT/OF node, but not both. Some forms of link
|
||||
|
@ -925,7 +964,8 @@ struct snd_soc_dai_link {
|
|||
*/
|
||||
const char *platform_name;
|
||||
struct device_node *platform_of_node;
|
||||
struct snd_soc_dai_link_component *platform;
|
||||
struct snd_soc_dai_link_component *platforms;
|
||||
unsigned int num_platforms;
|
||||
|
||||
int id; /* optional ID for machine driver link identification */
|
||||
|
||||
|
@ -1543,6 +1583,37 @@ struct snd_soc_dai *snd_soc_card_get_codec_dai(struct snd_soc_card *card,
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static inline
|
||||
int snd_soc_fixup_dai_links_platform_name(struct snd_soc_card *card,
|
||||
const char *platform_name)
|
||||
{
|
||||
struct snd_soc_dai_link *dai_link;
|
||||
const char *name;
|
||||
int i;
|
||||
|
||||
if (!platform_name) /* nothing to do */
|
||||
return 0;
|
||||
|
||||
/* set platform name for each dailink */
|
||||
for_each_card_prelinks(card, i, dai_link) {
|
||||
name = devm_kstrdup(card->dev, platform_name, GFP_KERNEL);
|
||||
if (!name)
|
||||
return -ENOMEM;
|
||||
|
||||
if (dai_link->platforms)
|
||||
/* only single platform is supported for now */
|
||||
dai_link->platforms->name = name;
|
||||
else
|
||||
/*
|
||||
* legacy mode, this case will be removed when all
|
||||
* derivers are switched to modern style dai_link.
|
||||
*/
|
||||
dai_link->platform_name = name;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
extern struct dentry *snd_soc_debugfs_root;
|
||||
#endif
|
||||
|
|
|
@ -84,7 +84,7 @@ ac97_of_get_child_device(struct ac97_controller *ac97_ctrl, int idx,
|
|||
if ((idx != of_property_read_u32(node, "reg", ®)) ||
|
||||
!of_device_is_compatible(node, compat))
|
||||
continue;
|
||||
return of_node_get(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
|
|
|
@ -82,6 +82,7 @@ static struct device_node *get_gpio(char *name,
|
|||
if (altname && (strcmp(audio_gpio, altname) == 0))
|
||||
break;
|
||||
}
|
||||
of_node_put(gpio);
|
||||
/* still not found, assume not there */
|
||||
if (!np)
|
||||
return NULL;
|
||||
|
|
|
@ -380,10 +380,6 @@ static int i2sbus_suspend(struct macio_dev* dev, pm_message_t state)
|
|||
int err, ret = 0;
|
||||
|
||||
list_for_each_entry(i2sdev, &control->list, item) {
|
||||
/* Notify Alsa */
|
||||
/* Suspend PCM streams */
|
||||
snd_pcm_suspend_all(i2sdev->sound.pcm);
|
||||
|
||||
/* Notify codecs */
|
||||
list_for_each_entry(cii, &i2sdev->sound.codec_list, list) {
|
||||
err = 0;
|
||||
|
|
|
@ -757,7 +757,6 @@ static int aaci_do_suspend(struct snd_card *card)
|
|||
{
|
||||
struct aaci *aaci = card->private_data;
|
||||
snd_power_change_state(card, SNDRV_CTL_POWER_D3cold);
|
||||
snd_pcm_suspend_all(aaci->pcm);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -942,7 +941,8 @@ static int aaci_init_pcm(struct aaci *aaci)
|
|||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &aaci_playback_ops);
|
||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &aaci_capture_ops);
|
||||
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
|
||||
NULL, 0, 64 * 1024);
|
||||
aaci->card->dev,
|
||||
0, 64 * 1024);
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
|
|
@ -124,7 +124,6 @@ static int pxa2xx_ac97_do_suspend(struct snd_card *card)
|
|||
pxa2xx_audio_ops_t *platform_ops = card->dev->platform_data;
|
||||
|
||||
snd_power_change_state(card, SNDRV_CTL_POWER_D3cold);
|
||||
snd_pcm_suspend_all(pxa2xx_ac97_pcm);
|
||||
snd_ac97_suspend(pxa2xx_ac97_ac97);
|
||||
if (platform_ops && platform_ops->suspend)
|
||||
platform_ops->suspend(platform_ops->priv);
|
||||
|
|
|
@ -603,11 +603,9 @@ static int atmel_ac97c_pcm_new(struct atmel_ac97c *chip)
|
|||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &atmel_ac97_capture_ops);
|
||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &atmel_ac97_playback_ops);
|
||||
|
||||
retval = snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
|
||||
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
|
||||
&chip->pdev->dev, hw.periods_min * hw.period_bytes_min,
|
||||
hw.buffer_bytes_max);
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
pcm->private_data = chip;
|
||||
pcm->info_flags = 0;
|
||||
|
|
|
@ -1015,22 +1015,13 @@ static int snd_compress_proc_init(struct snd_compr *compr)
|
|||
if (!entry)
|
||||
return -ENOMEM;
|
||||
entry->mode = S_IFDIR | 0555;
|
||||
if (snd_info_register(entry) < 0) {
|
||||
snd_info_free_entry(entry);
|
||||
return -ENOMEM;
|
||||
}
|
||||
compr->proc_root = entry;
|
||||
|
||||
entry = snd_info_create_card_entry(compr->card, "info",
|
||||
compr->proc_root);
|
||||
if (entry) {
|
||||
if (entry)
|
||||
snd_info_set_text_ops(entry, compr,
|
||||
snd_compress_proc_info_read);
|
||||
if (snd_info_register(entry) < 0) {
|
||||
snd_info_free_entry(entry);
|
||||
entry = NULL;
|
||||
}
|
||||
}
|
||||
compr->proc_info_entry = entry;
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -463,11 +463,12 @@ static struct snd_info_entry *create_subdir(struct module *mod,
|
|||
}
|
||||
|
||||
static struct snd_info_entry *
|
||||
snd_info_create_entry(const char *name, struct snd_info_entry *parent);
|
||||
snd_info_create_entry(const char *name, struct snd_info_entry *parent,
|
||||
struct module *module);
|
||||
|
||||
int __init snd_info_init(void)
|
||||
{
|
||||
snd_proc_root = snd_info_create_entry("asound", NULL);
|
||||
snd_proc_root = snd_info_create_entry("asound", NULL, THIS_MODULE);
|
||||
if (!snd_proc_root)
|
||||
return -ENOMEM;
|
||||
snd_proc_root->mode = S_IFDIR | 0555;
|
||||
|
@ -503,6 +504,14 @@ int __exit snd_info_done(void)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void snd_card_id_read(struct snd_info_entry *entry,
|
||||
struct snd_info_buffer *buffer)
|
||||
{
|
||||
struct snd_card *card = entry->private_data;
|
||||
|
||||
snd_iprintf(buffer, "%s\n", card->id);
|
||||
}
|
||||
|
||||
/*
|
||||
* create a card proc file
|
||||
* called from init.c
|
||||
|
@ -520,28 +529,8 @@ int snd_info_card_create(struct snd_card *card)
|
|||
if (!entry)
|
||||
return -ENOMEM;
|
||||
card->proc_root = entry;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* register all pending info entries */
|
||||
static int snd_info_register_recursive(struct snd_info_entry *entry)
|
||||
{
|
||||
struct snd_info_entry *p;
|
||||
int err;
|
||||
|
||||
if (!entry->p) {
|
||||
err = snd_info_register(entry);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
list_for_each_entry(p, &entry->children, list) {
|
||||
err = snd_info_register_recursive(p);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return snd_card_ro_proc_new(card, "id", card, snd_card_id_read);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -557,7 +546,7 @@ int snd_info_card_register(struct snd_card *card)
|
|||
if (snd_BUG_ON(!card))
|
||||
return -ENXIO;
|
||||
|
||||
err = snd_info_register_recursive(card->proc_root);
|
||||
err = snd_info_register(card->proc_root);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
|
@ -705,7 +694,8 @@ EXPORT_SYMBOL(snd_info_get_str);
|
|||
* Return: The pointer of the new instance, or %NULL on failure.
|
||||
*/
|
||||
static struct snd_info_entry *
|
||||
snd_info_create_entry(const char *name, struct snd_info_entry *parent)
|
||||
snd_info_create_entry(const char *name, struct snd_info_entry *parent,
|
||||
struct module *module)
|
||||
{
|
||||
struct snd_info_entry *entry;
|
||||
entry = kzalloc(sizeof(*entry), GFP_KERNEL);
|
||||
|
@ -722,6 +712,7 @@ snd_info_create_entry(const char *name, struct snd_info_entry *parent)
|
|||
INIT_LIST_HEAD(&entry->children);
|
||||
INIT_LIST_HEAD(&entry->list);
|
||||
entry->parent = parent;
|
||||
entry->module = module;
|
||||
if (parent)
|
||||
list_add_tail(&entry->list, &parent->children);
|
||||
return entry;
|
||||
|
@ -741,10 +732,9 @@ struct snd_info_entry *snd_info_create_module_entry(struct module * module,
|
|||
const char *name,
|
||||
struct snd_info_entry *parent)
|
||||
{
|
||||
struct snd_info_entry *entry = snd_info_create_entry(name, parent);
|
||||
if (entry)
|
||||
entry->module = module;
|
||||
return entry;
|
||||
if (!parent)
|
||||
parent = snd_proc_root;
|
||||
return snd_info_create_entry(name, parent, module);
|
||||
}
|
||||
EXPORT_SYMBOL(snd_info_create_module_entry);
|
||||
|
||||
|
@ -762,12 +752,9 @@ struct snd_info_entry *snd_info_create_card_entry(struct snd_card *card,
|
|||
const char *name,
|
||||
struct snd_info_entry * parent)
|
||||
{
|
||||
struct snd_info_entry *entry = snd_info_create_entry(name, parent);
|
||||
if (entry) {
|
||||
entry->module = card->module;
|
||||
entry->card = card;
|
||||
}
|
||||
return entry;
|
||||
if (!parent)
|
||||
parent = card->proc_root;
|
||||
return snd_info_create_entry(name, parent, card->module);
|
||||
}
|
||||
EXPORT_SYMBOL(snd_info_create_card_entry);
|
||||
|
||||
|
@ -813,15 +800,7 @@ void snd_info_free_entry(struct snd_info_entry * entry)
|
|||
}
|
||||
EXPORT_SYMBOL(snd_info_free_entry);
|
||||
|
||||
/**
|
||||
* snd_info_register - register the info entry
|
||||
* @entry: the info entry
|
||||
*
|
||||
* Registers the proc info entry.
|
||||
*
|
||||
* Return: Zero if successful, or a negative error code on failure.
|
||||
*/
|
||||
int snd_info_register(struct snd_info_entry * entry)
|
||||
static int __snd_info_register(struct snd_info_entry *entry)
|
||||
{
|
||||
struct proc_dir_entry *root, *p = NULL;
|
||||
|
||||
|
@ -829,6 +808,8 @@ int snd_info_register(struct snd_info_entry * entry)
|
|||
return -ENXIO;
|
||||
root = entry->parent == NULL ? snd_proc_root->p : entry->parent->p;
|
||||
mutex_lock(&info_mutex);
|
||||
if (entry->p || !root)
|
||||
goto unlock;
|
||||
if (S_ISDIR(entry->mode)) {
|
||||
p = proc_mkdir_mode(entry->name, entry->mode, root);
|
||||
if (!p) {
|
||||
|
@ -850,11 +831,73 @@ int snd_info_register(struct snd_info_entry * entry)
|
|||
proc_set_size(p, entry->size);
|
||||
}
|
||||
entry->p = p;
|
||||
unlock:
|
||||
mutex_unlock(&info_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_info_register - register the info entry
|
||||
* @entry: the info entry
|
||||
*
|
||||
* Registers the proc info entry.
|
||||
* The all children entries are registered recursively.
|
||||
*
|
||||
* Return: Zero if successful, or a negative error code on failure.
|
||||
*/
|
||||
int snd_info_register(struct snd_info_entry *entry)
|
||||
{
|
||||
struct snd_info_entry *p;
|
||||
int err;
|
||||
|
||||
if (!entry->p) {
|
||||
err = __snd_info_register(entry);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
list_for_each_entry(p, &entry->children, list) {
|
||||
err = snd_info_register(p);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(snd_info_register);
|
||||
|
||||
/**
|
||||
* snd_card_rw_proc_new - Create a read/write text proc file entry for the card
|
||||
* @card: the card instance
|
||||
* @name: the file name
|
||||
* @private_data: the arbitrary private data
|
||||
* @read: the read callback
|
||||
* @write: the write callback, NULL for read-only
|
||||
*
|
||||
* This proc file entry will be registered via snd_card_register() call, and
|
||||
* it will be removed automatically at the card removal, too.
|
||||
*/
|
||||
int snd_card_rw_proc_new(struct snd_card *card, const char *name,
|
||||
void *private_data,
|
||||
void (*read)(struct snd_info_entry *,
|
||||
struct snd_info_buffer *),
|
||||
void (*write)(struct snd_info_entry *entry,
|
||||
struct snd_info_buffer *buffer))
|
||||
{
|
||||
struct snd_info_entry *entry;
|
||||
|
||||
entry = snd_info_create_card_entry(card, name, card->proc_root);
|
||||
if (!entry)
|
||||
return -ENOMEM;
|
||||
snd_info_set_text_ops(entry, private_data, read);
|
||||
if (write) {
|
||||
entry->mode |= 0200;
|
||||
entry->c.text.write = write;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_card_rw_proc_new);
|
||||
|
||||
/*
|
||||
|
||||
*/
|
||||
|
|
|
@ -100,31 +100,6 @@ int (*snd_mixer_oss_notify_callback)(struct snd_card *card, int free_flag);
|
|||
EXPORT_SYMBOL(snd_mixer_oss_notify_callback);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_SND_PROC_FS
|
||||
static void snd_card_id_read(struct snd_info_entry *entry,
|
||||
struct snd_info_buffer *buffer)
|
||||
{
|
||||
snd_iprintf(buffer, "%s\n", entry->card->id);
|
||||
}
|
||||
|
||||
static int init_info_for_card(struct snd_card *card)
|
||||
{
|
||||
struct snd_info_entry *entry;
|
||||
|
||||
entry = snd_info_create_card_entry(card, "id", card->proc_root);
|
||||
if (!entry) {
|
||||
dev_dbg(card->dev, "unable to create card entry\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
entry->c.text.read = snd_card_id_read;
|
||||
card->proc_id = entry;
|
||||
|
||||
return snd_info_card_register(card);
|
||||
}
|
||||
#else /* !CONFIG_SND_PROC_FS */
|
||||
#define init_info_for_card(card)
|
||||
#endif
|
||||
|
||||
static int check_empty_slot(struct module *module, int slot)
|
||||
{
|
||||
return !slots[slot] || !*slots[slot];
|
||||
|
@ -491,7 +466,6 @@ static int snd_card_do_free(struct snd_card *card)
|
|||
snd_device_free_all(card);
|
||||
if (card->private_free)
|
||||
card->private_free(card);
|
||||
snd_info_free_entry(card->proc_id);
|
||||
if (snd_info_card_free(card) < 0) {
|
||||
dev_warn(card->dev, "unable to free card info\n");
|
||||
/* Not fatal error */
|
||||
|
@ -795,7 +769,10 @@ int snd_card_register(struct snd_card *card)
|
|||
}
|
||||
snd_cards[card->number] = card;
|
||||
mutex_unlock(&snd_card_mutex);
|
||||
init_info_for_card(card);
|
||||
err = snd_info_card_register(card);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
#if IS_ENABLED(CONFIG_SND_MIXER_OSS)
|
||||
if (snd_mixer_oss_notify_callback)
|
||||
snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_REGISTER);
|
||||
|
|
|
@ -182,6 +182,8 @@ int snd_dma_alloc_pages(int type, struct device *device, size_t size,
|
|||
return -ENXIO;
|
||||
if (WARN_ON(!dmab))
|
||||
return -ENXIO;
|
||||
if (WARN_ON(!device))
|
||||
return -EINVAL;
|
||||
|
||||
dmab->dev.type = type;
|
||||
dmab->dev.dev = device;
|
||||
|
|
|
@ -2427,7 +2427,6 @@ static int snd_pcm_oss_open_file(struct file *file,
|
|||
}
|
||||
|
||||
pcm_oss_file->streams[idx] = substream;
|
||||
substream->file = pcm_oss_file;
|
||||
snd_pcm_oss_init_substream(substream, &setup[idx], minor);
|
||||
}
|
||||
|
||||
|
|
163
sound/core/pcm.c
163
sound/core/pcm.c
|
@ -528,52 +528,44 @@ static int snd_pcm_stream_proc_init(struct snd_pcm_str *pstr)
|
|||
if (!entry)
|
||||
return -ENOMEM;
|
||||
entry->mode = S_IFDIR | 0555;
|
||||
if (snd_info_register(entry) < 0) {
|
||||
snd_info_free_entry(entry);
|
||||
return -ENOMEM;
|
||||
}
|
||||
pstr->proc_root = entry;
|
||||
entry = snd_info_create_card_entry(pcm->card, "info", pstr->proc_root);
|
||||
if (entry) {
|
||||
if (entry)
|
||||
snd_info_set_text_ops(entry, pstr, snd_pcm_stream_proc_info_read);
|
||||
if (snd_info_register(entry) < 0) {
|
||||
snd_info_free_entry(entry);
|
||||
entry = NULL;
|
||||
}
|
||||
}
|
||||
pstr->proc_info_entry = entry;
|
||||
|
||||
#ifdef CONFIG_SND_PCM_XRUN_DEBUG
|
||||
entry = snd_info_create_card_entry(pcm->card, "xrun_debug",
|
||||
pstr->proc_root);
|
||||
if (entry) {
|
||||
entry->c.text.read = snd_pcm_xrun_debug_read;
|
||||
snd_info_set_text_ops(entry, pstr, snd_pcm_xrun_debug_read);
|
||||
entry->c.text.write = snd_pcm_xrun_debug_write;
|
||||
entry->mode |= 0200;
|
||||
entry->private_data = pstr;
|
||||
if (snd_info_register(entry) < 0) {
|
||||
snd_info_free_entry(entry);
|
||||
entry = NULL;
|
||||
}
|
||||
}
|
||||
pstr->proc_xrun_debug_entry = entry;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_pcm_stream_proc_done(struct snd_pcm_str *pstr)
|
||||
{
|
||||
#ifdef CONFIG_SND_PCM_XRUN_DEBUG
|
||||
snd_info_free_entry(pstr->proc_xrun_debug_entry);
|
||||
pstr->proc_xrun_debug_entry = NULL;
|
||||
#endif
|
||||
snd_info_free_entry(pstr->proc_info_entry);
|
||||
pstr->proc_info_entry = NULL;
|
||||
snd_info_free_entry(pstr->proc_root);
|
||||
pstr->proc_root = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_info_entry *
|
||||
create_substream_info_entry(struct snd_pcm_substream *substream,
|
||||
const char *name,
|
||||
void (*read)(struct snd_info_entry *,
|
||||
struct snd_info_buffer *))
|
||||
{
|
||||
struct snd_info_entry *entry;
|
||||
|
||||
entry = snd_info_create_card_entry(substream->pcm->card, name,
|
||||
substream->proc_root);
|
||||
if (entry)
|
||||
snd_info_set_text_ops(entry, substream, read);
|
||||
return entry;
|
||||
}
|
||||
|
||||
static int snd_pcm_substream_proc_init(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_info_entry *entry;
|
||||
|
@ -588,101 +580,61 @@ static int snd_pcm_substream_proc_init(struct snd_pcm_substream *substream)
|
|||
if (!entry)
|
||||
return -ENOMEM;
|
||||
entry->mode = S_IFDIR | 0555;
|
||||
if (snd_info_register(entry) < 0) {
|
||||
snd_info_free_entry(entry);
|
||||
return -ENOMEM;
|
||||
}
|
||||
substream->proc_root = entry;
|
||||
entry = snd_info_create_card_entry(card, "info", substream->proc_root);
|
||||
if (entry) {
|
||||
snd_info_set_text_ops(entry, substream,
|
||||
snd_pcm_substream_proc_info_read);
|
||||
if (snd_info_register(entry) < 0) {
|
||||
snd_info_free_entry(entry);
|
||||
entry = NULL;
|
||||
}
|
||||
}
|
||||
substream->proc_info_entry = entry;
|
||||
entry = snd_info_create_card_entry(card, "hw_params",
|
||||
substream->proc_root);
|
||||
if (entry) {
|
||||
snd_info_set_text_ops(entry, substream,
|
||||
snd_pcm_substream_proc_hw_params_read);
|
||||
if (snd_info_register(entry) < 0) {
|
||||
snd_info_free_entry(entry);
|
||||
entry = NULL;
|
||||
}
|
||||
}
|
||||
substream->proc_hw_params_entry = entry;
|
||||
entry = snd_info_create_card_entry(card, "sw_params",
|
||||
substream->proc_root);
|
||||
if (entry) {
|
||||
snd_info_set_text_ops(entry, substream,
|
||||
snd_pcm_substream_proc_sw_params_read);
|
||||
if (snd_info_register(entry) < 0) {
|
||||
snd_info_free_entry(entry);
|
||||
entry = NULL;
|
||||
}
|
||||
}
|
||||
substream->proc_sw_params_entry = entry;
|
||||
entry = snd_info_create_card_entry(card, "status",
|
||||
substream->proc_root);
|
||||
if (entry) {
|
||||
snd_info_set_text_ops(entry, substream,
|
||||
snd_pcm_substream_proc_status_read);
|
||||
if (snd_info_register(entry) < 0) {
|
||||
snd_info_free_entry(entry);
|
||||
entry = NULL;
|
||||
}
|
||||
}
|
||||
substream->proc_status_entry = entry;
|
||||
|
||||
create_substream_info_entry(substream, "info",
|
||||
snd_pcm_substream_proc_info_read);
|
||||
create_substream_info_entry(substream, "hw_params",
|
||||
snd_pcm_substream_proc_hw_params_read);
|
||||
create_substream_info_entry(substream, "sw_params",
|
||||
snd_pcm_substream_proc_sw_params_read);
|
||||
create_substream_info_entry(substream, "status",
|
||||
snd_pcm_substream_proc_status_read);
|
||||
|
||||
#ifdef CONFIG_SND_PCM_XRUN_DEBUG
|
||||
entry = snd_info_create_card_entry(card, "xrun_injection",
|
||||
substream->proc_root);
|
||||
entry = create_substream_info_entry(substream, "xrun_injection", NULL);
|
||||
if (entry) {
|
||||
entry->private_data = substream;
|
||||
entry->c.text.read = NULL;
|
||||
entry->c.text.write = snd_pcm_xrun_injection_write;
|
||||
entry->mode = S_IFREG | 0200;
|
||||
if (snd_info_register(entry) < 0) {
|
||||
snd_info_free_entry(entry);
|
||||
entry = NULL;
|
||||
}
|
||||
}
|
||||
substream->proc_xrun_injection_entry = entry;
|
||||
#endif /* CONFIG_SND_PCM_XRUN_DEBUG */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_pcm_substream_proc_done(struct snd_pcm_substream *substream)
|
||||
{
|
||||
snd_info_free_entry(substream->proc_info_entry);
|
||||
substream->proc_info_entry = NULL;
|
||||
snd_info_free_entry(substream->proc_hw_params_entry);
|
||||
substream->proc_hw_params_entry = NULL;
|
||||
snd_info_free_entry(substream->proc_sw_params_entry);
|
||||
substream->proc_sw_params_entry = NULL;
|
||||
snd_info_free_entry(substream->proc_status_entry);
|
||||
substream->proc_status_entry = NULL;
|
||||
#ifdef CONFIG_SND_PCM_XRUN_DEBUG
|
||||
snd_info_free_entry(substream->proc_xrun_injection_entry);
|
||||
substream->proc_xrun_injection_entry = NULL;
|
||||
#endif
|
||||
snd_info_free_entry(substream->proc_root);
|
||||
substream->proc_root = NULL;
|
||||
return 0;
|
||||
}
|
||||
#else /* !CONFIG_SND_VERBOSE_PROCFS */
|
||||
static inline int snd_pcm_stream_proc_init(struct snd_pcm_str *pstr) { return 0; }
|
||||
static inline int snd_pcm_stream_proc_done(struct snd_pcm_str *pstr) { return 0; }
|
||||
static inline int snd_pcm_substream_proc_init(struct snd_pcm_substream *substream) { return 0; }
|
||||
static inline int snd_pcm_substream_proc_done(struct snd_pcm_substream *substream) { return 0; }
|
||||
#endif /* CONFIG_SND_VERBOSE_PROCFS */
|
||||
|
||||
static const struct attribute_group *pcm_dev_attr_groups[];
|
||||
|
||||
/*
|
||||
* PM callbacks: we need to deal only with suspend here, as the resume is
|
||||
* triggered either from user-space or the driver's resume callback
|
||||
*/
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int do_pcm_suspend(struct device *dev)
|
||||
{
|
||||
struct snd_pcm_str *pstr = container_of(dev, struct snd_pcm_str, dev);
|
||||
|
||||
if (!pstr->pcm->no_device_suspend)
|
||||
snd_pcm_suspend_all(pstr->pcm);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct dev_pm_ops pcm_dev_pm_ops = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(do_pcm_suspend, NULL)
|
||||
};
|
||||
|
||||
/* device type for PCM -- basically only for passing PM callbacks */
|
||||
static const struct device_type pcm_dev_type = {
|
||||
.name = "pcm",
|
||||
.pm = &pcm_dev_pm_ops,
|
||||
};
|
||||
|
||||
/**
|
||||
* snd_pcm_new_stream - create a new PCM stream
|
||||
* @pcm: the pcm instance
|
||||
|
@ -713,6 +665,7 @@ int snd_pcm_new_stream(struct snd_pcm *pcm, int stream, int substream_count)
|
|||
|
||||
snd_device_initialize(&pstr->dev, pcm->card);
|
||||
pstr->dev.groups = pcm_dev_attr_groups;
|
||||
pstr->dev.type = &pcm_dev_type;
|
||||
dev_set_name(&pstr->dev, "pcmC%iD%i%c", pcm->card->number, pcm->device,
|
||||
stream == SNDRV_PCM_STREAM_PLAYBACK ? 'p' : 'c');
|
||||
|
||||
|
@ -753,9 +706,7 @@ int snd_pcm_new_stream(struct snd_pcm *pcm, int stream, int substream_count)
|
|||
}
|
||||
}
|
||||
substream->group = &substream->self_group;
|
||||
spin_lock_init(&substream->self_group.lock);
|
||||
mutex_init(&substream->self_group.mutex);
|
||||
INIT_LIST_HEAD(&substream->self_group.substreams);
|
||||
snd_pcm_group_init(&substream->self_group);
|
||||
list_add_tail(&substream->link_list, &substream->self_group.substreams);
|
||||
atomic_set(&substream->mmap_count, 0);
|
||||
prev = substream;
|
||||
|
@ -885,15 +836,17 @@ static void snd_pcm_free_stream(struct snd_pcm_str * pstr)
|
|||
#if IS_ENABLED(CONFIG_SND_PCM_OSS)
|
||||
struct snd_pcm_oss_setup *setup, *setupn;
|
||||
#endif
|
||||
|
||||
/* free all proc files under the stream */
|
||||
snd_pcm_stream_proc_done(pstr);
|
||||
|
||||
substream = pstr->substream;
|
||||
while (substream) {
|
||||
substream_next = substream->next;
|
||||
snd_pcm_timer_done(substream);
|
||||
snd_pcm_substream_proc_done(substream);
|
||||
kfree(substream);
|
||||
substream = substream_next;
|
||||
}
|
||||
snd_pcm_stream_proc_done(pstr);
|
||||
#if IS_ENABLED(CONFIG_SND_PCM_OSS)
|
||||
for (setup = pstr->oss.setup_list; setup; setup = setupn) {
|
||||
setupn = setup->next;
|
||||
|
|
|
@ -2176,6 +2176,10 @@ snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream,
|
|||
if (runtime->status->state == SNDRV_PCM_STATE_RUNNING)
|
||||
snd_pcm_update_hw_ptr(substream);
|
||||
|
||||
/*
|
||||
* If size < start_threshold, wait indefinitely. Another
|
||||
* thread may start capture
|
||||
*/
|
||||
if (!is_playback &&
|
||||
runtime->status->state == SNDRV_PCM_STATE_PREPARED &&
|
||||
size >= runtime->start_threshold) {
|
||||
|
@ -2214,9 +2218,8 @@ snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream,
|
|||
if (frames > cont)
|
||||
frames = cont;
|
||||
if (snd_BUG_ON(!frames)) {
|
||||
runtime->twake = 0;
|
||||
snd_pcm_stream_unlock_irq(substream);
|
||||
return -EINVAL;
|
||||
err = -EINVAL;
|
||||
goto _end_unlock;
|
||||
}
|
||||
snd_pcm_stream_unlock_irq(substream);
|
||||
err = writer(substream, appl_ofs, data, offset, frames,
|
||||
|
|
|
@ -66,5 +66,6 @@ static inline void snd_pcm_timer_done(struct snd_pcm_substream *substream) {}
|
|||
#endif
|
||||
|
||||
void __snd_pcm_xrun(struct snd_pcm_substream *substream);
|
||||
void snd_pcm_group_init(struct snd_pcm_group *group);
|
||||
|
||||
#endif /* __SOUND_CORE_PCM_LOCAL_H */
|
||||
|
|
|
@ -87,19 +87,10 @@ static void snd_pcm_lib_preallocate_dma_free(struct snd_pcm_substream *substream
|
|||
* @substream: the pcm substream instance
|
||||
*
|
||||
* Releases the pre-allocated buffer of the given substream.
|
||||
*
|
||||
* Return: Zero if successful, or a negative error code on failure.
|
||||
*/
|
||||
int snd_pcm_lib_preallocate_free(struct snd_pcm_substream *substream)
|
||||
void snd_pcm_lib_preallocate_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
snd_pcm_lib_preallocate_dma_free(substream);
|
||||
#ifdef CONFIG_SND_VERBOSE_PROCFS
|
||||
snd_info_free_entry(substream->proc_prealloc_max_entry);
|
||||
substream->proc_prealloc_max_entry = NULL;
|
||||
snd_info_free_entry(substream->proc_prealloc_entry);
|
||||
substream->proc_prealloc_entry = NULL;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -107,10 +98,8 @@ int snd_pcm_lib_preallocate_free(struct snd_pcm_substream *substream)
|
|||
* @pcm: the pcm instance
|
||||
*
|
||||
* Releases all the pre-allocated buffers on the given pcm.
|
||||
*
|
||||
* Return: Zero if successful, or a negative error code on failure.
|
||||
*/
|
||||
int snd_pcm_lib_preallocate_free_for_all(struct snd_pcm *pcm)
|
||||
void snd_pcm_lib_preallocate_free_for_all(struct snd_pcm *pcm)
|
||||
{
|
||||
struct snd_pcm_substream *substream;
|
||||
int stream;
|
||||
|
@ -118,7 +107,6 @@ int snd_pcm_lib_preallocate_free_for_all(struct snd_pcm *pcm)
|
|||
for (stream = 0; stream < 2; stream++)
|
||||
for (substream = pcm->streams[stream].substream; substream; substream = substream->next)
|
||||
snd_pcm_lib_preallocate_free(substream);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(snd_pcm_lib_preallocate_free_for_all);
|
||||
|
||||
|
@ -198,26 +186,19 @@ static inline void preallocate_info_init(struct snd_pcm_substream *substream)
|
|||
{
|
||||
struct snd_info_entry *entry;
|
||||
|
||||
if ((entry = snd_info_create_card_entry(substream->pcm->card, "prealloc", substream->proc_root)) != NULL) {
|
||||
entry->c.text.read = snd_pcm_lib_preallocate_proc_read;
|
||||
entry = snd_info_create_card_entry(substream->pcm->card, "prealloc",
|
||||
substream->proc_root);
|
||||
if (entry) {
|
||||
snd_info_set_text_ops(entry, substream,
|
||||
snd_pcm_lib_preallocate_proc_read);
|
||||
entry->c.text.write = snd_pcm_lib_preallocate_proc_write;
|
||||
entry->mode |= 0200;
|
||||
entry->private_data = substream;
|
||||
if (snd_info_register(entry) < 0) {
|
||||
snd_info_free_entry(entry);
|
||||
entry = NULL;
|
||||
}
|
||||
}
|
||||
substream->proc_prealloc_entry = entry;
|
||||
if ((entry = snd_info_create_card_entry(substream->pcm->card, "prealloc_max", substream->proc_root)) != NULL) {
|
||||
entry->c.text.read = snd_pcm_lib_preallocate_max_proc_read;
|
||||
entry->private_data = substream;
|
||||
if (snd_info_register(entry) < 0) {
|
||||
snd_info_free_entry(entry);
|
||||
entry = NULL;
|
||||
}
|
||||
}
|
||||
substream->proc_prealloc_max_entry = entry;
|
||||
entry = snd_info_create_card_entry(substream->pcm->card, "prealloc_max",
|
||||
substream->proc_root);
|
||||
if (entry)
|
||||
snd_info_set_text_ops(entry, substream,
|
||||
snd_pcm_lib_preallocate_max_proc_read);
|
||||
}
|
||||
|
||||
#else /* !CONFIG_SND_VERBOSE_PROCFS */
|
||||
|
@ -227,7 +208,7 @@ static inline void preallocate_info_init(struct snd_pcm_substream *substream)
|
|||
/*
|
||||
* pre-allocate the buffer and create a proc file for the substream
|
||||
*/
|
||||
static int snd_pcm_lib_preallocate_pages1(struct snd_pcm_substream *substream,
|
||||
static void snd_pcm_lib_preallocate_pages1(struct snd_pcm_substream *substream,
|
||||
size_t size, size_t max)
|
||||
{
|
||||
|
||||
|
@ -238,7 +219,6 @@ static int snd_pcm_lib_preallocate_pages1(struct snd_pcm_substream *substream,
|
|||
substream->buffer_bytes_max = substream->dma_buffer.bytes;
|
||||
substream->dma_max = max;
|
||||
preallocate_info_init(substream);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
@ -251,16 +231,14 @@ static int snd_pcm_lib_preallocate_pages1(struct snd_pcm_substream *substream,
|
|||
* @max: the max. allowed pre-allocation size
|
||||
*
|
||||
* Do pre-allocation for the given DMA buffer type.
|
||||
*
|
||||
* Return: Zero if successful, or a negative error code on failure.
|
||||
*/
|
||||
int snd_pcm_lib_preallocate_pages(struct snd_pcm_substream *substream,
|
||||
void snd_pcm_lib_preallocate_pages(struct snd_pcm_substream *substream,
|
||||
int type, struct device *data,
|
||||
size_t size, size_t max)
|
||||
{
|
||||
substream->dma_buffer.dev.type = type;
|
||||
substream->dma_buffer.dev.dev = data;
|
||||
return snd_pcm_lib_preallocate_pages1(substream, size, max);
|
||||
snd_pcm_lib_preallocate_pages1(substream, size, max);
|
||||
}
|
||||
EXPORT_SYMBOL(snd_pcm_lib_preallocate_pages);
|
||||
|
||||
|
@ -274,21 +252,17 @@ EXPORT_SYMBOL(snd_pcm_lib_preallocate_pages);
|
|||
*
|
||||
* Do pre-allocation to all substreams of the given pcm for the
|
||||
* specified DMA type.
|
||||
*
|
||||
* Return: Zero if successful, or a negative error code on failure.
|
||||
*/
|
||||
int snd_pcm_lib_preallocate_pages_for_all(struct snd_pcm *pcm,
|
||||
void snd_pcm_lib_preallocate_pages_for_all(struct snd_pcm *pcm,
|
||||
int type, void *data,
|
||||
size_t size, size_t max)
|
||||
{
|
||||
struct snd_pcm_substream *substream;
|
||||
int stream, err;
|
||||
int stream;
|
||||
|
||||
for (stream = 0; stream < 2; stream++)
|
||||
for (substream = pcm->streams[stream].substream; substream; substream = substream->next)
|
||||
if ((err = snd_pcm_lib_preallocate_pages(substream, type, data, size, max)) < 0)
|
||||
return err;
|
||||
return 0;
|
||||
snd_pcm_lib_preallocate_pages(substream, type, data, size, max);
|
||||
}
|
||||
EXPORT_SYMBOL(snd_pcm_lib_preallocate_pages_for_all);
|
||||
|
||||
|
|
|
@ -85,71 +85,30 @@ static int snd_pcm_open(struct file *file, struct snd_pcm *pcm, int stream);
|
|||
*
|
||||
*/
|
||||
|
||||
static DEFINE_RWLOCK(snd_pcm_link_rwlock);
|
||||
static DECLARE_RWSEM(snd_pcm_link_rwsem);
|
||||
|
||||
/* Writer in rwsem may block readers even during its waiting in queue,
|
||||
* and this may lead to a deadlock when the code path takes read sem
|
||||
* twice (e.g. one in snd_pcm_action_nonatomic() and another in
|
||||
* snd_pcm_stream_lock()). As a (suboptimal) workaround, let writer to
|
||||
* sleep until all the readers are completed without blocking by writer.
|
||||
*/
|
||||
static inline void down_write_nonfifo(struct rw_semaphore *lock)
|
||||
void snd_pcm_group_init(struct snd_pcm_group *group)
|
||||
{
|
||||
while (!down_write_trylock(lock))
|
||||
msleep(1);
|
||||
spin_lock_init(&group->lock);
|
||||
mutex_init(&group->mutex);
|
||||
INIT_LIST_HEAD(&group->substreams);
|
||||
refcount_set(&group->refs, 0);
|
||||
}
|
||||
|
||||
#define PCM_LOCK_DEFAULT 0
|
||||
#define PCM_LOCK_IRQ 1
|
||||
#define PCM_LOCK_IRQSAVE 2
|
||||
|
||||
static unsigned long __snd_pcm_stream_lock_mode(struct snd_pcm_substream *substream,
|
||||
unsigned int mode)
|
||||
{
|
||||
unsigned long flags = 0;
|
||||
if (substream->pcm->nonatomic) {
|
||||
down_read_nested(&snd_pcm_link_rwsem, SINGLE_DEPTH_NESTING);
|
||||
mutex_lock(&substream->self_group.mutex);
|
||||
} else {
|
||||
switch (mode) {
|
||||
case PCM_LOCK_DEFAULT:
|
||||
read_lock(&snd_pcm_link_rwlock);
|
||||
break;
|
||||
case PCM_LOCK_IRQ:
|
||||
read_lock_irq(&snd_pcm_link_rwlock);
|
||||
break;
|
||||
case PCM_LOCK_IRQSAVE:
|
||||
read_lock_irqsave(&snd_pcm_link_rwlock, flags);
|
||||
break;
|
||||
}
|
||||
spin_lock(&substream->self_group.lock);
|
||||
}
|
||||
return flags;
|
||||
/* define group lock helpers */
|
||||
#define DEFINE_PCM_GROUP_LOCK(action, mutex_action) \
|
||||
static void snd_pcm_group_ ## action(struct snd_pcm_group *group, bool nonatomic) \
|
||||
{ \
|
||||
if (nonatomic) \
|
||||
mutex_ ## mutex_action(&group->mutex); \
|
||||
else \
|
||||
spin_ ## action(&group->lock); \
|
||||
}
|
||||
|
||||
static void __snd_pcm_stream_unlock_mode(struct snd_pcm_substream *substream,
|
||||
unsigned int mode, unsigned long flags)
|
||||
{
|
||||
if (substream->pcm->nonatomic) {
|
||||
mutex_unlock(&substream->self_group.mutex);
|
||||
up_read(&snd_pcm_link_rwsem);
|
||||
} else {
|
||||
spin_unlock(&substream->self_group.lock);
|
||||
|
||||
switch (mode) {
|
||||
case PCM_LOCK_DEFAULT:
|
||||
read_unlock(&snd_pcm_link_rwlock);
|
||||
break;
|
||||
case PCM_LOCK_IRQ:
|
||||
read_unlock_irq(&snd_pcm_link_rwlock);
|
||||
break;
|
||||
case PCM_LOCK_IRQSAVE:
|
||||
read_unlock_irqrestore(&snd_pcm_link_rwlock, flags);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
DEFINE_PCM_GROUP_LOCK(lock, lock);
|
||||
DEFINE_PCM_GROUP_LOCK(unlock, unlock);
|
||||
DEFINE_PCM_GROUP_LOCK(lock_irq, lock);
|
||||
DEFINE_PCM_GROUP_LOCK(unlock_irq, unlock);
|
||||
|
||||
/**
|
||||
* snd_pcm_stream_lock - Lock the PCM stream
|
||||
|
@ -161,7 +120,7 @@ static void __snd_pcm_stream_unlock_mode(struct snd_pcm_substream *substream,
|
|||
*/
|
||||
void snd_pcm_stream_lock(struct snd_pcm_substream *substream)
|
||||
{
|
||||
__snd_pcm_stream_lock_mode(substream, PCM_LOCK_DEFAULT);
|
||||
snd_pcm_group_lock(&substream->self_group, substream->pcm->nonatomic);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_pcm_stream_lock);
|
||||
|
||||
|
@ -173,7 +132,7 @@ EXPORT_SYMBOL_GPL(snd_pcm_stream_lock);
|
|||
*/
|
||||
void snd_pcm_stream_unlock(struct snd_pcm_substream *substream)
|
||||
{
|
||||
__snd_pcm_stream_unlock_mode(substream, PCM_LOCK_DEFAULT, 0);
|
||||
snd_pcm_group_unlock(&substream->self_group, substream->pcm->nonatomic);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_pcm_stream_unlock);
|
||||
|
||||
|
@ -187,7 +146,8 @@ EXPORT_SYMBOL_GPL(snd_pcm_stream_unlock);
|
|||
*/
|
||||
void snd_pcm_stream_lock_irq(struct snd_pcm_substream *substream)
|
||||
{
|
||||
__snd_pcm_stream_lock_mode(substream, PCM_LOCK_IRQ);
|
||||
snd_pcm_group_lock_irq(&substream->self_group,
|
||||
substream->pcm->nonatomic);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_pcm_stream_lock_irq);
|
||||
|
||||
|
@ -199,13 +159,19 @@ EXPORT_SYMBOL_GPL(snd_pcm_stream_lock_irq);
|
|||
*/
|
||||
void snd_pcm_stream_unlock_irq(struct snd_pcm_substream *substream)
|
||||
{
|
||||
__snd_pcm_stream_unlock_mode(substream, PCM_LOCK_IRQ, 0);
|
||||
snd_pcm_group_unlock_irq(&substream->self_group,
|
||||
substream->pcm->nonatomic);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_pcm_stream_unlock_irq);
|
||||
|
||||
unsigned long _snd_pcm_stream_lock_irqsave(struct snd_pcm_substream *substream)
|
||||
{
|
||||
return __snd_pcm_stream_lock_mode(substream, PCM_LOCK_IRQSAVE);
|
||||
unsigned long flags = 0;
|
||||
if (substream->pcm->nonatomic)
|
||||
mutex_lock(&substream->self_group.mutex);
|
||||
else
|
||||
spin_lock_irqsave(&substream->self_group.lock, flags);
|
||||
return flags;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(_snd_pcm_stream_lock_irqsave);
|
||||
|
||||
|
@ -219,7 +185,10 @@ EXPORT_SYMBOL_GPL(_snd_pcm_stream_lock_irqsave);
|
|||
void snd_pcm_stream_unlock_irqrestore(struct snd_pcm_substream *substream,
|
||||
unsigned long flags)
|
||||
{
|
||||
__snd_pcm_stream_unlock_mode(substream, PCM_LOCK_IRQSAVE, flags);
|
||||
if (substream->pcm->nonatomic)
|
||||
mutex_unlock(&substream->self_group.mutex);
|
||||
else
|
||||
spin_unlock_irqrestore(&substream->self_group.lock, flags);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_pcm_stream_unlock_irqrestore);
|
||||
|
||||
|
@ -1124,6 +1093,68 @@ static int snd_pcm_action_single(const struct action_ops *ops,
|
|||
return res;
|
||||
}
|
||||
|
||||
static void snd_pcm_group_assign(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_group *new_group)
|
||||
{
|
||||
substream->group = new_group;
|
||||
list_move(&substream->link_list, &new_group->substreams);
|
||||
}
|
||||
|
||||
/*
|
||||
* Unref and unlock the group, but keep the stream lock;
|
||||
* when the group becomes empty and no longer referred, destroy itself
|
||||
*/
|
||||
static void snd_pcm_group_unref(struct snd_pcm_group *group,
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
bool do_free;
|
||||
|
||||
if (!group)
|
||||
return;
|
||||
do_free = refcount_dec_and_test(&group->refs) &&
|
||||
list_empty(&group->substreams);
|
||||
snd_pcm_group_unlock(group, substream->pcm->nonatomic);
|
||||
if (do_free)
|
||||
kfree(group);
|
||||
}
|
||||
|
||||
/*
|
||||
* Lock the group inside a stream lock and reference it;
|
||||
* return the locked group object, or NULL if not linked
|
||||
*/
|
||||
static struct snd_pcm_group *
|
||||
snd_pcm_stream_group_ref(struct snd_pcm_substream *substream)
|
||||
{
|
||||
bool nonatomic = substream->pcm->nonatomic;
|
||||
struct snd_pcm_group *group;
|
||||
bool trylock;
|
||||
|
||||
for (;;) {
|
||||
if (!snd_pcm_stream_linked(substream))
|
||||
return NULL;
|
||||
group = substream->group;
|
||||
/* block freeing the group object */
|
||||
refcount_inc(&group->refs);
|
||||
|
||||
trylock = nonatomic ? mutex_trylock(&group->mutex) :
|
||||
spin_trylock(&group->lock);
|
||||
if (trylock)
|
||||
break; /* OK */
|
||||
|
||||
/* re-lock for avoiding ABBA deadlock */
|
||||
snd_pcm_stream_unlock(substream);
|
||||
snd_pcm_group_lock(group, nonatomic);
|
||||
snd_pcm_stream_lock(substream);
|
||||
|
||||
/* check the group again; the above opens a small race window */
|
||||
if (substream->group == group)
|
||||
break; /* OK */
|
||||
/* group changed, try again */
|
||||
snd_pcm_group_unref(group, substream);
|
||||
}
|
||||
return group;
|
||||
}
|
||||
|
||||
/*
|
||||
* Note: call with stream lock
|
||||
*/
|
||||
|
@ -1131,28 +1162,15 @@ static int snd_pcm_action(const struct action_ops *ops,
|
|||
struct snd_pcm_substream *substream,
|
||||
int state)
|
||||
{
|
||||
struct snd_pcm_group *group;
|
||||
int res;
|
||||
|
||||
if (!snd_pcm_stream_linked(substream))
|
||||
return snd_pcm_action_single(ops, substream, state);
|
||||
|
||||
if (substream->pcm->nonatomic) {
|
||||
if (!mutex_trylock(&substream->group->mutex)) {
|
||||
mutex_unlock(&substream->self_group.mutex);
|
||||
mutex_lock(&substream->group->mutex);
|
||||
mutex_lock(&substream->self_group.mutex);
|
||||
}
|
||||
group = snd_pcm_stream_group_ref(substream);
|
||||
if (group)
|
||||
res = snd_pcm_action_group(ops, substream, state, 1);
|
||||
mutex_unlock(&substream->group->mutex);
|
||||
} else {
|
||||
if (!spin_trylock(&substream->group->lock)) {
|
||||
spin_unlock(&substream->self_group.lock);
|
||||
spin_lock(&substream->group->lock);
|
||||
spin_lock(&substream->self_group.lock);
|
||||
}
|
||||
res = snd_pcm_action_group(ops, substream, state, 1);
|
||||
spin_unlock(&substream->group->lock);
|
||||
}
|
||||
else
|
||||
res = snd_pcm_action_single(ops, substream, state);
|
||||
snd_pcm_group_unref(group, substream);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
@ -1179,6 +1197,7 @@ static int snd_pcm_action_nonatomic(const struct action_ops *ops,
|
|||
{
|
||||
int res;
|
||||
|
||||
/* Guarantee the group members won't change during non-atomic action */
|
||||
down_read(&snd_pcm_link_rwsem);
|
||||
if (snd_pcm_stream_linked(substream))
|
||||
res = snd_pcm_action_group(ops, substream, state, 0);
|
||||
|
@ -1460,29 +1479,24 @@ static const struct action_ops snd_pcm_action_suspend = {
|
|||
.post_action = snd_pcm_post_suspend
|
||||
};
|
||||
|
||||
/**
|
||||
/*
|
||||
* snd_pcm_suspend - trigger SUSPEND to all linked streams
|
||||
* @substream: the PCM substream
|
||||
*
|
||||
* After this call, all streams are changed to SUSPENDED state.
|
||||
*
|
||||
* Return: Zero if successful (or @substream is %NULL), or a negative error
|
||||
* code.
|
||||
* Return: Zero if successful, or a negative error code.
|
||||
*/
|
||||
int snd_pcm_suspend(struct snd_pcm_substream *substream)
|
||||
static int snd_pcm_suspend(struct snd_pcm_substream *substream)
|
||||
{
|
||||
int err;
|
||||
unsigned long flags;
|
||||
|
||||
if (! substream)
|
||||
return 0;
|
||||
|
||||
snd_pcm_stream_lock_irqsave(substream, flags);
|
||||
err = snd_pcm_action(&snd_pcm_action_suspend, substream, 0);
|
||||
snd_pcm_stream_unlock_irqrestore(substream, flags);
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL(snd_pcm_suspend);
|
||||
|
||||
/**
|
||||
* snd_pcm_suspend_all - trigger SUSPEND to all substreams in the given pcm
|
||||
|
@ -1506,6 +1520,14 @@ int snd_pcm_suspend_all(struct snd_pcm *pcm)
|
|||
/* FIXME: the open/close code should lock this as well */
|
||||
if (substream->runtime == NULL)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* Skip BE dai link PCM's that are internal and may
|
||||
* not have their substream ops set.
|
||||
*/
|
||||
if (!substream->ops)
|
||||
continue;
|
||||
|
||||
err = snd_pcm_suspend(substream);
|
||||
if (err < 0 && err != -EBUSY)
|
||||
return err;
|
||||
|
@ -1792,8 +1814,6 @@ static const struct action_ops snd_pcm_action_drain_init = {
|
|||
.post_action = snd_pcm_post_drain_init
|
||||
};
|
||||
|
||||
static int snd_pcm_drop(struct snd_pcm_substream *substream);
|
||||
|
||||
/*
|
||||
* Drain the stream(s).
|
||||
* When the substream is linked, sync until the draining of all playback streams
|
||||
|
@ -1807,6 +1827,7 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream,
|
|||
struct snd_card *card;
|
||||
struct snd_pcm_runtime *runtime;
|
||||
struct snd_pcm_substream *s;
|
||||
struct snd_pcm_group *group;
|
||||
wait_queue_entry_t wait;
|
||||
int result = 0;
|
||||
int nonblock = 0;
|
||||
|
@ -1823,7 +1844,6 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream,
|
|||
} else if (substream->f_flags & O_NONBLOCK)
|
||||
nonblock = 1;
|
||||
|
||||
down_read(&snd_pcm_link_rwsem);
|
||||
snd_pcm_stream_lock_irq(substream);
|
||||
/* resume pause */
|
||||
if (runtime->status->state == SNDRV_PCM_STATE_PAUSED)
|
||||
|
@ -1848,6 +1868,7 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream,
|
|||
}
|
||||
/* find a substream to drain */
|
||||
to_check = NULL;
|
||||
group = snd_pcm_stream_group_ref(substream);
|
||||
snd_pcm_group_for_each_entry(s, substream) {
|
||||
if (s->stream != SNDRV_PCM_STREAM_PLAYBACK)
|
||||
continue;
|
||||
|
@ -1857,12 +1878,12 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream,
|
|||
break;
|
||||
}
|
||||
}
|
||||
snd_pcm_group_unref(group, substream);
|
||||
if (!to_check)
|
||||
break; /* all drained */
|
||||
init_waitqueue_entry(&wait, current);
|
||||
add_wait_queue(&to_check->sleep, &wait);
|
||||
snd_pcm_stream_unlock_irq(substream);
|
||||
up_read(&snd_pcm_link_rwsem);
|
||||
if (runtime->no_period_wakeup)
|
||||
tout = MAX_SCHEDULE_TIMEOUT;
|
||||
else {
|
||||
|
@ -1874,9 +1895,17 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream,
|
|||
tout = msecs_to_jiffies(tout * 1000);
|
||||
}
|
||||
tout = schedule_timeout_interruptible(tout);
|
||||
down_read(&snd_pcm_link_rwsem);
|
||||
|
||||
snd_pcm_stream_lock_irq(substream);
|
||||
remove_wait_queue(&to_check->sleep, &wait);
|
||||
group = snd_pcm_stream_group_ref(substream);
|
||||
snd_pcm_group_for_each_entry(s, substream) {
|
||||
if (s->runtime == to_check) {
|
||||
remove_wait_queue(&to_check->sleep, &wait);
|
||||
break;
|
||||
}
|
||||
}
|
||||
snd_pcm_group_unref(group, substream);
|
||||
|
||||
if (card->shutdown) {
|
||||
result = -ENODEV;
|
||||
break;
|
||||
|
@ -1896,7 +1925,6 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream,
|
|||
|
||||
unlock:
|
||||
snd_pcm_stream_unlock_irq(substream);
|
||||
up_read(&snd_pcm_link_rwsem);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -1935,13 +1963,19 @@ static int snd_pcm_drop(struct snd_pcm_substream *substream)
|
|||
static bool is_pcm_file(struct file *file)
|
||||
{
|
||||
struct inode *inode = file_inode(file);
|
||||
struct snd_pcm *pcm;
|
||||
unsigned int minor;
|
||||
|
||||
if (!S_ISCHR(inode->i_mode) || imajor(inode) != snd_major)
|
||||
return false;
|
||||
minor = iminor(inode);
|
||||
return snd_lookup_minor_data(minor, SNDRV_DEVICE_TYPE_PCM_PLAYBACK) ||
|
||||
snd_lookup_minor_data(minor, SNDRV_DEVICE_TYPE_PCM_CAPTURE);
|
||||
pcm = snd_lookup_minor_data(minor, SNDRV_DEVICE_TYPE_PCM_PLAYBACK);
|
||||
if (!pcm)
|
||||
pcm = snd_lookup_minor_data(minor, SNDRV_DEVICE_TYPE_PCM_CAPTURE);
|
||||
if (!pcm)
|
||||
return false;
|
||||
snd_card_unref(pcm->card);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1952,7 +1986,8 @@ static int snd_pcm_link(struct snd_pcm_substream *substream, int fd)
|
|||
int res = 0;
|
||||
struct snd_pcm_file *pcm_file;
|
||||
struct snd_pcm_substream *substream1;
|
||||
struct snd_pcm_group *group;
|
||||
struct snd_pcm_group *group, *target_group;
|
||||
bool nonatomic = substream->pcm->nonatomic;
|
||||
struct fd f = fdget(fd);
|
||||
|
||||
if (!f.file)
|
||||
|
@ -1963,13 +1998,14 @@ static int snd_pcm_link(struct snd_pcm_substream *substream, int fd)
|
|||
}
|
||||
pcm_file = f.file->private_data;
|
||||
substream1 = pcm_file->substream;
|
||||
group = kmalloc(sizeof(*group), GFP_KERNEL);
|
||||
group = kzalloc(sizeof(*group), GFP_KERNEL);
|
||||
if (!group) {
|
||||
res = -ENOMEM;
|
||||
goto _nolock;
|
||||
}
|
||||
down_write_nonfifo(&snd_pcm_link_rwsem);
|
||||
write_lock_irq(&snd_pcm_link_rwlock);
|
||||
snd_pcm_group_init(group);
|
||||
|
||||
down_write(&snd_pcm_link_rwsem);
|
||||
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN ||
|
||||
substream->runtime->status->state != substream1->runtime->status->state ||
|
||||
substream->pcm->nonatomic != substream1->pcm->nonatomic) {
|
||||
|
@ -1980,23 +2016,23 @@ static int snd_pcm_link(struct snd_pcm_substream *substream, int fd)
|
|||
res = -EALREADY;
|
||||
goto _end;
|
||||
}
|
||||
|
||||
snd_pcm_stream_lock_irq(substream);
|
||||
if (!snd_pcm_stream_linked(substream)) {
|
||||
substream->group = group;
|
||||
group = NULL;
|
||||
spin_lock_init(&substream->group->lock);
|
||||
mutex_init(&substream->group->mutex);
|
||||
INIT_LIST_HEAD(&substream->group->substreams);
|
||||
list_add_tail(&substream->link_list, &substream->group->substreams);
|
||||
substream->group->count = 1;
|
||||
snd_pcm_group_assign(substream, group);
|
||||
group = NULL; /* assigned, don't free this one below */
|
||||
}
|
||||
list_add_tail(&substream1->link_list, &substream->group->substreams);
|
||||
substream->group->count++;
|
||||
substream1->group = substream->group;
|
||||
target_group = substream->group;
|
||||
snd_pcm_stream_unlock_irq(substream);
|
||||
|
||||
snd_pcm_group_lock_irq(target_group, nonatomic);
|
||||
snd_pcm_stream_lock(substream1);
|
||||
snd_pcm_group_assign(substream1, target_group);
|
||||
snd_pcm_stream_unlock(substream1);
|
||||
snd_pcm_group_unlock_irq(target_group, nonatomic);
|
||||
_end:
|
||||
write_unlock_irq(&snd_pcm_link_rwlock);
|
||||
up_write(&snd_pcm_link_rwsem);
|
||||
_nolock:
|
||||
snd_card_unref(substream1->pcm->card);
|
||||
kfree(group);
|
||||
_badf:
|
||||
fdput(f);
|
||||
|
@ -2005,34 +2041,43 @@ static int snd_pcm_link(struct snd_pcm_substream *substream, int fd)
|
|||
|
||||
static void relink_to_local(struct snd_pcm_substream *substream)
|
||||
{
|
||||
substream->group = &substream->self_group;
|
||||
INIT_LIST_HEAD(&substream->self_group.substreams);
|
||||
list_add_tail(&substream->link_list, &substream->self_group.substreams);
|
||||
snd_pcm_stream_lock(substream);
|
||||
snd_pcm_group_assign(substream, &substream->self_group);
|
||||
snd_pcm_stream_unlock(substream);
|
||||
}
|
||||
|
||||
static int snd_pcm_unlink(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_substream *s;
|
||||
struct snd_pcm_group *group;
|
||||
bool nonatomic = substream->pcm->nonatomic;
|
||||
bool do_free = false;
|
||||
int res = 0;
|
||||
|
||||
down_write_nonfifo(&snd_pcm_link_rwsem);
|
||||
write_lock_irq(&snd_pcm_link_rwlock);
|
||||
down_write(&snd_pcm_link_rwsem);
|
||||
|
||||
if (!snd_pcm_stream_linked(substream)) {
|
||||
res = -EALREADY;
|
||||
goto _end;
|
||||
}
|
||||
list_del(&substream->link_list);
|
||||
substream->group->count--;
|
||||
if (substream->group->count == 1) { /* detach the last stream, too */
|
||||
snd_pcm_group_for_each_entry(s, substream) {
|
||||
relink_to_local(s);
|
||||
break;
|
||||
}
|
||||
kfree(substream->group);
|
||||
}
|
||||
|
||||
group = substream->group;
|
||||
snd_pcm_group_lock_irq(group, nonatomic);
|
||||
|
||||
relink_to_local(substream);
|
||||
|
||||
/* detach the last stream, too */
|
||||
if (list_is_singular(&group->substreams)) {
|
||||
relink_to_local(list_first_entry(&group->substreams,
|
||||
struct snd_pcm_substream,
|
||||
link_list));
|
||||
do_free = !refcount_read(&group->refs);
|
||||
}
|
||||
|
||||
snd_pcm_group_unlock_irq(group, nonatomic);
|
||||
if (do_free)
|
||||
kfree(group);
|
||||
|
||||
_end:
|
||||
write_unlock_irq(&snd_pcm_link_rwlock);
|
||||
up_write(&snd_pcm_link_rwsem);
|
||||
return res;
|
||||
}
|
||||
|
@ -2457,10 +2502,8 @@ static int snd_pcm_open_file(struct file *file,
|
|||
return -ENOMEM;
|
||||
}
|
||||
pcm_file->substream = substream;
|
||||
if (substream->ref_count == 1) {
|
||||
substream->file = pcm_file;
|
||||
if (substream->ref_count == 1)
|
||||
substream->pcm_release = pcm_release_private;
|
||||
}
|
||||
file->private_data = pcm_file;
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -1133,16 +1133,10 @@ static void print_cable_info(struct snd_info_entry *entry,
|
|||
static int loopback_proc_new(struct loopback *loopback, int cidx)
|
||||
{
|
||||
char name[32];
|
||||
struct snd_info_entry *entry;
|
||||
int err;
|
||||
|
||||
snprintf(name, sizeof(name), "cable#%d", cidx);
|
||||
err = snd_card_proc_new(loopback->card, name, &entry);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
snd_info_set_text_ops(entry, loopback, print_cable_info);
|
||||
return 0;
|
||||
return snd_card_ro_proc_new(loopback->card, name, loopback,
|
||||
print_cable_info);
|
||||
}
|
||||
|
||||
static int loopback_probe(struct platform_device *devptr)
|
||||
|
@ -1200,12 +1194,8 @@ static int loopback_remove(struct platform_device *devptr)
|
|||
static int loopback_suspend(struct device *pdev)
|
||||
{
|
||||
struct snd_card *card = dev_get_drvdata(pdev);
|
||||
struct loopback *loopback = card->private_data;
|
||||
|
||||
snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
|
||||
|
||||
snd_pcm_suspend_all(loopback->pcm[0]);
|
||||
snd_pcm_suspend_all(loopback->pcm[1]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -1037,14 +1037,8 @@ static void dummy_proc_write(struct snd_info_entry *entry,
|
|||
|
||||
static void dummy_proc_init(struct snd_dummy *chip)
|
||||
{
|
||||
struct snd_info_entry *entry;
|
||||
|
||||
if (!snd_card_proc_new(chip->card, "dummy_pcm", &entry)) {
|
||||
snd_info_set_text_ops(entry, chip, dummy_proc_read);
|
||||
entry->c.text.write = dummy_proc_write;
|
||||
entry->mode |= 0200;
|
||||
entry->private_data = chip;
|
||||
}
|
||||
snd_card_rw_proc_new(chip->card, "dummy_pcm", chip,
|
||||
dummy_proc_read, dummy_proc_write);
|
||||
}
|
||||
#else
|
||||
#define dummy_proc_init(x)
|
||||
|
@ -1138,10 +1132,8 @@ static int snd_dummy_remove(struct platform_device *devptr)
|
|||
static int snd_dummy_suspend(struct device *pdev)
|
||||
{
|
||||
struct snd_card *card = dev_get_drvdata(pdev);
|
||||
struct snd_dummy *dummy = card->private_data;
|
||||
|
||||
snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
|
||||
snd_pcm_suspend_all(dummy->pcm);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -114,10 +114,6 @@ int snd_opl4_create_proc(struct snd_opl4 *opl4)
|
|||
entry->c.ops = &snd_opl4_mem_proc_ops;
|
||||
entry->module = THIS_MODULE;
|
||||
entry->private_data = opl4;
|
||||
if (snd_info_register(entry) < 0) {
|
||||
snd_info_free_entry(entry);
|
||||
entry = NULL;
|
||||
}
|
||||
}
|
||||
opl4->proc_entry = entry;
|
||||
return 0;
|
||||
|
|
|
@ -197,7 +197,6 @@ static int pcsp_suspend(struct device *dev)
|
|||
{
|
||||
struct snd_pcsp *chip = dev_get_drvdata(dev);
|
||||
pcsp_stop_beep(chip);
|
||||
snd_pcm_suspend_all(chip->pcm);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -643,10 +643,7 @@ static void vx_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *b
|
|||
|
||||
static void vx_proc_init(struct vx_core *chip)
|
||||
{
|
||||
struct snd_info_entry *entry;
|
||||
|
||||
if (! snd_card_proc_new(chip->card, "vx-status", &entry))
|
||||
snd_info_set_text_ops(entry, chip, vx_proc_read);
|
||||
snd_card_ro_proc_new(chip->card, "vx-status", chip, vx_proc_read);
|
||||
}
|
||||
|
||||
|
||||
|
@ -732,12 +729,8 @@ EXPORT_SYMBOL(snd_vx_dsp_load);
|
|||
*/
|
||||
int snd_vx_suspend(struct vx_core *chip)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
snd_power_change_state(chip->card, SNDRV_CTL_POWER_D3hot);
|
||||
chip->chip_status |= VX_STAT_IN_SUSPEND;
|
||||
for (i = 0; i < chip->hw->num_codecs; i++)
|
||||
snd_pcm_suspend_all(chip->pcm[i]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -163,5 +163,6 @@ config SND_FIREFACE
|
|||
Say Y here to include support for RME fireface series.
|
||||
* Fireface 400
|
||||
* Fireface 800
|
||||
* Fireface UCX
|
||||
|
||||
endif # SND_FIREWIRE
|
||||
|
|
|
@ -448,7 +448,19 @@ static const struct ieee1394_device_id bebob_id_table[] = {
|
|||
/* Focusrite, SaffirePro 26 I/O */
|
||||
SND_BEBOB_DEV_ENTRY(VEN_FOCUSRITE, 0x00000003, &saffirepro_26_spec),
|
||||
/* Focusrite, SaffirePro 10 I/O */
|
||||
SND_BEBOB_DEV_ENTRY(VEN_FOCUSRITE, 0x00000006, &saffirepro_10_spec),
|
||||
{
|
||||
// The combination of vendor_id and model_id is the same as the
|
||||
// same as the one of Liquid Saffire 56.
|
||||
.match_flags = IEEE1394_MATCH_VENDOR_ID |
|
||||
IEEE1394_MATCH_MODEL_ID |
|
||||
IEEE1394_MATCH_SPECIFIER_ID |
|
||||
IEEE1394_MATCH_VERSION,
|
||||
.vendor_id = VEN_FOCUSRITE,
|
||||
.model_id = 0x000006,
|
||||
.specifier_id = 0x00a02d,
|
||||
.version = 0x010001,
|
||||
.driver_data = (kernel_ulong_t)&saffirepro_10_spec,
|
||||
},
|
||||
/* Focusrite, Saffire(no label and LE) */
|
||||
SND_BEBOB_DEV_ENTRY(VEN_FOCUSRITE, MODEL_FOCUSRITE_SAFFIRE_BOTH,
|
||||
&saffire_spec),
|
||||
|
|
|
@ -163,12 +163,8 @@ add_node(struct snd_bebob *bebob, struct snd_info_entry *root, const char *name,
|
|||
struct snd_info_entry *entry;
|
||||
|
||||
entry = snd_info_create_card_entry(bebob->card, name, root);
|
||||
if (entry == NULL)
|
||||
return;
|
||||
|
||||
snd_info_set_text_ops(entry, bebob, op);
|
||||
if (snd_info_register(entry) < 0)
|
||||
snd_info_free_entry(entry);
|
||||
if (entry)
|
||||
snd_info_set_text_ops(entry, bebob, op);
|
||||
}
|
||||
|
||||
void snd_bebob_proc_init(struct snd_bebob *bebob)
|
||||
|
@ -184,10 +180,6 @@ void snd_bebob_proc_init(struct snd_bebob *bebob)
|
|||
if (root == NULL)
|
||||
return;
|
||||
root->mode = S_IFDIR | 0555;
|
||||
if (snd_info_register(root) < 0) {
|
||||
snd_info_free_entry(root);
|
||||
return;
|
||||
}
|
||||
|
||||
add_node(bebob, root, "clock", proc_read_clock);
|
||||
add_node(bebob, root, "firmware", proc_read_hw_info);
|
||||
|
|
|
@ -285,12 +285,8 @@ static void add_node(struct snd_dice *dice, struct snd_info_entry *root,
|
|||
struct snd_info_entry *entry;
|
||||
|
||||
entry = snd_info_create_card_entry(dice->card, name, root);
|
||||
if (!entry)
|
||||
return;
|
||||
|
||||
snd_info_set_text_ops(entry, dice, op);
|
||||
if (snd_info_register(entry) < 0)
|
||||
snd_info_free_entry(entry);
|
||||
if (entry)
|
||||
snd_info_set_text_ops(entry, dice, op);
|
||||
}
|
||||
|
||||
void snd_dice_create_proc(struct snd_dice *dice)
|
||||
|
@ -306,10 +302,6 @@ void snd_dice_create_proc(struct snd_dice *dice)
|
|||
if (!root)
|
||||
return;
|
||||
root->mode = S_IFDIR | 0555;
|
||||
if (snd_info_register(root) < 0) {
|
||||
snd_info_free_entry(root);
|
||||
return;
|
||||
}
|
||||
|
||||
add_node(dice, root, "dice", dice_proc_read);
|
||||
add_node(dice, root, "formation", dice_proc_read_formation);
|
||||
|
|
|
@ -18,6 +18,7 @@ MODULE_LICENSE("GPL v2");
|
|||
#define OUI_ALESIS 0x000595
|
||||
#define OUI_MAUDIO 0x000d6c
|
||||
#define OUI_MYTEK 0x001ee8
|
||||
#define OUI_SSL 0x0050c2 // Actually ID reserved by IEEE.
|
||||
|
||||
#define DICE_CATEGORY_ID 0x04
|
||||
#define WEISS_CATEGORY_ID 0x00
|
||||
|
@ -196,7 +197,7 @@ static int dice_probe(struct fw_unit *unit,
|
|||
struct snd_dice *dice;
|
||||
int err;
|
||||
|
||||
if (!entry->driver_data) {
|
||||
if (!entry->driver_data && entry->vendor_id != OUI_SSL) {
|
||||
err = check_dice_category(unit);
|
||||
if (err < 0)
|
||||
return -ENODEV;
|
||||
|
@ -361,6 +362,15 @@ static const struct ieee1394_device_id dice_id_table[] = {
|
|||
.model_id = 0x000002,
|
||||
.driver_data = (kernel_ulong_t)snd_dice_detect_mytek_formats,
|
||||
},
|
||||
// Solid State Logic, Duende Classic and Mini.
|
||||
// NOTE: each field of GUID in config ROM is not compliant to standard
|
||||
// DICE scheme.
|
||||
{
|
||||
.match_flags = IEEE1394_MATCH_VENDOR_ID |
|
||||
IEEE1394_MATCH_MODEL_ID,
|
||||
.vendor_id = OUI_SSL,
|
||||
.model_id = 0x000070,
|
||||
},
|
||||
{
|
||||
.match_flags = IEEE1394_MATCH_VERSION,
|
||||
.version = DICE_INTERFACE,
|
||||
|
|
|
@ -80,20 +80,8 @@ void snd_dg00x_proc_init(struct snd_dg00x *dg00x)
|
|||
return;
|
||||
|
||||
root->mode = S_IFDIR | 0555;
|
||||
if (snd_info_register(root) < 0) {
|
||||
snd_info_free_entry(root);
|
||||
return;
|
||||
}
|
||||
|
||||
entry = snd_info_create_card_entry(dg00x->card, "clock", root);
|
||||
if (entry == NULL) {
|
||||
snd_info_free_entry(root);
|
||||
return;
|
||||
}
|
||||
|
||||
snd_info_set_text_ops(entry, dg00x, proc_read_clock);
|
||||
if (snd_info_register(entry) < 0) {
|
||||
snd_info_free_entry(entry);
|
||||
snd_info_free_entry(root);
|
||||
}
|
||||
if (entry)
|
||||
snd_info_set_text_ops(entry, dg00x, proc_read_clock);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
snd-fireface-objs := ff.o ff-transaction.o ff-midi.o ff-proc.o amdtp-ff.o \
|
||||
ff-stream.o ff-pcm.o ff-hwdep.o ff-protocol-ff400.o \
|
||||
ff-protocol-ff800.o
|
||||
ff-stream.o ff-pcm.o ff-hwdep.o ff-protocol-former.o \
|
||||
ff-protocol-latter.o
|
||||
obj-$(CONFIG_SND_FIREFACE) += snd-fireface.o
|
||||
|
|
|
@ -19,7 +19,7 @@ static int midi_playback_open(struct snd_rawmidi_substream *substream)
|
|||
struct snd_ff *ff = substream->rmidi->private_data;
|
||||
|
||||
/* Initialize internal status. */
|
||||
ff->running_status[substream->number] = 0;
|
||||
ff->on_sysex[substream->number] = 0;
|
||||
ff->rx_midi_error[substream->number] = false;
|
||||
|
||||
WRITE_ONCE(ff->rx_midi_substreams[substream->number], substream);
|
||||
|
|
|
@ -152,7 +152,7 @@ static int pcm_open(struct snd_pcm_substream *substream)
|
|||
if (err < 0)
|
||||
goto release_lock;
|
||||
|
||||
err = snd_ff_transaction_get_clock(ff, &rate, &src);
|
||||
err = ff->spec->protocol->get_clock(ff, &rate, &src);
|
||||
if (err < 0)
|
||||
goto release_lock;
|
||||
|
||||
|
|
|
@ -8,209 +8,29 @@
|
|||
|
||||
#include "./ff.h"
|
||||
|
||||
static void proc_dump_clock_config(struct snd_info_entry *entry,
|
||||
struct snd_info_buffer *buffer)
|
||||
const char *snd_ff_proc_get_clk_label(enum snd_ff_clock_src src)
|
||||
{
|
||||
struct snd_ff *ff = entry->private_data;
|
||||
__le32 reg;
|
||||
u32 data;
|
||||
unsigned int rate;
|
||||
const char *src;
|
||||
int err;
|
||||
static const char *const labels[] = {
|
||||
"Internal",
|
||||
"S/PDIF",
|
||||
"ADAT1",
|
||||
"ADAT2",
|
||||
"Word",
|
||||
"LTC",
|
||||
};
|
||||
|
||||
err = snd_fw_transaction(ff->unit, TCODE_READ_BLOCK_REQUEST,
|
||||
SND_FF_REG_CLOCK_CONFIG, ®, sizeof(reg), 0);
|
||||
if (err < 0)
|
||||
return;
|
||||
if (src >= ARRAY_SIZE(labels))
|
||||
return NULL;
|
||||
|
||||
data = le32_to_cpu(reg);
|
||||
|
||||
snd_iprintf(buffer, "Output S/PDIF format: %s (Emphasis: %s)\n",
|
||||
(data & 0x20) ? "Professional" : "Consumer",
|
||||
(data & 0x40) ? "on" : "off");
|
||||
|
||||
snd_iprintf(buffer, "Optical output interface format: %s\n",
|
||||
((data >> 8) & 0x01) ? "S/PDIF" : "ADAT");
|
||||
|
||||
snd_iprintf(buffer, "Word output single speed: %s\n",
|
||||
((data >> 8) & 0x20) ? "on" : "off");
|
||||
|
||||
snd_iprintf(buffer, "S/PDIF input interface: %s\n",
|
||||
((data >> 8) & 0x02) ? "Optical" : "Coaxial");
|
||||
|
||||
switch ((data >> 1) & 0x03) {
|
||||
case 0x01:
|
||||
rate = 32000;
|
||||
break;
|
||||
case 0x00:
|
||||
rate = 44100;
|
||||
break;
|
||||
case 0x03:
|
||||
rate = 48000;
|
||||
break;
|
||||
case 0x02:
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (data & 0x08)
|
||||
rate *= 2;
|
||||
else if (data & 0x10)
|
||||
rate *= 4;
|
||||
|
||||
snd_iprintf(buffer, "Sampling rate: %d\n", rate);
|
||||
|
||||
if (data & 0x01) {
|
||||
src = "Internal";
|
||||
} else {
|
||||
switch ((data >> 10) & 0x07) {
|
||||
case 0x00:
|
||||
src = "ADAT1";
|
||||
break;
|
||||
case 0x01:
|
||||
src = "ADAT2";
|
||||
break;
|
||||
case 0x03:
|
||||
src = "S/PDIF";
|
||||
break;
|
||||
case 0x04:
|
||||
src = "Word";
|
||||
break;
|
||||
case 0x05:
|
||||
src = "LTC";
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
snd_iprintf(buffer, "Sync to clock source: %s\n", src);
|
||||
return labels[src];
|
||||
}
|
||||
|
||||
static void proc_dump_sync_status(struct snd_info_entry *entry,
|
||||
struct snd_info_buffer *buffer)
|
||||
static void proc_dump_status(struct snd_info_entry *entry,
|
||||
struct snd_info_buffer *buffer)
|
||||
{
|
||||
struct snd_ff *ff = entry->private_data;
|
||||
__le32 reg;
|
||||
u32 data;
|
||||
int err;
|
||||
|
||||
err = snd_fw_transaction(ff->unit, TCODE_READ_QUADLET_REQUEST,
|
||||
SND_FF_REG_SYNC_STATUS, ®, sizeof(reg), 0);
|
||||
if (err < 0)
|
||||
return;
|
||||
|
||||
data = le32_to_cpu(reg);
|
||||
|
||||
snd_iprintf(buffer, "External source detection:\n");
|
||||
|
||||
snd_iprintf(buffer, "Word Clock:");
|
||||
if ((data >> 24) & 0x20) {
|
||||
if ((data >> 24) & 0x40)
|
||||
snd_iprintf(buffer, "sync\n");
|
||||
else
|
||||
snd_iprintf(buffer, "lock\n");
|
||||
} else {
|
||||
snd_iprintf(buffer, "none\n");
|
||||
}
|
||||
|
||||
snd_iprintf(buffer, "S/PDIF:");
|
||||
if ((data >> 16) & 0x10) {
|
||||
if ((data >> 16) & 0x04)
|
||||
snd_iprintf(buffer, "sync\n");
|
||||
else
|
||||
snd_iprintf(buffer, "lock\n");
|
||||
} else {
|
||||
snd_iprintf(buffer, "none\n");
|
||||
}
|
||||
|
||||
snd_iprintf(buffer, "ADAT1:");
|
||||
if ((data >> 8) & 0x04) {
|
||||
if ((data >> 8) & 0x10)
|
||||
snd_iprintf(buffer, "sync\n");
|
||||
else
|
||||
snd_iprintf(buffer, "lock\n");
|
||||
} else {
|
||||
snd_iprintf(buffer, "none\n");
|
||||
}
|
||||
|
||||
snd_iprintf(buffer, "ADAT2:");
|
||||
if ((data >> 8) & 0x08) {
|
||||
if ((data >> 8) & 0x20)
|
||||
snd_iprintf(buffer, "sync\n");
|
||||
else
|
||||
snd_iprintf(buffer, "lock\n");
|
||||
} else {
|
||||
snd_iprintf(buffer, "none\n");
|
||||
}
|
||||
|
||||
snd_iprintf(buffer, "\nUsed external source:\n");
|
||||
|
||||
if (((data >> 22) & 0x07) == 0x07) {
|
||||
snd_iprintf(buffer, "None\n");
|
||||
} else {
|
||||
switch ((data >> 22) & 0x07) {
|
||||
case 0x00:
|
||||
snd_iprintf(buffer, "ADAT1:");
|
||||
break;
|
||||
case 0x01:
|
||||
snd_iprintf(buffer, "ADAT2:");
|
||||
break;
|
||||
case 0x03:
|
||||
snd_iprintf(buffer, "S/PDIF:");
|
||||
break;
|
||||
case 0x04:
|
||||
snd_iprintf(buffer, "Word:");
|
||||
break;
|
||||
case 0x07:
|
||||
snd_iprintf(buffer, "Nothing:");
|
||||
break;
|
||||
case 0x02:
|
||||
case 0x05:
|
||||
case 0x06:
|
||||
default:
|
||||
snd_iprintf(buffer, "unknown:");
|
||||
break;
|
||||
}
|
||||
|
||||
if ((data >> 25) & 0x07) {
|
||||
switch ((data >> 25) & 0x07) {
|
||||
case 0x01:
|
||||
snd_iprintf(buffer, "32000\n");
|
||||
break;
|
||||
case 0x02:
|
||||
snd_iprintf(buffer, "44100\n");
|
||||
break;
|
||||
case 0x03:
|
||||
snd_iprintf(buffer, "48000\n");
|
||||
break;
|
||||
case 0x04:
|
||||
snd_iprintf(buffer, "64000\n");
|
||||
break;
|
||||
case 0x05:
|
||||
snd_iprintf(buffer, "88200\n");
|
||||
break;
|
||||
case 0x06:
|
||||
snd_iprintf(buffer, "96000\n");
|
||||
break;
|
||||
case 0x07:
|
||||
snd_iprintf(buffer, "128000\n");
|
||||
break;
|
||||
case 0x08:
|
||||
snd_iprintf(buffer, "176400\n");
|
||||
break;
|
||||
case 0x09:
|
||||
snd_iprintf(buffer, "192000\n");
|
||||
break;
|
||||
case 0x00:
|
||||
snd_iprintf(buffer, "unknown\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
snd_iprintf(buffer, "Multiplied:");
|
||||
snd_iprintf(buffer, "%d\n", (data & 0x3ff) * 250);
|
||||
ff->spec->protocol->dump_status(ff, buffer);
|
||||
}
|
||||
|
||||
static void add_node(struct snd_ff *ff, struct snd_info_entry *root,
|
||||
|
@ -221,12 +41,8 @@ static void add_node(struct snd_ff *ff, struct snd_info_entry *root,
|
|||
struct snd_info_entry *entry;
|
||||
|
||||
entry = snd_info_create_card_entry(ff->card, name, root);
|
||||
if (entry == NULL)
|
||||
return;
|
||||
|
||||
snd_info_set_text_ops(entry, ff, op);
|
||||
if (snd_info_register(entry) < 0)
|
||||
snd_info_free_entry(entry);
|
||||
if (entry)
|
||||
snd_info_set_text_ops(entry, ff, op);
|
||||
}
|
||||
|
||||
void snd_ff_proc_init(struct snd_ff *ff)
|
||||
|
@ -242,11 +58,6 @@ void snd_ff_proc_init(struct snd_ff *ff)
|
|||
if (root == NULL)
|
||||
return;
|
||||
root->mode = S_IFDIR | 0555;
|
||||
if (snd_info_register(root) < 0) {
|
||||
snd_info_free_entry(root);
|
||||
return;
|
||||
}
|
||||
|
||||
add_node(ff, root, "clock-config", proc_dump_clock_config);
|
||||
add_node(ff, root, "sync-status", proc_dump_sync_status);
|
||||
add_node(ff, root, "status", proc_dump_status);
|
||||
}
|
||||
|
|
|
@ -1,161 +0,0 @@
|
|||
/*
|
||||
* ff-protocol-ff400.c - a part of driver for RME Fireface series
|
||||
*
|
||||
* Copyright (c) 2015-2017 Takashi Sakamoto
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include "ff.h"
|
||||
|
||||
#define FF400_STF 0x000080100500ull
|
||||
#define FF400_RX_PACKET_FORMAT 0x000080100504ull
|
||||
#define FF400_ISOC_COMM_START 0x000080100508ull
|
||||
#define FF400_TX_PACKET_FORMAT 0x00008010050cull
|
||||
#define FF400_ISOC_COMM_STOP 0x000080100510ull
|
||||
|
||||
/*
|
||||
* Fireface 400 manages isochronous channel number in 3 bit field. Therefore,
|
||||
* we can allocate between 0 and 7 channel.
|
||||
*/
|
||||
static int keep_resources(struct snd_ff *ff, unsigned int rate)
|
||||
{
|
||||
enum snd_ff_stream_mode mode;
|
||||
int i;
|
||||
int err;
|
||||
|
||||
// Check whether the given value is supported or not.
|
||||
for (i = 0; i < CIP_SFC_COUNT; i++) {
|
||||
if (amdtp_rate_table[i] == rate)
|
||||
break;
|
||||
}
|
||||
if (i >= CIP_SFC_COUNT)
|
||||
return -EINVAL;
|
||||
|
||||
err = snd_ff_stream_get_multiplier_mode(i, &mode);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* Keep resources for in-stream. */
|
||||
ff->tx_resources.channels_mask = 0x00000000000000ffuLL;
|
||||
err = fw_iso_resources_allocate(&ff->tx_resources,
|
||||
amdtp_stream_get_max_payload(&ff->tx_stream),
|
||||
fw_parent_device(ff->unit)->max_speed);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* Keep resources for out-stream. */
|
||||
err = amdtp_ff_set_parameters(&ff->rx_stream, rate,
|
||||
ff->spec->pcm_playback_channels[mode]);
|
||||
if (err < 0)
|
||||
return err;
|
||||
ff->rx_resources.channels_mask = 0x00000000000000ffuLL;
|
||||
err = fw_iso_resources_allocate(&ff->rx_resources,
|
||||
amdtp_stream_get_max_payload(&ff->rx_stream),
|
||||
fw_parent_device(ff->unit)->max_speed);
|
||||
if (err < 0)
|
||||
fw_iso_resources_free(&ff->tx_resources);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int ff400_begin_session(struct snd_ff *ff, unsigned int rate)
|
||||
{
|
||||
__le32 reg;
|
||||
int err;
|
||||
|
||||
err = keep_resources(ff, rate);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* Set the number of data blocks transferred in a second. */
|
||||
reg = cpu_to_le32(rate);
|
||||
err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
||||
FF400_STF, ®, sizeof(reg), 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
msleep(100);
|
||||
|
||||
/*
|
||||
* Set isochronous channel and the number of quadlets of received
|
||||
* packets.
|
||||
*/
|
||||
reg = cpu_to_le32(((ff->rx_stream.data_block_quadlets << 3) << 8) |
|
||||
ff->rx_resources.channel);
|
||||
err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
||||
FF400_RX_PACKET_FORMAT, ®, sizeof(reg), 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/*
|
||||
* Set isochronous channel and the number of quadlets of transmitted
|
||||
* packet.
|
||||
*/
|
||||
/* TODO: investigate the purpose of this 0x80. */
|
||||
reg = cpu_to_le32((0x80 << 24) |
|
||||
(ff->tx_resources.channel << 5) |
|
||||
(ff->tx_stream.data_block_quadlets));
|
||||
err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
||||
FF400_TX_PACKET_FORMAT, ®, sizeof(reg), 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* Allow to transmit packets. */
|
||||
reg = cpu_to_le32(0x00000001);
|
||||
return snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
||||
FF400_ISOC_COMM_START, ®, sizeof(reg), 0);
|
||||
}
|
||||
|
||||
static void ff400_finish_session(struct snd_ff *ff)
|
||||
{
|
||||
__le32 reg;
|
||||
|
||||
reg = cpu_to_le32(0x80000000);
|
||||
snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
||||
FF400_ISOC_COMM_STOP, ®, sizeof(reg), 0);
|
||||
}
|
||||
|
||||
static void ff400_handle_midi_msg(struct snd_ff *ff, __le32 *buf, size_t length)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < length / 4; i++) {
|
||||
u32 quad = le32_to_cpu(buf[i]);
|
||||
u8 byte;
|
||||
unsigned int index;
|
||||
struct snd_rawmidi_substream *substream;
|
||||
|
||||
/* Message in first port. */
|
||||
/*
|
||||
* This value may represent the index of this unit when the same
|
||||
* units are on the same IEEE 1394 bus. This driver doesn't use
|
||||
* it.
|
||||
*/
|
||||
index = (quad >> 8) & 0xff;
|
||||
if (index > 0) {
|
||||
substream = READ_ONCE(ff->tx_midi_substreams[0]);
|
||||
if (substream != NULL) {
|
||||
byte = quad & 0xff;
|
||||
snd_rawmidi_receive(substream, &byte, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Message in second port. */
|
||||
index = (quad >> 24) & 0xff;
|
||||
if (index > 0) {
|
||||
substream = READ_ONCE(ff->tx_midi_substreams[1]);
|
||||
if (substream != NULL) {
|
||||
byte = (quad >> 16) & 0xff;
|
||||
snd_rawmidi_receive(substream, &byte, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const struct snd_ff_protocol snd_ff_protocol_ff400 = {
|
||||
.handle_midi_msg = ff400_handle_midi_msg,
|
||||
.begin_session = ff400_begin_session,
|
||||
.finish_session = ff400_finish_session,
|
||||
};
|
|
@ -1,143 +0,0 @@
|
|||
/*
|
||||
* ff-protocol-ff800.c - a part of driver for RME Fireface series
|
||||
*
|
||||
* Copyright (c) 2018 Takashi Sakamoto
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
|
||||
#include "ff.h"
|
||||
|
||||
#define FF800_STF 0x0000fc88f000
|
||||
#define FF800_RX_PACKET_FORMAT 0x0000fc88f004
|
||||
#define FF800_ALLOC_TX_STREAM 0x0000fc88f008
|
||||
#define FF800_ISOC_COMM_START 0x0000fc88f00c
|
||||
#define FF800_TX_S800_FLAG 0x00000800
|
||||
#define FF800_ISOC_COMM_STOP 0x0000fc88f010
|
||||
|
||||
#define FF800_TX_PACKET_ISOC_CH 0x0000801c0008
|
||||
|
||||
static int allocate_rx_resources(struct snd_ff *ff)
|
||||
{
|
||||
u32 data;
|
||||
__le32 reg;
|
||||
int err;
|
||||
|
||||
// Controllers should allocate isochronous resources for rx stream.
|
||||
err = fw_iso_resources_allocate(&ff->rx_resources,
|
||||
amdtp_stream_get_max_payload(&ff->rx_stream),
|
||||
fw_parent_device(ff->unit)->max_speed);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
// Set isochronous channel and the number of quadlets of rx packets.
|
||||
data = ff->rx_stream.data_block_quadlets << 3;
|
||||
data = (data << 8) | ff->rx_resources.channel;
|
||||
reg = cpu_to_le32(data);
|
||||
return snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
||||
FF800_RX_PACKET_FORMAT, ®, sizeof(reg), 0);
|
||||
}
|
||||
|
||||
static int allocate_tx_resources(struct snd_ff *ff)
|
||||
{
|
||||
__le32 reg;
|
||||
unsigned int count;
|
||||
unsigned int tx_isoc_channel;
|
||||
int err;
|
||||
|
||||
reg = cpu_to_le32(ff->tx_stream.data_block_quadlets);
|
||||
err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
||||
FF800_ALLOC_TX_STREAM, ®, sizeof(reg), 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
// Wait till the format of tx packet is available.
|
||||
count = 0;
|
||||
while (count++ < 10) {
|
||||
u32 data;
|
||||
err = snd_fw_transaction(ff->unit, TCODE_READ_QUADLET_REQUEST,
|
||||
FF800_TX_PACKET_ISOC_CH, ®, sizeof(reg), 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
data = le32_to_cpu(reg);
|
||||
if (data != 0xffffffff) {
|
||||
tx_isoc_channel = data;
|
||||
break;
|
||||
}
|
||||
|
||||
msleep(50);
|
||||
}
|
||||
if (count >= 10)
|
||||
return -ETIMEDOUT;
|
||||
|
||||
// NOTE: this is a makeshift to start OHCI 1394 IR context in the
|
||||
// channel. On the other hand, 'struct fw_iso_resources.allocated' is
|
||||
// not true and it's not deallocated at stop.
|
||||
ff->tx_resources.channel = tx_isoc_channel;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ff800_begin_session(struct snd_ff *ff, unsigned int rate)
|
||||
{
|
||||
__le32 reg;
|
||||
int err;
|
||||
|
||||
reg = cpu_to_le32(rate);
|
||||
err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
||||
FF800_STF, ®, sizeof(reg), 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
// If starting isochronous communication immediately, change of STF has
|
||||
// no effect. In this case, the communication runs based on former STF.
|
||||
// Let's sleep for a bit.
|
||||
msleep(100);
|
||||
|
||||
err = allocate_rx_resources(ff);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = allocate_tx_resources(ff);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
reg = cpu_to_le32(0x80000000);
|
||||
reg |= cpu_to_le32(ff->tx_stream.data_block_quadlets);
|
||||
if (fw_parent_device(ff->unit)->max_speed == SCODE_800)
|
||||
reg |= cpu_to_le32(FF800_TX_S800_FLAG);
|
||||
return snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
||||
FF800_ISOC_COMM_START, ®, sizeof(reg), 0);
|
||||
}
|
||||
|
||||
static void ff800_finish_session(struct snd_ff *ff)
|
||||
{
|
||||
__le32 reg;
|
||||
|
||||
reg = cpu_to_le32(0x80000000);
|
||||
snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
||||
FF800_ISOC_COMM_STOP, ®, sizeof(reg), 0);
|
||||
}
|
||||
|
||||
static void ff800_handle_midi_msg(struct snd_ff *ff, __le32 *buf, size_t length)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < length / 4; i++) {
|
||||
u8 byte = le32_to_cpu(buf[i]) & 0xff;
|
||||
struct snd_rawmidi_substream *substream;
|
||||
|
||||
substream = READ_ONCE(ff->tx_midi_substreams[0]);
|
||||
if (substream)
|
||||
snd_rawmidi_receive(substream, &byte, 1);
|
||||
}
|
||||
}
|
||||
|
||||
const struct snd_ff_protocol snd_ff_protocol_ff800 = {
|
||||
.handle_midi_msg = ff800_handle_midi_msg,
|
||||
.begin_session = ff800_begin_session,
|
||||
.finish_session = ff800_finish_session,
|
||||
};
|
|
@ -0,0 +1,597 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
// ff-protocol-former.c - a part of driver for RME Fireface series
|
||||
//
|
||||
// Copyright (c) 2019 Takashi Sakamoto
|
||||
//
|
||||
// Licensed under the terms of the GNU General Public License, version 2.
|
||||
|
||||
#include <linux/delay.h>
|
||||
|
||||
#include "ff.h"
|
||||
|
||||
#define FORMER_REG_SYNC_STATUS 0x0000801c0000ull
|
||||
/* For block write request. */
|
||||
#define FORMER_REG_FETCH_PCM_FRAMES 0x0000801c0000ull
|
||||
#define FORMER_REG_CLOCK_CONFIG 0x0000801c0004ull
|
||||
|
||||
static int parse_clock_bits(u32 data, unsigned int *rate,
|
||||
enum snd_ff_clock_src *src)
|
||||
{
|
||||
static const struct {
|
||||
unsigned int rate;
|
||||
u32 mask;
|
||||
} *rate_entry, rate_entries[] = {
|
||||
{ 32000, 0x00000002, },
|
||||
{ 44100, 0x00000000, },
|
||||
{ 48000, 0x00000006, },
|
||||
{ 64000, 0x0000000a, },
|
||||
{ 88200, 0x00000008, },
|
||||
{ 96000, 0x0000000e, },
|
||||
{ 128000, 0x00000012, },
|
||||
{ 176400, 0x00000010, },
|
||||
{ 192000, 0x00000016, },
|
||||
};
|
||||
static const struct {
|
||||
enum snd_ff_clock_src src;
|
||||
u32 mask;
|
||||
} *clk_entry, clk_entries[] = {
|
||||
{ SND_FF_CLOCK_SRC_ADAT1, 0x00000000, },
|
||||
{ SND_FF_CLOCK_SRC_ADAT2, 0x00000400, },
|
||||
{ SND_FF_CLOCK_SRC_SPDIF, 0x00000c00, },
|
||||
{ SND_FF_CLOCK_SRC_WORD, 0x00001000, },
|
||||
{ SND_FF_CLOCK_SRC_LTC, 0x00001800, },
|
||||
};
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(rate_entries); ++i) {
|
||||
rate_entry = rate_entries + i;
|
||||
if ((data & 0x0000001e) == rate_entry->mask) {
|
||||
*rate = rate_entry->rate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == ARRAY_SIZE(rate_entries))
|
||||
return -EIO;
|
||||
|
||||
if (data & 0x00000001) {
|
||||
*src = SND_FF_CLOCK_SRC_INTERNAL;
|
||||
} else {
|
||||
for (i = 0; i < ARRAY_SIZE(clk_entries); ++i) {
|
||||
clk_entry = clk_entries + i;
|
||||
if ((data & 0x00001c00) == clk_entry->mask) {
|
||||
*src = clk_entry->src;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == ARRAY_SIZE(clk_entries))
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int former_get_clock(struct snd_ff *ff, unsigned int *rate,
|
||||
enum snd_ff_clock_src *src)
|
||||
{
|
||||
__le32 reg;
|
||||
u32 data;
|
||||
int err;
|
||||
|
||||
err = snd_fw_transaction(ff->unit, TCODE_READ_QUADLET_REQUEST,
|
||||
FORMER_REG_CLOCK_CONFIG, ®, sizeof(reg), 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
data = le32_to_cpu(reg);
|
||||
|
||||
return parse_clock_bits(data, rate, src);
|
||||
}
|
||||
|
||||
static int former_switch_fetching_mode(struct snd_ff *ff, bool enable)
|
||||
{
|
||||
unsigned int count;
|
||||
__le32 *reg;
|
||||
int i;
|
||||
int err;
|
||||
|
||||
count = 0;
|
||||
for (i = 0; i < SND_FF_STREAM_MODE_COUNT; ++i)
|
||||
count = max(count, ff->spec->pcm_playback_channels[i]);
|
||||
|
||||
reg = kcalloc(count, sizeof(__le32), GFP_KERNEL);
|
||||
if (!reg)
|
||||
return -ENOMEM;
|
||||
|
||||
if (!enable) {
|
||||
/*
|
||||
* Each quadlet is corresponding to data channels in a data
|
||||
* blocks in reverse order. Precisely, quadlets for available
|
||||
* data channels should be enabled. Here, I take second best
|
||||
* to fetch PCM frames from all of data channels regardless of
|
||||
* stf.
|
||||
*/
|
||||
for (i = 0; i < count; ++i)
|
||||
reg[i] = cpu_to_le32(0x00000001);
|
||||
}
|
||||
|
||||
err = snd_fw_transaction(ff->unit, TCODE_WRITE_BLOCK_REQUEST,
|
||||
FORMER_REG_FETCH_PCM_FRAMES, reg,
|
||||
sizeof(__le32) * count, 0);
|
||||
kfree(reg);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void dump_clock_config(struct snd_ff *ff, struct snd_info_buffer *buffer)
|
||||
{
|
||||
__le32 reg;
|
||||
u32 data;
|
||||
unsigned int rate;
|
||||
enum snd_ff_clock_src src;
|
||||
const char *label;
|
||||
int err;
|
||||
|
||||
err = snd_fw_transaction(ff->unit, TCODE_READ_BLOCK_REQUEST,
|
||||
FORMER_REG_CLOCK_CONFIG, ®, sizeof(reg), 0);
|
||||
if (err < 0)
|
||||
return;
|
||||
data = le32_to_cpu(reg);
|
||||
|
||||
snd_iprintf(buffer, "Output S/PDIF format: %s (Emphasis: %s)\n",
|
||||
(data & 0x00000020) ? "Professional" : "Consumer",
|
||||
(data & 0x00000040) ? "on" : "off");
|
||||
|
||||
snd_iprintf(buffer, "Optical output interface format: %s\n",
|
||||
(data & 0x00000100) ? "S/PDIF" : "ADAT");
|
||||
|
||||
snd_iprintf(buffer, "Word output single speed: %s\n",
|
||||
(data & 0x00002000) ? "on" : "off");
|
||||
|
||||
snd_iprintf(buffer, "S/PDIF input interface: %s\n",
|
||||
(data & 0x00000200) ? "Optical" : "Coaxial");
|
||||
|
||||
err = parse_clock_bits(data, &rate, &src);
|
||||
if (err < 0)
|
||||
return;
|
||||
label = snd_ff_proc_get_clk_label(src);
|
||||
if (!label)
|
||||
return;
|
||||
|
||||
snd_iprintf(buffer, "Clock configuration: %d %s\n", rate, label);
|
||||
}
|
||||
|
||||
static void dump_sync_status(struct snd_ff *ff, struct snd_info_buffer *buffer)
|
||||
{
|
||||
static const struct {
|
||||
char *const label;
|
||||
u32 locked_mask;
|
||||
u32 synced_mask;
|
||||
} *clk_entry, clk_entries[] = {
|
||||
{ "WDClk", 0x40000000, 0x20000000, },
|
||||
{ "S/PDIF", 0x00080000, 0x00040000, },
|
||||
{ "ADAT1", 0x00000400, 0x00001000, },
|
||||
{ "ADAT2", 0x00000800, 0x00002000, },
|
||||
};
|
||||
static const struct {
|
||||
char *const label;
|
||||
u32 mask;
|
||||
} *referred_entry, referred_entries[] = {
|
||||
{ "ADAT1", 0x00000000, },
|
||||
{ "ADAT2", 0x00400000, },
|
||||
{ "S/PDIF", 0x00c00000, },
|
||||
{ "WDclk", 0x01000000, },
|
||||
{ "TCO", 0x01400000, },
|
||||
};
|
||||
static const struct {
|
||||
unsigned int rate;
|
||||
u32 mask;
|
||||
} *rate_entry, rate_entries[] = {
|
||||
{ 32000, 0x02000000, },
|
||||
{ 44100, 0x04000000, },
|
||||
{ 48000, 0x06000000, },
|
||||
{ 64000, 0x08000000, },
|
||||
{ 88200, 0x0a000000, },
|
||||
{ 96000, 0x0c000000, },
|
||||
{ 128000, 0x0e000000, },
|
||||
{ 176400, 0x10000000, },
|
||||
{ 192000, 0x12000000, },
|
||||
};
|
||||
__le32 reg[2];
|
||||
u32 data[2];
|
||||
int i;
|
||||
int err;
|
||||
|
||||
err = snd_fw_transaction(ff->unit, TCODE_READ_BLOCK_REQUEST,
|
||||
FORMER_REG_SYNC_STATUS, reg, sizeof(reg), 0);
|
||||
if (err < 0)
|
||||
return;
|
||||
data[0] = le32_to_cpu(reg[0]);
|
||||
data[1] = le32_to_cpu(reg[1]);
|
||||
|
||||
snd_iprintf(buffer, "External source detection:\n");
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(clk_entries); ++i) {
|
||||
const char *state;
|
||||
|
||||
clk_entry = clk_entries + i;
|
||||
if (data[0] & clk_entry->locked_mask) {
|
||||
if (data[0] & clk_entry->synced_mask)
|
||||
state = "sync";
|
||||
else
|
||||
state = "lock";
|
||||
} else {
|
||||
state = "none";
|
||||
}
|
||||
|
||||
snd_iprintf(buffer, "%s: %s\n", clk_entry->label, state);
|
||||
}
|
||||
|
||||
snd_iprintf(buffer, "Referred clock:\n");
|
||||
|
||||
if (data[1] & 0x00000001) {
|
||||
snd_iprintf(buffer, "Internal\n");
|
||||
} else {
|
||||
unsigned int rate;
|
||||
const char *label;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(referred_entries); ++i) {
|
||||
referred_entry = referred_entries + i;
|
||||
if ((data[0] & 0x1e0000) == referred_entry->mask) {
|
||||
label = referred_entry->label;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == ARRAY_SIZE(referred_entries))
|
||||
label = "none";
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(rate_entries); ++i) {
|
||||
rate_entry = rate_entries + i;
|
||||
if ((data[0] & 0x1e000000) == rate_entry->mask) {
|
||||
rate = rate_entry->rate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == ARRAY_SIZE(rate_entries))
|
||||
rate = 0;
|
||||
|
||||
snd_iprintf(buffer, "%s %d\n", label, rate);
|
||||
}
|
||||
}
|
||||
|
||||
static void former_dump_status(struct snd_ff *ff,
|
||||
struct snd_info_buffer *buffer)
|
||||
{
|
||||
dump_clock_config(ff, buffer);
|
||||
dump_sync_status(ff, buffer);
|
||||
}
|
||||
|
||||
static int former_fill_midi_msg(struct snd_ff *ff,
|
||||
struct snd_rawmidi_substream *substream,
|
||||
unsigned int port)
|
||||
{
|
||||
u8 *buf = (u8 *)ff->msg_buf[port];
|
||||
int len;
|
||||
int i;
|
||||
|
||||
len = snd_rawmidi_transmit_peek(substream, buf,
|
||||
SND_FF_MAXIMIM_MIDI_QUADS);
|
||||
if (len <= 0)
|
||||
return len;
|
||||
|
||||
// One quadlet includes one byte.
|
||||
for (i = len - 1; i >= 0; --i)
|
||||
ff->msg_buf[port][i] = cpu_to_le32(buf[i]);
|
||||
ff->rx_bytes[port] = len;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
#define FF800_STF 0x0000fc88f000
|
||||
#define FF800_RX_PACKET_FORMAT 0x0000fc88f004
|
||||
#define FF800_ALLOC_TX_STREAM 0x0000fc88f008
|
||||
#define FF800_ISOC_COMM_START 0x0000fc88f00c
|
||||
#define FF800_TX_S800_FLAG 0x00000800
|
||||
#define FF800_ISOC_COMM_STOP 0x0000fc88f010
|
||||
|
||||
#define FF800_TX_PACKET_ISOC_CH 0x0000801c0008
|
||||
|
||||
static int allocate_rx_resources(struct snd_ff *ff)
|
||||
{
|
||||
u32 data;
|
||||
__le32 reg;
|
||||
int err;
|
||||
|
||||
// Controllers should allocate isochronous resources for rx stream.
|
||||
err = fw_iso_resources_allocate(&ff->rx_resources,
|
||||
amdtp_stream_get_max_payload(&ff->rx_stream),
|
||||
fw_parent_device(ff->unit)->max_speed);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
// Set isochronous channel and the number of quadlets of rx packets.
|
||||
data = ff->rx_stream.data_block_quadlets << 3;
|
||||
data = (data << 8) | ff->rx_resources.channel;
|
||||
reg = cpu_to_le32(data);
|
||||
return snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
||||
FF800_RX_PACKET_FORMAT, ®, sizeof(reg), 0);
|
||||
}
|
||||
|
||||
static int allocate_tx_resources(struct snd_ff *ff)
|
||||
{
|
||||
__le32 reg;
|
||||
unsigned int count;
|
||||
unsigned int tx_isoc_channel;
|
||||
int err;
|
||||
|
||||
reg = cpu_to_le32(ff->tx_stream.data_block_quadlets);
|
||||
err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
||||
FF800_ALLOC_TX_STREAM, ®, sizeof(reg), 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
// Wait till the format of tx packet is available.
|
||||
count = 0;
|
||||
while (count++ < 10) {
|
||||
u32 data;
|
||||
err = snd_fw_transaction(ff->unit, TCODE_READ_QUADLET_REQUEST,
|
||||
FF800_TX_PACKET_ISOC_CH, ®, sizeof(reg), 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
data = le32_to_cpu(reg);
|
||||
if (data != 0xffffffff) {
|
||||
tx_isoc_channel = data;
|
||||
break;
|
||||
}
|
||||
|
||||
msleep(50);
|
||||
}
|
||||
if (count >= 10)
|
||||
return -ETIMEDOUT;
|
||||
|
||||
// NOTE: this is a makeshift to start OHCI 1394 IR context in the
|
||||
// channel. On the other hand, 'struct fw_iso_resources.allocated' is
|
||||
// not true and it's not deallocated at stop.
|
||||
ff->tx_resources.channel = tx_isoc_channel;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ff800_begin_session(struct snd_ff *ff, unsigned int rate)
|
||||
{
|
||||
__le32 reg;
|
||||
int err;
|
||||
|
||||
reg = cpu_to_le32(rate);
|
||||
err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
||||
FF800_STF, ®, sizeof(reg), 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
// If starting isochronous communication immediately, change of STF has
|
||||
// no effect. In this case, the communication runs based on former STF.
|
||||
// Let's sleep for a bit.
|
||||
msleep(100);
|
||||
|
||||
err = allocate_rx_resources(ff);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = allocate_tx_resources(ff);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
reg = cpu_to_le32(0x80000000);
|
||||
reg |= cpu_to_le32(ff->tx_stream.data_block_quadlets);
|
||||
if (fw_parent_device(ff->unit)->max_speed == SCODE_800)
|
||||
reg |= cpu_to_le32(FF800_TX_S800_FLAG);
|
||||
return snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
||||
FF800_ISOC_COMM_START, ®, sizeof(reg), 0);
|
||||
}
|
||||
|
||||
static void ff800_finish_session(struct snd_ff *ff)
|
||||
{
|
||||
__le32 reg;
|
||||
|
||||
reg = cpu_to_le32(0x80000000);
|
||||
snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
||||
FF800_ISOC_COMM_STOP, ®, sizeof(reg), 0);
|
||||
}
|
||||
|
||||
// Fireface 800 doesn't allow drivers to register lower 4 bytes of destination
|
||||
// address.
|
||||
// A write transaction to clear registered higher 4 bytes of destination address
|
||||
// has an effect to suppress asynchronous transaction from device.
|
||||
static void ff800_handle_midi_msg(struct snd_ff *ff, unsigned int offset,
|
||||
__le32 *buf, size_t length)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < length / 4; i++) {
|
||||
u8 byte = le32_to_cpu(buf[i]) & 0xff;
|
||||
struct snd_rawmidi_substream *substream;
|
||||
|
||||
substream = READ_ONCE(ff->tx_midi_substreams[0]);
|
||||
if (substream)
|
||||
snd_rawmidi_receive(substream, &byte, 1);
|
||||
}
|
||||
}
|
||||
|
||||
const struct snd_ff_protocol snd_ff_protocol_ff800 = {
|
||||
.handle_midi_msg = ff800_handle_midi_msg,
|
||||
.fill_midi_msg = former_fill_midi_msg,
|
||||
.get_clock = former_get_clock,
|
||||
.switch_fetching_mode = former_switch_fetching_mode,
|
||||
.begin_session = ff800_begin_session,
|
||||
.finish_session = ff800_finish_session,
|
||||
.dump_status = former_dump_status,
|
||||
};
|
||||
|
||||
#define FF400_STF 0x000080100500ull
|
||||
#define FF400_RX_PACKET_FORMAT 0x000080100504ull
|
||||
#define FF400_ISOC_COMM_START 0x000080100508ull
|
||||
#define FF400_TX_PACKET_FORMAT 0x00008010050cull
|
||||
#define FF400_ISOC_COMM_STOP 0x000080100510ull
|
||||
|
||||
/*
|
||||
* Fireface 400 manages isochronous channel number in 3 bit field. Therefore,
|
||||
* we can allocate between 0 and 7 channel.
|
||||
*/
|
||||
static int keep_resources(struct snd_ff *ff, unsigned int rate)
|
||||
{
|
||||
enum snd_ff_stream_mode mode;
|
||||
int i;
|
||||
int err;
|
||||
|
||||
// Check whether the given value is supported or not.
|
||||
for (i = 0; i < CIP_SFC_COUNT; i++) {
|
||||
if (amdtp_rate_table[i] == rate)
|
||||
break;
|
||||
}
|
||||
if (i >= CIP_SFC_COUNT)
|
||||
return -EINVAL;
|
||||
|
||||
err = snd_ff_stream_get_multiplier_mode(i, &mode);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* Keep resources for in-stream. */
|
||||
ff->tx_resources.channels_mask = 0x00000000000000ffuLL;
|
||||
err = fw_iso_resources_allocate(&ff->tx_resources,
|
||||
amdtp_stream_get_max_payload(&ff->tx_stream),
|
||||
fw_parent_device(ff->unit)->max_speed);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* Keep resources for out-stream. */
|
||||
ff->rx_resources.channels_mask = 0x00000000000000ffuLL;
|
||||
err = fw_iso_resources_allocate(&ff->rx_resources,
|
||||
amdtp_stream_get_max_payload(&ff->rx_stream),
|
||||
fw_parent_device(ff->unit)->max_speed);
|
||||
if (err < 0)
|
||||
fw_iso_resources_free(&ff->tx_resources);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int ff400_begin_session(struct snd_ff *ff, unsigned int rate)
|
||||
{
|
||||
__le32 reg;
|
||||
int err;
|
||||
|
||||
err = keep_resources(ff, rate);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* Set the number of data blocks transferred in a second. */
|
||||
reg = cpu_to_le32(rate);
|
||||
err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
||||
FF400_STF, ®, sizeof(reg), 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
msleep(100);
|
||||
|
||||
/*
|
||||
* Set isochronous channel and the number of quadlets of received
|
||||
* packets.
|
||||
*/
|
||||
reg = cpu_to_le32(((ff->rx_stream.data_block_quadlets << 3) << 8) |
|
||||
ff->rx_resources.channel);
|
||||
err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
||||
FF400_RX_PACKET_FORMAT, ®, sizeof(reg), 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/*
|
||||
* Set isochronous channel and the number of quadlets of transmitted
|
||||
* packet.
|
||||
*/
|
||||
/* TODO: investigate the purpose of this 0x80. */
|
||||
reg = cpu_to_le32((0x80 << 24) |
|
||||
(ff->tx_resources.channel << 5) |
|
||||
(ff->tx_stream.data_block_quadlets));
|
||||
err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
||||
FF400_TX_PACKET_FORMAT, ®, sizeof(reg), 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* Allow to transmit packets. */
|
||||
reg = cpu_to_le32(0x00000001);
|
||||
return snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
||||
FF400_ISOC_COMM_START, ®, sizeof(reg), 0);
|
||||
}
|
||||
|
||||
static void ff400_finish_session(struct snd_ff *ff)
|
||||
{
|
||||
__le32 reg;
|
||||
|
||||
reg = cpu_to_le32(0x80000000);
|
||||
snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
||||
FF400_ISOC_COMM_STOP, ®, sizeof(reg), 0);
|
||||
}
|
||||
|
||||
// For Fireface 400, lower 4 bytes of destination address is configured by bit
|
||||
// flag in quadlet register (little endian) at 0x'0000'801'0051c. Drivers can
|
||||
// select one of 4 options:
|
||||
//
|
||||
// bit flags: offset of destination address
|
||||
// - 0x04000000: 0x'....'....'0000'0000
|
||||
// - 0x08000000: 0x'....'....'0000'0080
|
||||
// - 0x10000000: 0x'....'....'0000'0100
|
||||
// - 0x20000000: 0x'....'....'0000'0180
|
||||
//
|
||||
// Drivers can suppress the device to transfer asynchronous transactions by
|
||||
// using below 2 bits.
|
||||
// - 0x01000000: suppress transmission
|
||||
// - 0x02000000: suppress transmission
|
||||
//
|
||||
// Actually, the register is write-only and includes the other options such as
|
||||
// input attenuation. This driver allocates destination address with '0000'0000
|
||||
// in its lower offset and expects userspace application to configure the
|
||||
// register for it.
|
||||
static void ff400_handle_midi_msg(struct snd_ff *ff, unsigned int offset,
|
||||
__le32 *buf, size_t length)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < length / 4; i++) {
|
||||
u32 quad = le32_to_cpu(buf[i]);
|
||||
u8 byte;
|
||||
unsigned int index;
|
||||
struct snd_rawmidi_substream *substream;
|
||||
|
||||
/* Message in first port. */
|
||||
/*
|
||||
* This value may represent the index of this unit when the same
|
||||
* units are on the same IEEE 1394 bus. This driver doesn't use
|
||||
* it.
|
||||
*/
|
||||
index = (quad >> 8) & 0xff;
|
||||
if (index > 0) {
|
||||
substream = READ_ONCE(ff->tx_midi_substreams[0]);
|
||||
if (substream != NULL) {
|
||||
byte = quad & 0xff;
|
||||
snd_rawmidi_receive(substream, &byte, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Message in second port. */
|
||||
index = (quad >> 24) & 0xff;
|
||||
if (index > 0) {
|
||||
substream = READ_ONCE(ff->tx_midi_substreams[1]);
|
||||
if (substream != NULL) {
|
||||
byte = (quad >> 16) & 0xff;
|
||||
snd_rawmidi_receive(substream, &byte, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const struct snd_ff_protocol snd_ff_protocol_ff400 = {
|
||||
.handle_midi_msg = ff400_handle_midi_msg,
|
||||
.fill_midi_msg = former_fill_midi_msg,
|
||||
.get_clock = former_get_clock,
|
||||
.switch_fetching_mode = former_switch_fetching_mode,
|
||||
.begin_session = ff400_begin_session,
|
||||
.finish_session = ff400_finish_session,
|
||||
.dump_status = former_dump_status,
|
||||
};
|
|
@ -0,0 +1,430 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
// ff-protocol-latter - a part of driver for RME Fireface series
|
||||
//
|
||||
// Copyright (c) 2019 Takashi Sakamoto
|
||||
//
|
||||
// Licensed under the terms of the GNU General Public License, version 2.
|
||||
|
||||
#include <linux/delay.h>
|
||||
|
||||
#include "ff.h"
|
||||
|
||||
#define LATTER_STF 0xffff00000004
|
||||
#define LATTER_ISOC_CHANNELS 0xffff00000008
|
||||
#define LATTER_ISOC_START 0xffff0000000c
|
||||
#define LATTER_FETCH_MODE 0xffff00000010
|
||||
#define LATTER_SYNC_STATUS 0x0000801c0000
|
||||
|
||||
static int parse_clock_bits(u32 data, unsigned int *rate,
|
||||
enum snd_ff_clock_src *src)
|
||||
{
|
||||
static const struct {
|
||||
unsigned int rate;
|
||||
u32 flag;
|
||||
} *rate_entry, rate_entries[] = {
|
||||
{ 32000, 0x00000000, },
|
||||
{ 44100, 0x01000000, },
|
||||
{ 48000, 0x02000000, },
|
||||
{ 64000, 0x04000000, },
|
||||
{ 88200, 0x05000000, },
|
||||
{ 96000, 0x06000000, },
|
||||
{ 128000, 0x08000000, },
|
||||
{ 176400, 0x09000000, },
|
||||
{ 192000, 0x0a000000, },
|
||||
};
|
||||
static const struct {
|
||||
enum snd_ff_clock_src src;
|
||||
u32 flag;
|
||||
} *clk_entry, clk_entries[] = {
|
||||
{ SND_FF_CLOCK_SRC_SPDIF, 0x00000200, },
|
||||
{ SND_FF_CLOCK_SRC_ADAT1, 0x00000400, },
|
||||
{ SND_FF_CLOCK_SRC_WORD, 0x00000600, },
|
||||
{ SND_FF_CLOCK_SRC_INTERNAL, 0x00000e00, },
|
||||
};
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(rate_entries); ++i) {
|
||||
rate_entry = rate_entries + i;
|
||||
if ((data & 0x0f000000) == rate_entry->flag) {
|
||||
*rate = rate_entry->rate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == ARRAY_SIZE(rate_entries))
|
||||
return -EIO;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(clk_entries); ++i) {
|
||||
clk_entry = clk_entries + i;
|
||||
if ((data & 0x000e00) == clk_entry->flag) {
|
||||
*src = clk_entry->src;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == ARRAY_SIZE(clk_entries))
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int latter_get_clock(struct snd_ff *ff, unsigned int *rate,
|
||||
enum snd_ff_clock_src *src)
|
||||
{
|
||||
__le32 reg;
|
||||
u32 data;
|
||||
int err;
|
||||
|
||||
err = snd_fw_transaction(ff->unit, TCODE_READ_QUADLET_REQUEST,
|
||||
LATTER_SYNC_STATUS, ®, sizeof(reg), 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
data = le32_to_cpu(reg);
|
||||
|
||||
return parse_clock_bits(data, rate, src);
|
||||
}
|
||||
|
||||
static int latter_switch_fetching_mode(struct snd_ff *ff, bool enable)
|
||||
{
|
||||
u32 data;
|
||||
__le32 reg;
|
||||
|
||||
if (enable)
|
||||
data = 0x00000000;
|
||||
else
|
||||
data = 0xffffffff;
|
||||
reg = cpu_to_le32(data);
|
||||
|
||||
return snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
||||
LATTER_FETCH_MODE, ®, sizeof(reg), 0);
|
||||
}
|
||||
|
||||
static int keep_resources(struct snd_ff *ff, unsigned int rate)
|
||||
{
|
||||
enum snd_ff_stream_mode mode;
|
||||
int i;
|
||||
int err;
|
||||
|
||||
// Check whether the given value is supported or not.
|
||||
for (i = 0; i < CIP_SFC_COUNT; i++) {
|
||||
if (amdtp_rate_table[i] == rate)
|
||||
break;
|
||||
}
|
||||
if (i >= CIP_SFC_COUNT)
|
||||
return -EINVAL;
|
||||
|
||||
err = snd_ff_stream_get_multiplier_mode(i, &mode);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* Keep resources for in-stream. */
|
||||
ff->tx_resources.channels_mask = 0x00000000000000ffuLL;
|
||||
err = fw_iso_resources_allocate(&ff->tx_resources,
|
||||
amdtp_stream_get_max_payload(&ff->tx_stream),
|
||||
fw_parent_device(ff->unit)->max_speed);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* Keep resources for out-stream. */
|
||||
ff->rx_resources.channels_mask = 0x00000000000000ffuLL;
|
||||
err = fw_iso_resources_allocate(&ff->rx_resources,
|
||||
amdtp_stream_get_max_payload(&ff->rx_stream),
|
||||
fw_parent_device(ff->unit)->max_speed);
|
||||
if (err < 0)
|
||||
fw_iso_resources_free(&ff->tx_resources);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int latter_begin_session(struct snd_ff *ff, unsigned int rate)
|
||||
{
|
||||
static const struct {
|
||||
unsigned int stf;
|
||||
unsigned int code;
|
||||
unsigned int flag;
|
||||
} *entry, rate_table[] = {
|
||||
{ 32000, 0x00, 0x92, },
|
||||
{ 44100, 0x02, 0x92, },
|
||||
{ 48000, 0x04, 0x92, },
|
||||
{ 64000, 0x08, 0x8e, },
|
||||
{ 88200, 0x0a, 0x8e, },
|
||||
{ 96000, 0x0c, 0x8e, },
|
||||
{ 128000, 0x10, 0x8c, },
|
||||
{ 176400, 0x12, 0x8c, },
|
||||
{ 192000, 0x14, 0x8c, },
|
||||
};
|
||||
u32 data;
|
||||
__le32 reg;
|
||||
unsigned int count;
|
||||
int i;
|
||||
int err;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(rate_table); ++i) {
|
||||
entry = rate_table + i;
|
||||
if (entry->stf == rate)
|
||||
break;
|
||||
}
|
||||
if (i == ARRAY_SIZE(rate_table))
|
||||
return -EINVAL;
|
||||
|
||||
reg = cpu_to_le32(entry->code);
|
||||
err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
||||
LATTER_STF, ®, sizeof(reg), 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
// Confirm to shift transmission clock.
|
||||
count = 0;
|
||||
while (count++ < 10) {
|
||||
unsigned int curr_rate;
|
||||
enum snd_ff_clock_src src;
|
||||
|
||||
err = latter_get_clock(ff, &curr_rate, &src);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if (curr_rate == rate)
|
||||
break;
|
||||
}
|
||||
if (count == 10)
|
||||
return -ETIMEDOUT;
|
||||
|
||||
err = keep_resources(ff, rate);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
data = (ff->tx_resources.channel << 8) | ff->rx_resources.channel;
|
||||
reg = cpu_to_le32(data);
|
||||
err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
||||
LATTER_ISOC_CHANNELS, ®, sizeof(reg), 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
// Always use the maximum number of data channels in data block of
|
||||
// packet.
|
||||
reg = cpu_to_le32(entry->flag);
|
||||
return snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
||||
LATTER_ISOC_START, ®, sizeof(reg), 0);
|
||||
}
|
||||
|
||||
static void latter_finish_session(struct snd_ff *ff)
|
||||
{
|
||||
__le32 reg;
|
||||
|
||||
reg = cpu_to_le32(0x00000000);
|
||||
snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
||||
LATTER_ISOC_START, ®, sizeof(reg), 0);
|
||||
}
|
||||
|
||||
static void latter_dump_status(struct snd_ff *ff, struct snd_info_buffer *buffer)
|
||||
{
|
||||
static const struct {
|
||||
char *const label;
|
||||
u32 locked_mask;
|
||||
u32 synced_mask;
|
||||
} *clk_entry, clk_entries[] = {
|
||||
{ "S/PDIF", 0x00000001, 0x00000010, },
|
||||
{ "ADAT", 0x00000002, 0x00000020, },
|
||||
{ "WDClk", 0x00000004, 0x00000040, },
|
||||
};
|
||||
__le32 reg;
|
||||
u32 data;
|
||||
unsigned int rate;
|
||||
enum snd_ff_clock_src src;
|
||||
const char *label;
|
||||
int i;
|
||||
int err;
|
||||
|
||||
err = snd_fw_transaction(ff->unit, TCODE_READ_QUADLET_REQUEST,
|
||||
LATTER_SYNC_STATUS, ®, sizeof(reg), 0);
|
||||
if (err < 0)
|
||||
return;
|
||||
data = le32_to_cpu(reg);
|
||||
|
||||
snd_iprintf(buffer, "External source detection:\n");
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(clk_entries); ++i) {
|
||||
clk_entry = clk_entries + i;
|
||||
snd_iprintf(buffer, "%s: ", clk_entry->label);
|
||||
if (data & clk_entry->locked_mask) {
|
||||
if (data & clk_entry->synced_mask)
|
||||
snd_iprintf(buffer, "sync\n");
|
||||
else
|
||||
snd_iprintf(buffer, "lock\n");
|
||||
} else {
|
||||
snd_iprintf(buffer, "none\n");
|
||||
}
|
||||
}
|
||||
|
||||
err = parse_clock_bits(data, &rate, &src);
|
||||
if (err < 0)
|
||||
return;
|
||||
label = snd_ff_proc_get_clk_label(src);
|
||||
if (!label)
|
||||
return;
|
||||
|
||||
snd_iprintf(buffer, "Referred clock: %s %d\n", label, rate);
|
||||
}
|
||||
|
||||
// NOTE: transactions are transferred within 0x00-0x7f in allocated range of
|
||||
// address. This seems to be for check of discontinuity in receiver side.
|
||||
//
|
||||
// Like Fireface 400, drivers can select one of 4 options for lower 4 bytes of
|
||||
// destination address by bit flags in quadlet register (little endian) at
|
||||
// 0x'ffff'0000'0014:
|
||||
//
|
||||
// bit flags: offset of destination address
|
||||
// - 0x00002000: 0x'....'....'0000'0000
|
||||
// - 0x00004000: 0x'....'....'0000'0080
|
||||
// - 0x00008000: 0x'....'....'0000'0100
|
||||
// - 0x00010000: 0x'....'....'0000'0180
|
||||
//
|
||||
// Drivers can suppress the device to transfer asynchronous transactions by
|
||||
// clear these bit flags.
|
||||
//
|
||||
// Actually, the register is write-only and includes the other settings such as
|
||||
// input attenuation. This driver allocates for the first option
|
||||
// (0x'....'....'0000'0000) and expects userspace application to configure the
|
||||
// register for it.
|
||||
static void latter_handle_midi_msg(struct snd_ff *ff, unsigned int offset,
|
||||
__le32 *buf, size_t length)
|
||||
{
|
||||
u32 data = le32_to_cpu(*buf);
|
||||
unsigned int index = (data & 0x000000f0) >> 4;
|
||||
u8 byte[3];
|
||||
struct snd_rawmidi_substream *substream;
|
||||
unsigned int len;
|
||||
|
||||
if (index >= ff->spec->midi_in_ports)
|
||||
return;
|
||||
|
||||
switch (data & 0x0000000f) {
|
||||
case 0x00000008:
|
||||
case 0x00000009:
|
||||
case 0x0000000a:
|
||||
case 0x0000000b:
|
||||
case 0x0000000e:
|
||||
len = 3;
|
||||
break;
|
||||
case 0x0000000c:
|
||||
case 0x0000000d:
|
||||
len = 2;
|
||||
break;
|
||||
default:
|
||||
len = data & 0x00000003;
|
||||
if (len == 0)
|
||||
len = 3;
|
||||
break;
|
||||
}
|
||||
|
||||
byte[0] = (data & 0x0000ff00) >> 8;
|
||||
byte[1] = (data & 0x00ff0000) >> 16;
|
||||
byte[2] = (data & 0xff000000) >> 24;
|
||||
|
||||
substream = READ_ONCE(ff->tx_midi_substreams[index]);
|
||||
if (substream)
|
||||
snd_rawmidi_receive(substream, byte, len);
|
||||
}
|
||||
|
||||
/*
|
||||
* When return minus value, given argument is not MIDI status.
|
||||
* When return 0, given argument is a beginning of system exclusive.
|
||||
* When return the others, given argument is MIDI data.
|
||||
*/
|
||||
static inline int calculate_message_bytes(u8 status)
|
||||
{
|
||||
switch (status) {
|
||||
case 0xf6: /* Tune request. */
|
||||
case 0xf8: /* Timing clock. */
|
||||
case 0xfa: /* Start. */
|
||||
case 0xfb: /* Continue. */
|
||||
case 0xfc: /* Stop. */
|
||||
case 0xfe: /* Active sensing. */
|
||||
case 0xff: /* System reset. */
|
||||
return 1;
|
||||
case 0xf1: /* MIDI time code quarter frame. */
|
||||
case 0xf3: /* Song select. */
|
||||
return 2;
|
||||
case 0xf2: /* Song position pointer. */
|
||||
return 3;
|
||||
case 0xf0: /* Exclusive. */
|
||||
return 0;
|
||||
case 0xf7: /* End of exclusive. */
|
||||
break;
|
||||
case 0xf4: /* Undefined. */
|
||||
case 0xf5: /* Undefined. */
|
||||
case 0xf9: /* Undefined. */
|
||||
case 0xfd: /* Undefined. */
|
||||
break;
|
||||
default:
|
||||
switch (status & 0xf0) {
|
||||
case 0x80: /* Note on. */
|
||||
case 0x90: /* Note off. */
|
||||
case 0xa0: /* Polyphonic key pressure. */
|
||||
case 0xb0: /* Control change and Mode change. */
|
||||
case 0xe0: /* Pitch bend change. */
|
||||
return 3;
|
||||
case 0xc0: /* Program change. */
|
||||
case 0xd0: /* Channel pressure. */
|
||||
return 2;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int latter_fill_midi_msg(struct snd_ff *ff,
|
||||
struct snd_rawmidi_substream *substream,
|
||||
unsigned int port)
|
||||
{
|
||||
u32 data = {0};
|
||||
u8 *buf = (u8 *)&data;
|
||||
int consumed;
|
||||
|
||||
buf[0] = port << 4;
|
||||
consumed = snd_rawmidi_transmit_peek(substream, buf + 1, 3);
|
||||
if (consumed <= 0)
|
||||
return consumed;
|
||||
|
||||
if (!ff->on_sysex[port]) {
|
||||
if (buf[1] != 0xf0) {
|
||||
if (consumed < calculate_message_bytes(buf[1]))
|
||||
return 0;
|
||||
} else {
|
||||
// The beginning of exclusives.
|
||||
ff->on_sysex[port] = true;
|
||||
}
|
||||
|
||||
buf[0] |= consumed;
|
||||
} else {
|
||||
if (buf[1] != 0xf7) {
|
||||
if (buf[2] == 0xf7 || buf[3] == 0xf7) {
|
||||
// Transfer end code at next time.
|
||||
consumed -= 1;
|
||||
}
|
||||
|
||||
buf[0] |= consumed;
|
||||
} else {
|
||||
// The end of exclusives.
|
||||
ff->on_sysex[port] = false;
|
||||
consumed = 1;
|
||||
buf[0] |= 0x0f;
|
||||
}
|
||||
}
|
||||
|
||||
ff->msg_buf[port][0] = cpu_to_le32(data);
|
||||
ff->rx_bytes[port] = consumed;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
const struct snd_ff_protocol snd_ff_protocol_latter = {
|
||||
.handle_midi_msg = latter_handle_midi_msg,
|
||||
.fill_midi_msg = latter_fill_midi_msg,
|
||||
.get_clock = latter_get_clock,
|
||||
.switch_fetching_mode = latter_switch_fetching_mode,
|
||||
.begin_session = latter_begin_session,
|
||||
.finish_session = latter_finish_session,
|
||||
.dump_status = latter_dump_status,
|
||||
};
|
|
@ -37,44 +37,10 @@ static void release_resources(struct snd_ff *ff)
|
|||
fw_iso_resources_free(&ff->rx_resources);
|
||||
}
|
||||
|
||||
static int switch_fetching_mode(struct snd_ff *ff, bool enable)
|
||||
{
|
||||
unsigned int count;
|
||||
__le32 *reg;
|
||||
int i;
|
||||
int err;
|
||||
|
||||
count = 0;
|
||||
for (i = 0; i < SND_FF_STREAM_MODE_COUNT; ++i)
|
||||
count = max(count, ff->spec->pcm_playback_channels[i]);
|
||||
|
||||
reg = kcalloc(count, sizeof(__le32), GFP_KERNEL);
|
||||
if (!reg)
|
||||
return -ENOMEM;
|
||||
|
||||
if (!enable) {
|
||||
/*
|
||||
* Each quadlet is corresponding to data channels in a data
|
||||
* blocks in reverse order. Precisely, quadlets for available
|
||||
* data channels should be enabled. Here, I take second best
|
||||
* to fetch PCM frames from all of data channels regardless of
|
||||
* stf.
|
||||
*/
|
||||
for (i = 0; i < count; ++i)
|
||||
reg[i] = cpu_to_le32(0x00000001);
|
||||
}
|
||||
|
||||
err = snd_fw_transaction(ff->unit, TCODE_WRITE_BLOCK_REQUEST,
|
||||
SND_FF_REG_FETCH_PCM_FRAMES, reg,
|
||||
sizeof(__le32) * count, 0);
|
||||
kfree(reg);
|
||||
return err;
|
||||
}
|
||||
|
||||
static inline void finish_session(struct snd_ff *ff)
|
||||
{
|
||||
ff->spec->protocol->finish_session(ff);
|
||||
switch_fetching_mode(ff, false);
|
||||
ff->spec->protocol->switch_fetching_mode(ff, false);
|
||||
}
|
||||
|
||||
static int init_stream(struct snd_ff *ff, enum amdtp_stream_direction dir)
|
||||
|
@ -147,7 +113,7 @@ int snd_ff_stream_start_duplex(struct snd_ff *ff, unsigned int rate)
|
|||
if (ff->substreams_counter == 0)
|
||||
return 0;
|
||||
|
||||
err = snd_ff_transaction_get_clock(ff, &curr_rate, &src);
|
||||
err = ff->spec->protocol->get_clock(ff, &curr_rate, &src);
|
||||
if (err < 0)
|
||||
return err;
|
||||
if (curr_rate != rate ||
|
||||
|
@ -206,7 +172,7 @@ int snd_ff_stream_start_duplex(struct snd_ff *ff, unsigned int rate)
|
|||
goto error;
|
||||
}
|
||||
|
||||
err = switch_fetching_mode(ff, true);
|
||||
err = ff->spec->protocol->switch_fetching_mode(ff, true);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
}
|
||||
|
|
|
@ -8,72 +8,6 @@
|
|||
|
||||
#include "ff.h"
|
||||
|
||||
#define SND_FF_REG_MIDI_RX_PORT_0 0x000080180000ull
|
||||
#define SND_FF_REG_MIDI_RX_PORT_1 0x000080190000ull
|
||||
|
||||
int snd_ff_transaction_get_clock(struct snd_ff *ff, unsigned int *rate,
|
||||
enum snd_ff_clock_src *src)
|
||||
{
|
||||
__le32 reg;
|
||||
u32 data;
|
||||
int err;
|
||||
|
||||
err = snd_fw_transaction(ff->unit, TCODE_READ_QUADLET_REQUEST,
|
||||
SND_FF_REG_CLOCK_CONFIG, ®, sizeof(reg), 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
data = le32_to_cpu(reg);
|
||||
|
||||
/* Calculate sampling rate. */
|
||||
switch ((data >> 1) & 0x03) {
|
||||
case 0x01:
|
||||
*rate = 32000;
|
||||
break;
|
||||
case 0x00:
|
||||
*rate = 44100;
|
||||
break;
|
||||
case 0x03:
|
||||
*rate = 48000;
|
||||
break;
|
||||
case 0x02:
|
||||
default:
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (data & 0x08)
|
||||
*rate *= 2;
|
||||
else if (data & 0x10)
|
||||
*rate *= 4;
|
||||
|
||||
/* Calculate source of clock. */
|
||||
if (data & 0x01) {
|
||||
*src = SND_FF_CLOCK_SRC_INTERNAL;
|
||||
} else {
|
||||
/* TODO: 0x02, 0x06, 0x07? */
|
||||
switch ((data >> 10) & 0x07) {
|
||||
case 0x00:
|
||||
*src = SND_FF_CLOCK_SRC_ADAT1;
|
||||
break;
|
||||
case 0x01:
|
||||
*src = SND_FF_CLOCK_SRC_ADAT2;
|
||||
break;
|
||||
case 0x03:
|
||||
*src = SND_FF_CLOCK_SRC_SPDIF;
|
||||
break;
|
||||
case 0x04:
|
||||
*src = SND_FF_CLOCK_SRC_WORD;
|
||||
break;
|
||||
case 0x05:
|
||||
*src = SND_FF_CLOCK_SRC_LTC;
|
||||
break;
|
||||
default:
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void finish_transmit_midi_msg(struct snd_ff *ff, unsigned int port,
|
||||
int rcode)
|
||||
{
|
||||
|
@ -117,23 +51,17 @@ static void finish_transmit_midi1_msg(struct fw_card *card, int rcode,
|
|||
finish_transmit_midi_msg(ff, 1, rcode);
|
||||
}
|
||||
|
||||
static inline void fill_midi_buf(struct snd_ff *ff, unsigned int port,
|
||||
unsigned int index, u8 byte)
|
||||
{
|
||||
ff->msg_buf[port][index] = cpu_to_le32(byte);
|
||||
}
|
||||
|
||||
static void transmit_midi_msg(struct snd_ff *ff, unsigned int port)
|
||||
{
|
||||
struct snd_rawmidi_substream *substream =
|
||||
READ_ONCE(ff->rx_midi_substreams[port]);
|
||||
u8 *buf = (u8 *)ff->msg_buf[port];
|
||||
int i, len;
|
||||
int quad_count;
|
||||
|
||||
struct fw_device *fw_dev = fw_parent_device(ff->unit);
|
||||
unsigned long long addr;
|
||||
int generation;
|
||||
fw_transaction_callback_t callback;
|
||||
int tcode;
|
||||
|
||||
if (substream == NULL || snd_rawmidi_transmit_empty(substream))
|
||||
return;
|
||||
|
@ -147,26 +75,26 @@ static void transmit_midi_msg(struct snd_ff *ff, unsigned int port)
|
|||
return;
|
||||
}
|
||||
|
||||
len = snd_rawmidi_transmit_peek(substream, buf,
|
||||
SND_FF_MAXIMIM_MIDI_QUADS);
|
||||
if (len <= 0)
|
||||
quad_count = ff->spec->protocol->fill_midi_msg(ff, substream, port);
|
||||
if (quad_count <= 0)
|
||||
return;
|
||||
|
||||
for (i = len - 1; i >= 0; i--)
|
||||
fill_midi_buf(ff, port, i, buf[i]);
|
||||
|
||||
if (port == 0) {
|
||||
addr = SND_FF_REG_MIDI_RX_PORT_0;
|
||||
addr = ff->spec->midi_rx_addrs[0];
|
||||
callback = finish_transmit_midi0_msg;
|
||||
} else {
|
||||
addr = SND_FF_REG_MIDI_RX_PORT_1;
|
||||
addr = ff->spec->midi_rx_addrs[1];
|
||||
callback = finish_transmit_midi1_msg;
|
||||
}
|
||||
|
||||
/* Set interval to next transaction. */
|
||||
ff->next_ktime[port] = ktime_add_ns(ktime_get(),
|
||||
len * 8 * NSEC_PER_SEC / 31250);
|
||||
ff->rx_bytes[port] = len;
|
||||
ff->rx_bytes[port] * 8 * NSEC_PER_SEC / 31250);
|
||||
|
||||
if (quad_count == 1)
|
||||
tcode = TCODE_WRITE_QUADLET_REQUEST;
|
||||
else
|
||||
tcode = TCODE_WRITE_BLOCK_REQUEST;
|
||||
|
||||
/*
|
||||
* In Linux FireWire core, when generation is updated with memory
|
||||
|
@ -178,10 +106,9 @@ static void transmit_midi_msg(struct snd_ff *ff, unsigned int port)
|
|||
*/
|
||||
generation = fw_dev->generation;
|
||||
smp_rmb();
|
||||
fw_send_request(fw_dev->card, &ff->transactions[port],
|
||||
TCODE_WRITE_BLOCK_REQUEST,
|
||||
fw_send_request(fw_dev->card, &ff->transactions[port], tcode,
|
||||
fw_dev->node_id, generation, fw_dev->max_speed,
|
||||
addr, &ff->msg_buf[port], len * 4,
|
||||
addr, &ff->msg_buf[port], quad_count * 4,
|
||||
callback, &ff->transactions[port]);
|
||||
}
|
||||
|
||||
|
@ -209,7 +136,9 @@ static void handle_midi_msg(struct fw_card *card, struct fw_request *request,
|
|||
|
||||
fw_send_response(card, request, RCODE_COMPLETE);
|
||||
|
||||
ff->spec->protocol->handle_midi_msg(ff, buf, length);
|
||||
offset -= ff->async_handler.offset;
|
||||
ff->spec->protocol->handle_midi_msg(ff, (unsigned int)offset, buf,
|
||||
length);
|
||||
}
|
||||
|
||||
static int allocate_own_address(struct snd_ff *ff, int i)
|
||||
|
@ -217,7 +146,7 @@ static int allocate_own_address(struct snd_ff *ff, int i)
|
|||
struct fw_address_region midi_msg_region;
|
||||
int err;
|
||||
|
||||
ff->async_handler.length = SND_FF_MAXIMIM_MIDI_QUADS * 4;
|
||||
ff->async_handler.length = ff->spec->midi_addr_range;
|
||||
ff->async_handler.address_callback = handle_midi_msg;
|
||||
ff->async_handler.callback_data = ff;
|
||||
|
||||
|
@ -236,35 +165,13 @@ static int allocate_own_address(struct snd_ff *ff, int i)
|
|||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Controllers are allowed to register higher 4 bytes of address to receive
|
||||
* the transactions. Different models have different registers for this purpose;
|
||||
* e.g. 0x'0000'8010'03f4 for Fireface 400.
|
||||
* The controllers are not allowed to register lower 4 bytes of the address.
|
||||
* They are forced to select one of 4 options for the part of address by writing
|
||||
* corresponding bits to 0x'0000'8010'051f.
|
||||
*
|
||||
* The 3rd-6th bits of this register are flags to indicate lower 4 bytes of
|
||||
* address to which the device transferrs the transactions. In short:
|
||||
* - 0x20: 0x'....'....'0000'0180
|
||||
* - 0x10: 0x'....'....'0000'0100
|
||||
* - 0x08: 0x'....'....'0000'0080
|
||||
* - 0x04: 0x'....'....'0000'0000
|
||||
*
|
||||
* This driver configure 0x'....'....'0000'0000 to receive MIDI messages from
|
||||
* units. The 3rd bit of the register should be configured, however this driver
|
||||
* deligates this task to userspace applications due to a restriction that this
|
||||
* register is write-only and the other bits have own effects.
|
||||
*
|
||||
* Unlike Fireface 800, Fireface 400 cancels transferring asynchronous
|
||||
* transactions when the 1st and 2nd of the register stand. These two bits have
|
||||
* the same effect.
|
||||
* - 0x02, 0x01: cancel transferring
|
||||
*
|
||||
* On the other hand, the bits have no effect on Fireface 800. This model
|
||||
* cancels asynchronous transactions when the higher 4 bytes of address is
|
||||
* overwritten with zero.
|
||||
*/
|
||||
// Controllers are allowed to register higher 4 bytes of destination address to
|
||||
// receive asynchronous transactions for MIDI messages, while the way to
|
||||
// register lower 4 bytes of address is different depending on protocols. For
|
||||
// details, please refer to comments in protocol implementations.
|
||||
//
|
||||
// This driver expects userspace applications to configure registers for the
|
||||
// lower address because in most cases such registers has the other settings.
|
||||
int snd_ff_transaction_reregister(struct snd_ff *ff)
|
||||
{
|
||||
struct fw_card *fw_card = fw_parent_device(ff->unit)->card;
|
||||
|
|
|
@ -153,6 +153,8 @@ static const struct snd_ff_spec spec_ff800 = {
|
|||
.midi_out_ports = 1,
|
||||
.protocol = &snd_ff_protocol_ff800,
|
||||
.midi_high_addr = 0x000200000320ull,
|
||||
.midi_addr_range = 12,
|
||||
.midi_rx_addrs = {0x000080180000ull, 0},
|
||||
};
|
||||
|
||||
static const struct snd_ff_spec spec_ff400 = {
|
||||
|
@ -163,6 +165,20 @@ static const struct snd_ff_spec spec_ff400 = {
|
|||
.midi_out_ports = 2,
|
||||
.protocol = &snd_ff_protocol_ff400,
|
||||
.midi_high_addr = 0x0000801003f4ull,
|
||||
.midi_addr_range = SND_FF_MAXIMIM_MIDI_QUADS * 4,
|
||||
.midi_rx_addrs = {0x000080180000ull, 0x000080190000ull},
|
||||
};
|
||||
|
||||
static const struct snd_ff_spec spec_ucx = {
|
||||
.name = "FirefaceUCX",
|
||||
.pcm_capture_channels = {18, 14, 12},
|
||||
.pcm_playback_channels = {18, 14, 12},
|
||||
.midi_in_ports = 2,
|
||||
.midi_out_ports = 2,
|
||||
.protocol = &snd_ff_protocol_latter,
|
||||
.midi_high_addr = 0xffff00000034ull,
|
||||
.midi_addr_range = 0x80,
|
||||
.midi_rx_addrs = {0xffff00000030ull, 0xffff00000030ull},
|
||||
};
|
||||
|
||||
static const struct ieee1394_device_id snd_ff_id_table[] = {
|
||||
|
@ -190,6 +206,18 @@ static const struct ieee1394_device_id snd_ff_id_table[] = {
|
|||
.model_id = 0x101800,
|
||||
.driver_data = (kernel_ulong_t)&spec_ff400,
|
||||
},
|
||||
// Fireface UCX.
|
||||
{
|
||||
.match_flags = IEEE1394_MATCH_VENDOR_ID |
|
||||
IEEE1394_MATCH_SPECIFIER_ID |
|
||||
IEEE1394_MATCH_VERSION |
|
||||
IEEE1394_MATCH_MODEL_ID,
|
||||
.vendor_id = OUI_RME,
|
||||
.specifier_id = OUI_RME,
|
||||
.version = 0x000004,
|
||||
.model_id = 0x101800,
|
||||
.driver_data = (kernel_ulong_t)&spec_ucx,
|
||||
},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(ieee1394, snd_ff_id_table);
|
||||
|
|
|
@ -35,11 +35,6 @@
|
|||
#define SND_FF_IN_MIDI_PORTS 2
|
||||
#define SND_FF_OUT_MIDI_PORTS 2
|
||||
|
||||
#define SND_FF_REG_SYNC_STATUS 0x0000801c0000ull
|
||||
/* For block write request. */
|
||||
#define SND_FF_REG_FETCH_PCM_FRAMES 0x0000801c0000ull
|
||||
#define SND_FF_REG_CLOCK_CONFIG 0x0000801c0004ull
|
||||
|
||||
enum snd_ff_stream_mode {
|
||||
SND_FF_STREAM_MODE_LOW = 0,
|
||||
SND_FF_STREAM_MODE_MID,
|
||||
|
@ -59,6 +54,8 @@ struct snd_ff_spec {
|
|||
|
||||
const struct snd_ff_protocol *protocol;
|
||||
u64 midi_high_addr;
|
||||
u8 midi_addr_range;
|
||||
u64 midi_rx_addrs[SND_FF_OUT_MIDI_PORTS];
|
||||
};
|
||||
|
||||
struct snd_ff {
|
||||
|
@ -78,7 +75,7 @@ struct snd_ff {
|
|||
|
||||
/* TO handle MIDI rx. */
|
||||
struct snd_rawmidi_substream *rx_midi_substreams[SND_FF_OUT_MIDI_PORTS];
|
||||
u8 running_status[SND_FF_OUT_MIDI_PORTS];
|
||||
bool on_sysex[SND_FF_OUT_MIDI_PORTS];
|
||||
__le32 msg_buf[SND_FF_OUT_MIDI_PORTS][SND_FF_MAXIMIM_MIDI_QUADS];
|
||||
struct work_struct rx_midi_work[SND_FF_OUT_MIDI_PORTS];
|
||||
struct fw_transaction transactions[SND_FF_OUT_MIDI_PORTS];
|
||||
|
@ -108,16 +105,23 @@ enum snd_ff_clock_src {
|
|||
};
|
||||
|
||||
struct snd_ff_protocol {
|
||||
void (*handle_midi_msg)(struct snd_ff *ff, __le32 *buf, size_t length);
|
||||
void (*handle_midi_msg)(struct snd_ff *ff, unsigned int offset,
|
||||
__le32 *buf, size_t length);
|
||||
int (*fill_midi_msg)(struct snd_ff *ff,
|
||||
struct snd_rawmidi_substream *substream,
|
||||
unsigned int port);
|
||||
int (*get_clock)(struct snd_ff *ff, unsigned int *rate,
|
||||
enum snd_ff_clock_src *src);
|
||||
int (*switch_fetching_mode)(struct snd_ff *ff, bool enable);
|
||||
int (*begin_session)(struct snd_ff *ff, unsigned int rate);
|
||||
void (*finish_session)(struct snd_ff *ff);
|
||||
void (*dump_status)(struct snd_ff *ff, struct snd_info_buffer *buffer);
|
||||
};
|
||||
|
||||
extern const struct snd_ff_protocol snd_ff_protocol_ff800;
|
||||
extern const struct snd_ff_protocol snd_ff_protocol_ff400;
|
||||
extern const struct snd_ff_protocol snd_ff_protocol_latter;
|
||||
|
||||
int snd_ff_transaction_get_clock(struct snd_ff *ff, unsigned int *rate,
|
||||
enum snd_ff_clock_src *src);
|
||||
int snd_ff_transaction_register(struct snd_ff *ff);
|
||||
int snd_ff_transaction_reregister(struct snd_ff *ff);
|
||||
void snd_ff_transaction_unregister(struct snd_ff *ff);
|
||||
|
@ -142,6 +146,7 @@ int snd_ff_stream_lock_try(struct snd_ff *ff);
|
|||
void snd_ff_stream_lock_release(struct snd_ff *ff);
|
||||
|
||||
void snd_ff_proc_init(struct snd_ff *ff);
|
||||
const char *snd_ff_proc_get_clk_label(enum snd_ff_clock_src src);
|
||||
|
||||
int snd_ff_create_midi_devices(struct snd_ff *ff);
|
||||
|
||||
|
|
|
@ -199,12 +199,8 @@ add_node(struct snd_efw *efw, struct snd_info_entry *root, const char *name,
|
|||
struct snd_info_entry *entry;
|
||||
|
||||
entry = snd_info_create_card_entry(efw->card, name, root);
|
||||
if (entry == NULL)
|
||||
return;
|
||||
|
||||
snd_info_set_text_ops(entry, efw, op);
|
||||
if (snd_info_register(entry) < 0)
|
||||
snd_info_free_entry(entry);
|
||||
if (entry)
|
||||
snd_info_set_text_ops(entry, efw, op);
|
||||
}
|
||||
|
||||
void snd_efw_proc_init(struct snd_efw *efw)
|
||||
|
@ -220,10 +216,6 @@ void snd_efw_proc_init(struct snd_efw *efw)
|
|||
if (root == NULL)
|
||||
return;
|
||||
root->mode = S_IFDIR | 0555;
|
||||
if (snd_info_register(root) < 0) {
|
||||
snd_info_free_entry(root);
|
||||
return;
|
||||
}
|
||||
|
||||
add_node(efw, root, "clock", proc_read_clock);
|
||||
add_node(efw, root, "firmware", proc_read_hwinfo);
|
||||
|
|
|
@ -136,7 +136,9 @@ static void read_pcm_s32(struct amdtp_stream *s,
|
|||
byte = (u8 *)buffer + p->pcm_byte_offset;
|
||||
|
||||
for (c = 0; c < channels; ++c) {
|
||||
*dst = (byte[0] << 24) | (byte[1] << 16) | byte[2];
|
||||
*dst = (byte[0] << 24) |
|
||||
(byte[1] << 16) |
|
||||
(byte[2] << 8);
|
||||
byte += 3;
|
||||
dst++;
|
||||
}
|
||||
|
|
|
@ -87,12 +87,8 @@ static void add_node(struct snd_motu *motu, struct snd_info_entry *root,
|
|||
struct snd_info_entry *entry;
|
||||
|
||||
entry = snd_info_create_card_entry(motu->card, name, root);
|
||||
if (entry == NULL)
|
||||
return;
|
||||
|
||||
snd_info_set_text_ops(entry, motu, op);
|
||||
if (snd_info_register(entry) < 0)
|
||||
snd_info_free_entry(entry);
|
||||
if (entry)
|
||||
snd_info_set_text_ops(entry, motu, op);
|
||||
}
|
||||
|
||||
void snd_motu_proc_init(struct snd_motu *motu)
|
||||
|
@ -108,10 +104,6 @@ void snd_motu_proc_init(struct snd_motu *motu)
|
|||
if (root == NULL)
|
||||
return;
|
||||
root->mode = S_IFDIR | 0555;
|
||||
if (snd_info_register(root) < 0) {
|
||||
snd_info_free_entry(root);
|
||||
return;
|
||||
}
|
||||
|
||||
add_node(motu, root, "clock", proc_read_clock);
|
||||
add_node(motu, root, "format", proc_read_format);
|
||||
|
|
|
@ -83,12 +83,8 @@ static void add_node(struct snd_oxfw *oxfw, struct snd_info_entry *root,
|
|||
struct snd_info_entry *entry;
|
||||
|
||||
entry = snd_info_create_card_entry(oxfw->card, name, root);
|
||||
if (entry == NULL)
|
||||
return;
|
||||
|
||||
snd_info_set_text_ops(entry, oxfw, op);
|
||||
if (snd_info_register(entry) < 0)
|
||||
snd_info_free_entry(entry);
|
||||
if (entry)
|
||||
snd_info_set_text_ops(entry, oxfw, op);
|
||||
}
|
||||
|
||||
void snd_oxfw_proc_init(struct snd_oxfw *oxfw)
|
||||
|
@ -104,10 +100,6 @@ void snd_oxfw_proc_init(struct snd_oxfw *oxfw)
|
|||
if (root == NULL)
|
||||
return;
|
||||
root->mode = S_IFDIR | 0555;
|
||||
if (snd_info_register(root) < 0) {
|
||||
snd_info_free_entry(root);
|
||||
return;
|
||||
}
|
||||
|
||||
add_node(oxfw, root, "formation", proc_read_formation);
|
||||
}
|
||||
|
|
|
@ -58,12 +58,8 @@ static void add_node(struct snd_tscm *tscm, struct snd_info_entry *root,
|
|||
struct snd_info_entry *entry;
|
||||
|
||||
entry = snd_info_create_card_entry(tscm->card, name, root);
|
||||
if (entry == NULL)
|
||||
return;
|
||||
|
||||
snd_info_set_text_ops(entry, tscm, op);
|
||||
if (snd_info_register(entry) < 0)
|
||||
snd_info_free_entry(entry);
|
||||
if (entry)
|
||||
snd_info_set_text_ops(entry, tscm, op);
|
||||
}
|
||||
|
||||
void snd_tscm_proc_init(struct snd_tscm *tscm)
|
||||
|
@ -79,10 +75,6 @@ void snd_tscm_proc_init(struct snd_tscm *tscm)
|
|||
if (root == NULL)
|
||||
return;
|
||||
root->mode = S_IFDIR | 0555;
|
||||
if (snd_info_register(root) < 0) {
|
||||
snd_info_free_entry(root);
|
||||
return;
|
||||
}
|
||||
|
||||
add_node(tscm, root, "firmware", proc_read_firmware);
|
||||
}
|
||||
|
|
|
@ -376,7 +376,7 @@ void snd_hdac_bus_exit_link_reset(struct hdac_bus *bus)
|
|||
{
|
||||
unsigned long timeout;
|
||||
|
||||
snd_hdac_chip_updateb(bus, GCTL, 0, AZX_GCTL_RESET);
|
||||
snd_hdac_chip_updateb(bus, GCTL, AZX_GCTL_RESET, AZX_GCTL_RESET);
|
||||
|
||||
timeout = jiffies + msecs_to_jiffies(100);
|
||||
while (!snd_hdac_chip_readb(bus, GCTL) && time_before(jiffies, timeout))
|
||||
|
@ -415,7 +415,7 @@ int snd_hdac_bus_reset_link(struct hdac_bus *bus, bool full_reset)
|
|||
}
|
||||
|
||||
/* Accept unsolicited responses */
|
||||
snd_hdac_chip_updatel(bus, GCTL, 0, AZX_GCTL_UNSOL);
|
||||
snd_hdac_chip_updatel(bus, GCTL, AZX_GCTL_UNSOL, AZX_GCTL_UNSOL);
|
||||
|
||||
/* detect codecs */
|
||||
if (!bus->codec_mask) {
|
||||
|
@ -431,7 +431,9 @@ EXPORT_SYMBOL_GPL(snd_hdac_bus_reset_link);
|
|||
static void azx_int_enable(struct hdac_bus *bus)
|
||||
{
|
||||
/* enable controller CIE and GIE */
|
||||
snd_hdac_chip_updatel(bus, INTCTL, 0, AZX_INT_CTRL_EN | AZX_INT_GLOBAL_EN);
|
||||
snd_hdac_chip_updatel(bus, INTCTL,
|
||||
AZX_INT_CTRL_EN | AZX_INT_GLOBAL_EN,
|
||||
AZX_INT_CTRL_EN | AZX_INT_GLOBAL_EN);
|
||||
}
|
||||
|
||||
/* disable interrupts */
|
||||
|
|
|
@ -144,9 +144,9 @@ int snd_hdac_i915_init(struct hdac_bus *bus)
|
|||
return -ENODEV;
|
||||
if (!acomp->ops) {
|
||||
request_module("i915");
|
||||
/* 10s timeout */
|
||||
/* 60s timeout */
|
||||
wait_for_completion_timeout(&bind_complete,
|
||||
msecs_to_jiffies(10 * 1000));
|
||||
msecs_to_jiffies(60 * 1000));
|
||||
}
|
||||
if (!acomp->ops) {
|
||||
dev_info(bus->dev, "couldn't bind with audio component\n");
|
||||
|
|
|
@ -12,6 +12,40 @@
|
|||
#include <sound/hda_register.h>
|
||||
#include "trace.h"
|
||||
|
||||
/**
|
||||
* snd_hdac_get_stream_stripe_ctl - get stripe control value
|
||||
* @bus: HD-audio core bus
|
||||
* @substream: PCM substream
|
||||
*/
|
||||
int snd_hdac_get_stream_stripe_ctl(struct hdac_bus *bus,
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
unsigned int channels = runtime->channels,
|
||||
rate = runtime->rate,
|
||||
bits_per_sample = runtime->sample_bits,
|
||||
max_sdo_lines, value, sdo_line;
|
||||
|
||||
/* T_AZA_GCAP_NSDO is 1:2 bitfields in GCAP */
|
||||
max_sdo_lines = snd_hdac_chip_readl(bus, GCAP) & AZX_GCAP_NSDO;
|
||||
|
||||
/* following is from HD audio spec */
|
||||
for (sdo_line = max_sdo_lines; sdo_line > 0; sdo_line >>= 1) {
|
||||
if (rate > 48000)
|
||||
value = (channels * bits_per_sample *
|
||||
(rate / 48000)) / sdo_line;
|
||||
else
|
||||
value = (channels * bits_per_sample) / sdo_line;
|
||||
|
||||
if (value >= 8)
|
||||
break;
|
||||
}
|
||||
|
||||
/* stripe value: 0 for 1SDO, 1 for 2SDO, 2 for 4SDO lines */
|
||||
return sdo_line >> 1;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hdac_get_stream_stripe_ctl);
|
||||
|
||||
/**
|
||||
* snd_hdac_stream_init - initialize each stream (aka device)
|
||||
* @bus: HD-audio core bus
|
||||
|
@ -48,6 +82,7 @@ EXPORT_SYMBOL_GPL(snd_hdac_stream_init);
|
|||
void snd_hdac_stream_start(struct hdac_stream *azx_dev, bool fresh_start)
|
||||
{
|
||||
struct hdac_bus *bus = azx_dev->bus;
|
||||
int stripe_ctl;
|
||||
|
||||
trace_snd_hdac_stream_start(bus, azx_dev);
|
||||
|
||||
|
@ -56,7 +91,13 @@ void snd_hdac_stream_start(struct hdac_stream *azx_dev, bool fresh_start)
|
|||
azx_dev->start_wallclk -= azx_dev->period_wallclk;
|
||||
|
||||
/* enable SIE */
|
||||
snd_hdac_chip_updatel(bus, INTCTL, 0, 1 << azx_dev->index);
|
||||
snd_hdac_chip_updatel(bus, INTCTL,
|
||||
1 << azx_dev->index,
|
||||
1 << azx_dev->index);
|
||||
/* set stripe control */
|
||||
stripe_ctl = snd_hdac_get_stream_stripe_ctl(bus, azx_dev->substream);
|
||||
snd_hdac_stream_updateb(azx_dev, SD_CTL_3B, SD_CTL_STRIPE_MASK,
|
||||
stripe_ctl);
|
||||
/* set DMA start and interrupt mask */
|
||||
snd_hdac_stream_updateb(azx_dev, SD_CTL,
|
||||
0, SD_CTL_DMA_START | SD_INT_MASK);
|
||||
|
@ -73,6 +114,7 @@ void snd_hdac_stream_clear(struct hdac_stream *azx_dev)
|
|||
snd_hdac_stream_updateb(azx_dev, SD_CTL,
|
||||
SD_CTL_DMA_START | SD_INT_MASK, 0);
|
||||
snd_hdac_stream_writeb(azx_dev, SD_STS, SD_INT_MASK); /* to be sure */
|
||||
snd_hdac_stream_updateb(azx_dev, SD_CTL_3B, SD_CTL_STRIPE_MASK, 0);
|
||||
azx_dev->running = false;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_hdac_stream_clear);
|
||||
|
|
|
@ -492,9 +492,8 @@ static void snd_ak4113_proc_regs_read(struct snd_info_entry *entry,
|
|||
|
||||
static void snd_ak4113_proc_init(struct ak4113 *ak4113)
|
||||
{
|
||||
struct snd_info_entry *entry;
|
||||
if (!snd_card_proc_new(ak4113->card, "ak4113", &entry))
|
||||
snd_info_set_text_ops(entry, ak4113, snd_ak4113_proc_regs_read);
|
||||
snd_card_ro_proc_new(ak4113->card, "ak4113", ak4113,
|
||||
snd_ak4113_proc_regs_read);
|
||||
}
|
||||
|
||||
int snd_ak4113_build(struct ak4113 *ak4113,
|
||||
|
|
|
@ -465,9 +465,8 @@ static void snd_ak4114_proc_regs_read(struct snd_info_entry *entry,
|
|||
|
||||
static void snd_ak4114_proc_init(struct ak4114 *ak4114)
|
||||
{
|
||||
struct snd_info_entry *entry;
|
||||
if (!snd_card_proc_new(ak4114->card, "ak4114", &entry))
|
||||
snd_info_set_text_ops(entry, ak4114, snd_ak4114_proc_regs_read);
|
||||
snd_card_ro_proc_new(ak4114->card, "ak4114", ak4114,
|
||||
snd_ak4114_proc_regs_read);
|
||||
}
|
||||
|
||||
int snd_ak4114_build(struct ak4114 *ak4114,
|
||||
|
|
|
@ -875,13 +875,7 @@ static void proc_regs_read(struct snd_info_entry *entry,
|
|||
|
||||
static int proc_init(struct snd_akm4xxx *ak)
|
||||
{
|
||||
struct snd_info_entry *entry;
|
||||
int err;
|
||||
err = snd_card_proc_new(ak->card, ak->name, &entry);
|
||||
if (err < 0)
|
||||
return err;
|
||||
snd_info_set_text_ops(entry, ak, proc_regs_read);
|
||||
return 0;
|
||||
return snd_card_ro_proc_new(ak->card, ak->name, ak, proc_regs_read);
|
||||
}
|
||||
|
||||
int snd_akm4xxx_build_controls(struct snd_akm4xxx *ak)
|
||||
|
|
|
@ -518,7 +518,6 @@ void snd_ad1816a_suspend(struct snd_ad1816a *chip)
|
|||
int reg;
|
||||
unsigned long flags;
|
||||
|
||||
snd_pcm_suspend_all(chip->pcm);
|
||||
spin_lock_irqsave(&chip->lock, flags);
|
||||
for (reg = 0; reg < 48; reg++)
|
||||
chip->image[reg] = snd_ad1816a_read(chip, reg);
|
||||
|
@ -694,7 +693,7 @@ int snd_ad1816a_pcm(struct snd_ad1816a *chip, int device)
|
|||
snd_ad1816a_init(chip);
|
||||
|
||||
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
|
||||
snd_dma_isa_data(),
|
||||
chip->card->dev,
|
||||
64*1024, chip->dma1 > 3 || chip->dma2 > 3 ? 128*1024 : 64*1024);
|
||||
|
||||
chip->pcm = pcm;
|
||||
|
|
|
@ -322,7 +322,6 @@ static int snd_als100_pnp_suspend(struct pnp_card_link *pcard, pm_message_t stat
|
|||
struct snd_sb *chip = acard->chip;
|
||||
|
||||
snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
|
||||
snd_pcm_suspend_all(chip->pcm);
|
||||
snd_sbmixer_suspend(chip);
|
||||
return 0;
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue