sound updates for 3.19-rc1
This became a fairly large pull request. In addition to the usual driver updates / fixes, there have been a high amount of cleanups in ASoC area, as well as control API helpers and kernel documentations fixes touching through the whole tree. In the driver side, the biggest changes are the support for new Intel SoC found on new x86 machines, and the updates of FireWire dice and oxfw drivers. Some remarkable items are below: * ALSA core - PCM mmap code cleanup, removal of arch-dependent codes - PCM xrun injection support - PCM hwptr tracepoint support - Refactoring of snd_pcm_action(), simplification of PCM locking - Robustified sequecner auto-load functionality - New control API helpers and lots of cleanups along with them - Lots of kerneldoc fixes and cleanups * USB-audio - The mixer resume code was largely rewritten, and the devices with quirks are resumed properly. - New hardware support: Focusrite Scarlett, Digidesign Mbox1, Denon/Marantz DACs, Zoom R16/24 * FireWire - DICE driver updates with better duplex and sync support, including MIDI support - New OXFW driver for Oxford Semiconductor FW970/971 chipset, including the previous LaCie Speakers device. Fullduplex and MIDI support included as well as DICE driver. * HD-audio - Refactoring the driver-caps quirk handling in snd-hda-intel - More consistent control names representing the topology better - Fixups: HP mute LED with ALC268 codec, Ideapad S210 built-in mic fix, ASUS Z99He laptop EAPD * ASoC - Conversion of AC'97 drivers to use regmap, bringing us closer to the removal of the ASoC level I/O code - Clean up a lot of old drivers that were open coding things that have subsequently been implemented in the core - Some DAPM performance improvements - Removal of the now seldom used CODEC mutex - Lots of updates for the newer Intel SoC support, including support for the DSP and some Cherrytrail and Braswell machine drivers - Support for Samsung boards using rt5631 as the CODEC - Removal of the obsolete AFEB9260 machine driver - Driver support for the TI TS3A227E headset driver used in some Chrombeooks * Others - ASIHPI driver update and cleanups - Lots of dev_*() printk conversions - Lots of trivial cleanups for the codes spotted by Coccinelle -----BEGIN PGP SIGNATURE----- Version: GnuPG v2 iQIcBAABAgAGBQJUiYaqAAoJEGwxgFQ9KSmkeo0P/2aDx2w8iVi8n7Og/7VBubkm VZkk08IOpP3h1ojyQRsBQPI0H5AquqQTZN1TJUDcy+6PD9vckYYcag9JWhA+0RBr I+BfTMLB3E4umIkzOjxeoyOzheL7GoZ+eZYEm8DkAhaue+cFhjNJz+S6g8ENkxJ9 lSjErXQxyiowc39I0v1WBZcuq6glX1psEsVup9U8m7KhNx6lexj28A2MkqicW4hs DZE6pYrk57W7y3+/NWxaBiglrItvScBAPpPqoyDm9zuDNTmAtGjf1uMRmRyHe30Z iunHXki8Fc2yBBapmfYrcLC2jyIyZykcxniF8Hd4nXUvddisFUEFFhNmB6v392d0 4/NXSqTnsq48vm0Ezjia2LySWKZZVQtam8t9262BKHcosKYObxirekD6vijSoWO8 ZWoXa+U1oWSFEoOAFDsu6GFqFHFRi5VhqBgIaPEIxrT2MQGHL3KU1bp8CJi/5CTU pNh0wC9SMtnSJJXBIP/nYH81WQxaik3c4eiHFPN4+0McBZQiIaIqMG6x+iiVNvPB MNLLVAzk0QiWeCmSo8OBdjOV0/T+pfQ7lrTCn2B1jdJi1CkAO8m2SwQrG4PpRx8k lUTBd4zTx5DYR+yPF69OyoCQg0XKjW9g62Qo5rmxrQreiidROZOBS1bljWzIPeft otupLmK5kz67n3eB2eto =sB6v -----END PGP SIGNATURE----- Merge tag 'sound-3.19-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound Pull sound updates from Takashi Iwai: "This became a fairly large pull request. In addition to the usual driver updates / fixes, there have been a high amount of cleanups in ASoC area, as well as control API helpers and kernel documentations fixes touching through the whole tree. In the driver side, the biggest changes are the support for new Intel SoC found on new x86 machines, and the updates of FireWire dice and oxfw drivers. Some remarkable items are below: ALSA core: - PCM mmap code cleanup, removal of arch-dependent codes - PCM xrun injection support - PCM hwptr tracepoint support - Refactoring of snd_pcm_action(), simplification of PCM locking - Robustified sequecner auto-load functionality - New control API helpers and lots of cleanups along with them - Lots of kerneldoc fixes and cleanups USB-audio: - The mixer resume code was largely rewritten, and the devices with quirks are resumed properly. - New hardware support: Focusrite Scarlett, Digidesign Mbox1, Denon/Marantz DACs, Zoom R16/24 FireWire: - DICE driver updates with better duplex and sync support, including MIDI support - New OXFW driver for Oxford Semiconductor FW970/971 chipset, including the previous LaCie Speakers device. Fullduplex and MIDI support included as well as DICE driver. HD-audio: - Refactoring the driver-caps quirk handling in snd-hda-intel - More consistent control names representing the topology better - Fixups: HP mute LED with ALC268 codec, Ideapad S210 built-in mic fix, ASUS Z99He laptop EAPD ASoC: - Conversion of AC'97 drivers to use regmap, bringing us closer to the removal of the ASoC level I/O code - Clean up a lot of old drivers that were open coding things that have subsequently been implemented in the core - Some DAPM performance improvements - Removal of the now seldom used CODEC mutex - Lots of updates for the newer Intel SoC support, including support for the DSP and some Cherrytrail and Braswell machine drivers - Support for Samsung boards using rt5631 as the CODEC - Removal of the obsolete AFEB9260 machine driver - Driver support for the TI TS3A227E headset driver used in some Chrombeooks Others: - ASIHPI driver update and cleanups - Lots of dev_*() printk conversions - Lots of trivial cleanups for the codes spotted by Coccinelle" * tag 'sound-3.19-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound: (594 commits) ALSA: pcxhr: NULL dereference on probe failure ALSA: lola: NULL dereference on probe failure ALSA: hda - Add "eapd" model string for AD1986A codec ALSA: hda - Add EAPD fixup for ASUS Z99He laptop ALSA: oxfw: Add hwdep interface ALSA: oxfw: Add support for capture/playback MIDI messages ALSA: oxfw: add support for capturing PCM samples ALSA: oxfw: Add support AMDTP in-stream ALSA: oxfw: Add support for Behringer/Mackie devices ALSA: oxfw: Change the way to start stream ALSA: oxfw: Add proc interface for debugging purpose ALSA: oxfw: Change the way to make PCM rules/constraints ALSA: oxfw: Add support for AV/C stream format command to get/set supported stream formation ALSA: oxfw: Change the way to name card ALSA: dice: Add support for MIDI capture/playback ALSA: dice: Add support for capturing PCM samples ALSA: dice: Support for non SYT-Match sampling clock source mode ALSA: dice: Add support for duplex streams with synchronization ALSA: dice: Change the way to start stream ALSA: jack: Add dummy snd_jack_set_key() definition ...
This commit is contained in:
Коммит
bae41e45b7
|
@ -57,6 +57,7 @@
|
|||
!Esound/core/pcm.c
|
||||
!Esound/core/pcm_lib.c
|
||||
!Esound/core/pcm_native.c
|
||||
!Iinclude/sound/pcm.h
|
||||
</sect1>
|
||||
<sect1><title>PCM Format Helpers</title>
|
||||
!Esound/core/pcm_misc.c
|
||||
|
@ -64,6 +65,10 @@
|
|||
<sect1><title>PCM Memory Management</title>
|
||||
!Esound/core/pcm_memory.c
|
||||
</sect1>
|
||||
<sect1><title>PCM DMA Engine API</title>
|
||||
!Esound/core/pcm_dmaengine.c
|
||||
!Iinclude/sound/dmaengine_pcm.h
|
||||
</sect1>
|
||||
</chapter>
|
||||
<chapter><title>Control/Mixer API</title>
|
||||
<sect1><title>General Control Interface</title>
|
||||
|
@ -91,12 +96,38 @@
|
|||
!Esound/core/info.c
|
||||
</sect1>
|
||||
</chapter>
|
||||
<chapter><title>Compress Offload</title>
|
||||
<sect1><title>Compress Offload API</title>
|
||||
!Esound/core/compress_offload.c
|
||||
!Iinclude/uapi/sound/compress_offload.h
|
||||
!Iinclude/uapi/sound/compress_params.h
|
||||
!Iinclude/sound/compress_driver.h
|
||||
</sect1>
|
||||
</chapter>
|
||||
<chapter><title>ASoC</title>
|
||||
<sect1><title>ASoC Core API</title>
|
||||
!Iinclude/sound/soc.h
|
||||
!Esound/soc/soc-core.c
|
||||
!Esound/soc/soc-cache.c
|
||||
!Esound/soc/soc-devres.c
|
||||
!Esound/soc/soc-io.c
|
||||
!Esound/soc/soc-pcm.c
|
||||
</sect1>
|
||||
<sect1><title>ASoC DAPM API</title>
|
||||
!Esound/soc/soc-dapm.c
|
||||
</sect1>
|
||||
<sect1><title>ASoC DMA Engine API</title>
|
||||
!Esound/soc/soc-generic-dmaengine-pcm.c
|
||||
</sect1>
|
||||
</chapter>
|
||||
<chapter><title>Miscellaneous Functions</title>
|
||||
<sect1><title>Hardware-Dependent Devices API</title>
|
||||
!Esound/core/hwdep.c
|
||||
</sect1>
|
||||
<sect1><title>Jack Abstraction Layer API</title>
|
||||
!Iinclude/sound/jack.h
|
||||
!Esound/core/jack.c
|
||||
!Esound/soc/soc-jack.c
|
||||
</sect1>
|
||||
<sect1><title>ISA DMA Helpers</title>
|
||||
!Esound/core/isadma.c
|
||||
|
|
|
@ -3657,6 +3657,29 @@ struct _snd_pcm_runtime {
|
|||
</informalexample>
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The above callback can be simplified with a helper function,
|
||||
<function>snd_ctl_enum_info</function>. The final code
|
||||
looks like below.
|
||||
(You can pass ARRAY_SIZE(texts) instead of 4 in the third
|
||||
argument; it's a matter of taste.)
|
||||
|
||||
<informalexample>
|
||||
<programlisting>
|
||||
<![CDATA[
|
||||
static int snd_myctl_enum_info(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_info *uinfo)
|
||||
{
|
||||
static char *texts[4] = {
|
||||
"First", "Second", "Third", "Fourth"
|
||||
};
|
||||
return snd_ctl_enum_info(uinfo, 1, 4, texts);
|
||||
}
|
||||
]]>
|
||||
</programlisting>
|
||||
</informalexample>
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Some common info callbacks are available for your convenience:
|
||||
<function>snd_ctl_boolean_mono_info()</function> and
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
Audio Binding for Arndale boards
|
||||
|
||||
Required properties:
|
||||
- compatible : Can be the following,
|
||||
"samsung,arndale-rt5631"
|
||||
|
||||
- samsung,audio-cpu: The phandle of the Samsung I2S controller
|
||||
- samsung,audio-codec: The phandle of the audio codec
|
||||
|
||||
Optional:
|
||||
- samsung,model: The name of the sound-card
|
||||
|
||||
Arndale Boards has many audio daughter cards, one of them is
|
||||
rt5631/alc5631. Below example shows audio bindings for rt5631/
|
||||
alc5631 based codec.
|
||||
|
||||
Example:
|
||||
|
||||
sound {
|
||||
compatible = "samsung,arndale-rt5631";
|
||||
|
||||
samsung,audio-cpu = <&i2s0>
|
||||
samsung,audio-codec = <&rt5631>;
|
||||
};
|
|
@ -32,7 +32,7 @@ Optional properties:
|
|||
- rx-num-evt : FIFO levels.
|
||||
- sram-size-playback : size of sram to be allocated during playback
|
||||
- sram-size-capture : size of sram to be allocated during capture
|
||||
- interrupts : Interrupt numbers for McASP, currently not used by the driver
|
||||
- interrupts : Interrupt numbers for McASP
|
||||
- interrupt-names : Known interrupt names are "tx" and "rx"
|
||||
- pinctrl-0: Should specify pin control group used for this controller.
|
||||
- pinctrl-names: Should contain only one value - "default", for more details
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
Audio complex for Eukrea boards with tlv320aic23 codec.
|
||||
|
||||
Required properties:
|
||||
- compatible : "eukrea,asoc-tlv320"
|
||||
- eukrea,model : The user-visible name of this sound complex.
|
||||
- ssi-controller : The phandle of the SSI controller.
|
||||
- fsl,mux-int-port : The internal port of the i.MX audio muxer (AUDMUX).
|
||||
- fsl,mux-ext-port : The external port of the i.MX audio muxer.
|
||||
|
||||
- compatible : "eukrea,asoc-tlv320"
|
||||
|
||||
- eukrea,model : The user-visible name of this sound complex.
|
||||
|
||||
- ssi-controller : The phandle of the SSI controller.
|
||||
|
||||
- fsl,mux-int-port : The internal port of the i.MX audio muxer (AUDMUX).
|
||||
|
||||
- fsl,mux-ext-port : The external port of the i.MX audio muxer.
|
||||
|
||||
Note: The AUDMUX port numbering should start at 1, which is consistent with
|
||||
hardware manual.
|
||||
|
|
|
@ -7,37 +7,39 @@ other DSPs. It has up to six transmitters and four receivers.
|
|||
|
||||
Required properties:
|
||||
|
||||
- compatible : Compatible list, must contain "fsl,imx35-esai" or
|
||||
"fsl,vf610-esai"
|
||||
- compatible : Compatible list, must contain "fsl,imx35-esai" or
|
||||
"fsl,vf610-esai"
|
||||
|
||||
- reg : Offset and length of the register set for the device.
|
||||
- reg : Offset and length of the register set for the device.
|
||||
|
||||
- interrupts : Contains the spdif interrupt.
|
||||
- interrupts : Contains the spdif interrupt.
|
||||
|
||||
- dmas : Generic dma devicetree binding as described in
|
||||
Documentation/devicetree/bindings/dma/dma.txt.
|
||||
- dmas : Generic dma devicetree binding as described in
|
||||
Documentation/devicetree/bindings/dma/dma.txt.
|
||||
|
||||
- dma-names : Two dmas have to be defined, "tx" and "rx".
|
||||
- dma-names : Two dmas have to be defined, "tx" and "rx".
|
||||
|
||||
- clocks: Contains an entry for each entry in clock-names.
|
||||
- clocks : Contains an entry for each entry in clock-names.
|
||||
|
||||
- clock-names : Includes the following entries:
|
||||
"core" The core clock used to access registers
|
||||
"extal" The esai baud clock for esai controller used to derive
|
||||
HCK, SCK and FS.
|
||||
"fsys" The system clock derived from ahb clock used to derive
|
||||
HCK, SCK and FS.
|
||||
- clock-names : Includes the following entries:
|
||||
"core" The core clock used to access registers
|
||||
"extal" The esai baud clock for esai controller used to
|
||||
derive HCK, SCK and FS.
|
||||
"fsys" The system clock derived from ahb clock used to
|
||||
derive HCK, SCK and FS.
|
||||
|
||||
- fsl,fifo-depth: The number of elements in the transmit and receive FIFOs.
|
||||
This number is the maximum allowed value for TFCR[TFWM] or RFCR[RFWM].
|
||||
- fsl,fifo-depth : The number of elements in the transmit and receive
|
||||
FIFOs. This number is the maximum allowed value for
|
||||
TFCR[TFWM] or RFCR[RFWM].
|
||||
|
||||
- fsl,esai-synchronous: This is a boolean property. If present, indicating
|
||||
that ESAI would work in the synchronous mode, which means all the settings
|
||||
for Receiving would be duplicated from Transmition related registers.
|
||||
that ESAI would work in the synchronous mode, which
|
||||
means all the settings for Receiving would be
|
||||
duplicated from Transmition related registers.
|
||||
|
||||
- big-endian : If this property is absent, the native endian mode will
|
||||
be in use as default, or the big endian mode will be in use for all the
|
||||
device registers.
|
||||
- big-endian : If this property is absent, the native endian mode
|
||||
will be in use as default, or the big endian mode
|
||||
will be in use for all the device registers.
|
||||
|
||||
Example:
|
||||
|
||||
|
|
|
@ -6,32 +6,31 @@ a fibre cable.
|
|||
|
||||
Required properties:
|
||||
|
||||
- compatible : Compatible list, must contain "fsl,imx35-spdif".
|
||||
- compatible : Compatible list, must contain "fsl,imx35-spdif".
|
||||
|
||||
- reg : Offset and length of the register set for the device.
|
||||
- reg : Offset and length of the register set for the device.
|
||||
|
||||
- interrupts : Contains the spdif interrupt.
|
||||
- interrupts : Contains the spdif interrupt.
|
||||
|
||||
- dmas : Generic dma devicetree binding as described in
|
||||
Documentation/devicetree/bindings/dma/dma.txt.
|
||||
- dmas : Generic dma devicetree binding as described in
|
||||
Documentation/devicetree/bindings/dma/dma.txt.
|
||||
|
||||
- dma-names : Two dmas have to be defined, "tx" and "rx".
|
||||
- dma-names : Two dmas have to be defined, "tx" and "rx".
|
||||
|
||||
- clocks : Contains an entry for each entry in clock-names.
|
||||
- clocks : Contains an entry for each entry in clock-names.
|
||||
|
||||
- clock-names : Includes the following entries:
|
||||
"core" The core clock of spdif controller
|
||||
"rxtx<0-7>" Clock source list for tx and rx clock.
|
||||
This clock list should be identical to
|
||||
the source list connecting to the spdif
|
||||
clock mux in "SPDIF Transceiver Clock
|
||||
Diagram" of SoC reference manual. It
|
||||
can also be referred to TxClk_Source
|
||||
bit of register SPDIF_STC.
|
||||
- clock-names : Includes the following entries:
|
||||
"core" The core clock of spdif controller.
|
||||
"rxtx<0-7>" Clock source list for tx and rx clock.
|
||||
This clock list should be identical to the source
|
||||
list connecting to the spdif clock mux in "SPDIF
|
||||
Transceiver Clock Diagram" of SoC reference manual.
|
||||
It can also be referred to TxClk_Source bit of
|
||||
register SPDIF_STC.
|
||||
|
||||
- big-endian : If this property is absent, the native endian mode will
|
||||
be in use as default, or the big endian mode will be in use for all the
|
||||
device registers.
|
||||
- big-endian : If this property is absent, the native endian mode
|
||||
will be in use as default, or the big endian mode
|
||||
will be in use for all the device registers.
|
||||
|
||||
Example:
|
||||
|
||||
|
|
|
@ -5,32 +5,48 @@ which provides a synchronous audio interface that supports fullduplex
|
|||
serial interfaces with frame synchronization such as I2S, AC97, TDM, and
|
||||
codec/DSP interfaces.
|
||||
|
||||
|
||||
Required properties:
|
||||
- compatible: Compatible list, contains "fsl,vf610-sai" or "fsl,imx6sx-sai".
|
||||
- reg: Offset and length of the register set for the device.
|
||||
- clocks: Must contain an entry for each entry in clock-names.
|
||||
- clock-names : Must include the "bus" for register access and "mclk1" "mclk2"
|
||||
"mclk3" for bit clock and frame clock providing.
|
||||
- dmas : Generic dma devicetree binding as described in
|
||||
Documentation/devicetree/bindings/dma/dma.txt.
|
||||
- dma-names : Two dmas have to be defined, "tx" and "rx".
|
||||
- pinctrl-names: Must contain a "default" entry.
|
||||
- pinctrl-NNN: One property must exist for each entry in pinctrl-names.
|
||||
See ../pinctrl/pinctrl-bindings.txt for details of the property values.
|
||||
- big-endian: Boolean property, required if all the FTM_PWM registers
|
||||
are big-endian rather than little-endian.
|
||||
- lsb-first: Configures whether the LSB or the MSB is transmitted first for
|
||||
the fifo data. If this property is absent, the MSB is transmitted first as
|
||||
default, or the LSB is transmitted first.
|
||||
- fsl,sai-synchronous-rx: This is a boolean property. If present, indicating
|
||||
that SAI will work in the synchronous mode (sync Tx with Rx) which means
|
||||
both the transimitter and receiver will send and receive data by following
|
||||
receiver's bit clocks and frame sync clocks.
|
||||
- fsl,sai-asynchronous: This is a boolean property. If present, indicating
|
||||
that SAI will work in the asynchronous mode, which means both transimitter
|
||||
and receiver will send and receive data by following their own bit clocks
|
||||
and frame sync clocks separately.
|
||||
|
||||
- compatible : Compatible list, contains "fsl,vf610-sai" or
|
||||
"fsl,imx6sx-sai".
|
||||
|
||||
- reg : Offset and length of the register set for the device.
|
||||
|
||||
- clocks : Must contain an entry for each entry in clock-names.
|
||||
|
||||
- clock-names : Must include the "bus" for register access and
|
||||
"mclk1", "mclk2", "mclk3" for bit clock and frame
|
||||
clock providing.
|
||||
- dmas : Generic dma devicetree binding as described in
|
||||
Documentation/devicetree/bindings/dma/dma.txt.
|
||||
|
||||
- dma-names : Two dmas have to be defined, "tx" and "rx".
|
||||
|
||||
- pinctrl-names : Must contain a "default" entry.
|
||||
|
||||
- pinctrl-NNN : One property must exist for each entry in
|
||||
pinctrl-names. See ../pinctrl/pinctrl-bindings.txt
|
||||
for details of the property values.
|
||||
|
||||
- big-endian : Boolean property, required if all the FTM_PWM
|
||||
registers are big-endian rather than little-endian.
|
||||
|
||||
- lsb-first : Configures whether the LSB or the MSB is transmitted
|
||||
first for the fifo data. If this property is absent,
|
||||
the MSB is transmitted first as default, or the LSB
|
||||
is transmitted first.
|
||||
|
||||
- fsl,sai-synchronous-rx: This is a boolean property. If present, indicating
|
||||
that SAI will work in the synchronous mode (sync Tx
|
||||
with Rx) which means both the transimitter and the
|
||||
receiver will send and receive data by following
|
||||
receiver's bit clocks and frame sync clocks.
|
||||
|
||||
- fsl,sai-asynchronous: This is a boolean property. If present, indicating
|
||||
that SAI will work in the asynchronous mode, which
|
||||
means both transimitter and receiver will send and
|
||||
receive data by following their own bit clocks and
|
||||
frame sync clocks separately.
|
||||
|
||||
Note:
|
||||
- If both fsl,sai-asynchronous and fsl,sai-synchronous-rx are absent, the
|
||||
|
|
|
@ -1,33 +1,40 @@
|
|||
Freescale i.MX audio complex with SGTL5000 codec
|
||||
|
||||
Required properties:
|
||||
- compatible : "fsl,imx-audio-sgtl5000"
|
||||
- model : The user-visible name of this sound complex
|
||||
- ssi-controller : The phandle of the i.MX SSI controller
|
||||
- audio-codec : The phandle of the SGTL5000 audio codec
|
||||
- audio-routing : A list of the connections between audio components.
|
||||
Each entry is a pair of strings, the first being the connection's sink,
|
||||
the second being the connection's source. Valid names could be power
|
||||
supplies, SGTL5000 pins, and the jacks on the board:
|
||||
|
||||
Power supplies:
|
||||
* Mic Bias
|
||||
- compatible : "fsl,imx-audio-sgtl5000"
|
||||
|
||||
SGTL5000 pins:
|
||||
* MIC_IN
|
||||
* LINE_IN
|
||||
* HP_OUT
|
||||
* LINE_OUT
|
||||
- model : The user-visible name of this sound complex
|
||||
|
||||
Board connectors:
|
||||
* Mic Jack
|
||||
* Line In Jack
|
||||
* Headphone Jack
|
||||
* Line Out Jack
|
||||
* Ext Spk
|
||||
- ssi-controller : The phandle of the i.MX SSI controller
|
||||
|
||||
- mux-int-port : The internal port of the i.MX audio muxer (AUDMUX)
|
||||
- mux-ext-port : The external port of the i.MX audio muxer
|
||||
- audio-codec : The phandle of the SGTL5000 audio codec
|
||||
|
||||
- audio-routing : A list of the connections between audio components.
|
||||
Each entry is a pair of strings, the first being the
|
||||
connection's sink, the second being the connection's
|
||||
source. Valid names could be power supplies, SGTL5000
|
||||
pins, and the jacks on the board:
|
||||
|
||||
Power supplies:
|
||||
* Mic Bias
|
||||
|
||||
SGTL5000 pins:
|
||||
* MIC_IN
|
||||
* LINE_IN
|
||||
* HP_OUT
|
||||
* LINE_OUT
|
||||
|
||||
Board connectors:
|
||||
* Mic Jack
|
||||
* Line In Jack
|
||||
* Headphone Jack
|
||||
* Line Out Jack
|
||||
* Ext Spk
|
||||
|
||||
- mux-int-port : The internal port of the i.MX audio muxer (AUDMUX)
|
||||
|
||||
- mux-ext-port : The external port of the i.MX audio muxer
|
||||
|
||||
Note: The AUDMUX port numbering should start at 1, which is consistent with
|
||||
hardware manual.
|
||||
|
|
|
@ -2,23 +2,25 @@ Freescale i.MX audio complex with S/PDIF transceiver
|
|||
|
||||
Required properties:
|
||||
|
||||
- compatible : "fsl,imx-audio-spdif"
|
||||
- compatible : "fsl,imx-audio-spdif"
|
||||
|
||||
- model : The user-visible name of this sound complex
|
||||
- model : The user-visible name of this sound complex
|
||||
|
||||
- spdif-controller : The phandle of the i.MX S/PDIF controller
|
||||
- spdif-controller : The phandle of the i.MX S/PDIF controller
|
||||
|
||||
|
||||
Optional properties:
|
||||
|
||||
- spdif-out : This is a boolean property. If present, the transmitting
|
||||
function of S/PDIF will be enabled, indicating there's a physical
|
||||
S/PDIF out connector/jack on the board or it's connecting to some
|
||||
other IP block, such as an HDMI encoder/display-controller.
|
||||
- spdif-out : This is a boolean property. If present, the
|
||||
transmitting function of S/PDIF will be enabled,
|
||||
indicating there's a physical S/PDIF out connector
|
||||
or jack on the board or it's connecting to some
|
||||
other IP block, such as an HDMI encoder or
|
||||
display-controller.
|
||||
|
||||
- spdif-in : This is a boolean property. If present, the receiving
|
||||
function of S/PDIF will be enabled, indicating there's a physical
|
||||
S/PDIF in connector/jack on the board.
|
||||
- spdif-in : This is a boolean property. If present, the receiving
|
||||
function of S/PDIF will be enabled, indicating there
|
||||
is a physical S/PDIF in connector/jack on the board.
|
||||
|
||||
* Note: At least one of these two properties should be set in the DT binding.
|
||||
|
||||
|
|
|
@ -1,25 +1,32 @@
|
|||
Freescale i.MX audio complex with WM8962 codec
|
||||
|
||||
Required properties:
|
||||
- compatible : "fsl,imx-audio-wm8962"
|
||||
- model : The user-visible name of this sound complex
|
||||
- ssi-controller : The phandle of the i.MX SSI controller
|
||||
- audio-codec : The phandle of the WM8962 audio codec
|
||||
- audio-routing : A list of the connections between audio components.
|
||||
Each entry is a pair of strings, the first being the connection's sink,
|
||||
the second being the connection's source. Valid names could be power
|
||||
supplies, WM8962 pins, and the jacks on the board:
|
||||
|
||||
Power supplies:
|
||||
* Mic Bias
|
||||
- compatible : "fsl,imx-audio-wm8962"
|
||||
|
||||
Board connectors:
|
||||
* Mic Jack
|
||||
* Headphone Jack
|
||||
* Ext Spk
|
||||
- model : The user-visible name of this sound complex
|
||||
|
||||
- mux-int-port : The internal port of the i.MX audio muxer (AUDMUX)
|
||||
- mux-ext-port : The external port of the i.MX audio muxer
|
||||
- ssi-controller : The phandle of the i.MX SSI controller
|
||||
|
||||
- audio-codec : The phandle of the WM8962 audio codec
|
||||
|
||||
- audio-routing : A list of the connections between audio components.
|
||||
Each entry is a pair of strings, the first being the
|
||||
connection's sink, the second being the connection's
|
||||
source. Valid names could be power supplies, WM8962
|
||||
pins, and the jacks on the board:
|
||||
|
||||
Power supplies:
|
||||
* Mic Bias
|
||||
|
||||
Board connectors:
|
||||
* Mic Jack
|
||||
* Headphone Jack
|
||||
* Ext Spk
|
||||
|
||||
- mux-int-port : The internal port of the i.MX audio muxer (AUDMUX)
|
||||
|
||||
- mux-ext-port : The external port of the i.MX audio muxer
|
||||
|
||||
Note: The AUDMUX port numbering should start at 1, which is consistent with
|
||||
hardware manual.
|
||||
|
|
|
@ -1,18 +1,24 @@
|
|||
Freescale Digital Audio Mux (AUDMUX) device
|
||||
|
||||
Required properties:
|
||||
- compatible : "fsl,imx21-audmux" for AUDMUX version firstly used on i.MX21,
|
||||
or "fsl,imx31-audmux" for the version firstly used on i.MX31.
|
||||
- reg : Should contain AUDMUX registers location and length
|
||||
|
||||
- compatible : "fsl,imx21-audmux" for AUDMUX version firstly used
|
||||
on i.MX21, or "fsl,imx31-audmux" for the version
|
||||
firstly used on i.MX31.
|
||||
|
||||
- reg : Should contain AUDMUX registers location and length.
|
||||
|
||||
An initial configuration can be setup using child nodes.
|
||||
|
||||
Required properties of optional child nodes:
|
||||
- fsl,audmux-port : Integer of the audmux port that is configured by this
|
||||
child node.
|
||||
- fsl,port-config : List of configuration options for the specific port. For
|
||||
imx31-audmux and above, it is a list of tuples <ptcr pdcr>. For
|
||||
imx21-audmux it is a list of pcr values.
|
||||
|
||||
- fsl,audmux-port : Integer of the audmux port that is configured by this
|
||||
child node.
|
||||
|
||||
- fsl,port-config : List of configuration options for the specific port.
|
||||
For imx31-audmux and above, it is a list of tuples
|
||||
<ptcr pdcr>. For imx21-audmux it is a list of pcr
|
||||
values.
|
||||
|
||||
Example:
|
||||
|
||||
|
|
|
@ -16,6 +16,8 @@ Optional properties:
|
|||
|
||||
- clock-names: Should be "mclk"
|
||||
|
||||
- maxim,dmic-freq: Frequency at which to clock DMIC
|
||||
|
||||
Pins on the device (for linking into audio routes):
|
||||
|
||||
* MIC1
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
Renesas FSI
|
||||
|
||||
Required properties:
|
||||
- compatible : "renesas,sh_fsi2" or "renesas,sh_fsi"
|
||||
- compatible : "renesas,fsi2-<soctype>",
|
||||
"renesas,sh_fsi2" or "renesas,sh_fsi" as
|
||||
fallback.
|
||||
Examples with soctypes are:
|
||||
- "renesas,fsi2-r8a7740" (R-Mobile A1)
|
||||
- "renesas,fsi2-sh73a0" (SH-Mobile AG5)
|
||||
- reg : Should contain the register physical address and length
|
||||
- interrupts : Should contain FSI interrupt
|
||||
|
||||
- fsia,spdif-connection : FSI is connected by S/PDFI
|
||||
- fsia,spdif-connection : FSI is connected by S/PDIF
|
||||
- fsia,stream-mode-support : FSI supports 16bit stream mode.
|
||||
- fsia,use-internal-clock : FSI uses internal clock when master mode.
|
||||
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
Renesas R-Car sound
|
||||
|
||||
Required properties:
|
||||
- compatible : "renesas,rcar_sound-gen1" if generation1
|
||||
- compatible : "renesas,rcar_sound-<soctype>", fallbacks
|
||||
"renesas,rcar_sound-gen1" if generation1, and
|
||||
"renesas,rcar_sound-gen2" if generation2
|
||||
Examples with soctypes are:
|
||||
- "renesas,rcar_sound-r8a7790" (R-Car H2)
|
||||
- "renesas,rcar_sound-r8a7791" (R-Car M2-W)
|
||||
- reg : Should contain the register physical address.
|
||||
required register is
|
||||
SRU/ADG/SSI if generation1
|
||||
|
@ -35,9 +39,9 @@ DAI subnode properties:
|
|||
|
||||
Example:
|
||||
|
||||
rcar_sound: rcar_sound@0xffd90000 {
|
||||
rcar_sound: rcar_sound@ec500000 {
|
||||
#sound-dai-cells = <1>;
|
||||
compatible = "renesas,rcar_sound-gen2";
|
||||
compatible = "renesas,rcar_sound-r8a7791", "renesas,rcar_sound-gen2";
|
||||
reg = <0 0xec500000 0 0x1000>, /* SCU */
|
||||
<0 0xec5a0000 0 0x100>, /* ADG */
|
||||
<0 0xec540000 0 0x1000>, /* SSIU */
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
ALC5631/RT5631 audio CODEC
|
||||
|
||||
This device supports I2C only.
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible : "realtek,alc5631" or "realtek,rt5631"
|
||||
|
||||
- reg : the I2C address of the device.
|
||||
|
||||
Pins on the device (for linking into audio routes):
|
||||
|
||||
* SPK_OUT_R_P
|
||||
* SPK_OUT_R_N
|
||||
* SPK_OUT_L_P
|
||||
* SPK_OUT_L_N
|
||||
* HP_OUT_L
|
||||
* HP_OUT_R
|
||||
* AUX_OUT2_LP
|
||||
* AUX_OUT2_RN
|
||||
* AUX_OUT1_LP
|
||||
* AUX_OUT1_RN
|
||||
* AUX_IN_L_JD
|
||||
* AUX_IN_R_JD
|
||||
* MONO_IN_P
|
||||
* MONO_IN_N
|
||||
* MIC1_P
|
||||
* MIC1_N
|
||||
* MIC2_P
|
||||
* MIC2_N
|
||||
* MONO_OUT_P
|
||||
* MONO_OUT_N
|
||||
* MICBIAS1
|
||||
* MICBIAS2
|
||||
|
||||
Example:
|
||||
|
||||
alc5631: alc5631@1a {
|
||||
compatible = "realtek,alc5631";
|
||||
reg = <0x1a>;
|
||||
};
|
||||
|
||||
or
|
||||
|
||||
rt5631: rt5631@1a {
|
||||
compatible = "realtek,rt5631";
|
||||
reg = <0x1a>;
|
||||
};
|
|
@ -27,6 +27,21 @@ Optional properties:
|
|||
Boolean. Indicate MIC1/2 input and LOUT1/2/3 outputs are differential,
|
||||
rather than single-ended.
|
||||
|
||||
- realtek,gpio-config
|
||||
Array of six 8bit elements that configures GPIO.
|
||||
0 - floating (reset value)
|
||||
1 - pull down
|
||||
2 - pull up
|
||||
|
||||
- realtek,jd1-gpio
|
||||
Configures GPIO Mic Jack detection 1.
|
||||
Select 0 ~ 3 as OFF, GPIO1, GPIO2 and GPIO3 respectively.
|
||||
|
||||
- realtek,jd2-gpio
|
||||
- realtek,jd3-gpio
|
||||
Configures GPIO Mic Jack detection 2 and 3.
|
||||
Select 0 ~ 3 as OFF, GPIO4, GPIO5 and GPIO6 respectively.
|
||||
|
||||
Pins on the device (for linking into audio routes):
|
||||
|
||||
* IN1P
|
||||
|
@ -56,4 +71,6 @@ rt5677 {
|
|||
realtek,pow-ldo2-gpio =
|
||||
<&gpio TEGRA_GPIO(V, 3) GPIO_ACTIVE_HIGH>;
|
||||
realtek,in1-differential = "true";
|
||||
realtek,gpio-config = /bits/ 8 <0 0 0 0 0 2>; /* pull up GPIO6 */
|
||||
realtek,jd2-gpio = <3>; /* Enables Jack detection for GPIO6 */
|
||||
};
|
||||
|
|
|
@ -6,10 +6,17 @@ Required SoC Specific Properties:
|
|||
- samsung,s3c6410-i2s: for 8/16/24bit stereo I2S.
|
||||
- samsung,s5pv210-i2s: for 8/16/24bit multichannel(5.1) I2S with
|
||||
secondary fifo, s/w reset control and internal mux for root clk src.
|
||||
- samsung,exynos5420-i2s: for 8/16/24bit multichannel(7.1) I2S with
|
||||
secondary fifo, s/w reset control, internal mux for root clk src and
|
||||
TDM support. TDM (Time division multiplexing) is to allow transfer of
|
||||
multiple channel audio data on single data line.
|
||||
- samsung,exynos5420-i2s: for 8/16/24bit multichannel(5.1) I2S for
|
||||
playback, sterio channel capture, secondary fifo using internal
|
||||
or external dma, s/w reset control, internal mux for root clk src
|
||||
and 7.1 channel TDM support for playback. TDM (Time division multiplexing)
|
||||
is to allow transfer of multiple channel audio data on single data line.
|
||||
- samsung,exynos7-i2s: with all the available features of exynos5 i2s,
|
||||
exynos7 I2S has 7.1 channel TDM support for capture, secondary fifo
|
||||
with only external dma and more no.of root clk sampling frequencies.
|
||||
- samsung,exynos7-i2s1: I2S1 on previous samsung platforms supports
|
||||
stereo channels. exynos7 i2s1 upgraded to 5.1 multichannel with
|
||||
slightly modified bit offsets.
|
||||
|
||||
- reg: physical base address of the controller and length of memory mapped
|
||||
region.
|
||||
|
|
|
@ -7,6 +7,17 @@ Required properties:
|
|||
|
||||
- clocks : the clock provider of SYS_MCLK
|
||||
|
||||
- micbias-resistor-k-ohms : the bias resistor to be used in kOmhs
|
||||
The resistor can take values of 2k, 4k or 8k.
|
||||
If set to 0 it will be off.
|
||||
If this node is not mentioned or if the value is unknown, then
|
||||
micbias resistor is set to 4K.
|
||||
|
||||
- micbias-voltage-m-volts : the bias voltage to be used in mVolts
|
||||
The voltage can take values from 1.25V to 3V by 250mV steps
|
||||
If this node is not mentionned or the value is unknown, then
|
||||
the value is set to 1.25V.
|
||||
|
||||
- VDDA-supply : the regulator provider of VDDA
|
||||
|
||||
- VDDIO-supply: the regulator provider of VDDIO
|
||||
|
@ -21,6 +32,8 @@ codec: sgtl5000@0a {
|
|||
compatible = "fsl,sgtl5000";
|
||||
reg = <0x0a>;
|
||||
clocks = <&clks 150>;
|
||||
micbias-resistor-k-ohms = <2>;
|
||||
micbias-voltage-m-volts = <2250>;
|
||||
VDDA-supply = <®_3p3v>;
|
||||
VDDIO-supply = <®_3p3v>;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
Texas Instruments TS3A227E
|
||||
Autonomous Audio Accessory Detection and Configuration Switch
|
||||
|
||||
The TS3A227E detect headsets of 3-ring and 4-ring standards and
|
||||
switches automatically to route the microphone correctly. It also
|
||||
handles key press detection in accordance with the Android audio
|
||||
headset specification v1.0.
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible: Should contain "ti,ts3a227e".
|
||||
- reg: The i2c address. Should contain <0x3b>.
|
||||
- interrupt-parent: The parent interrupt controller
|
||||
- interrupts: Interrupt number for /INT pin from the 227e
|
||||
|
||||
|
||||
Examples:
|
||||
|
||||
i2c {
|
||||
ts3a227e@3b {
|
||||
compatible = "ti,ts3a227e";
|
||||
reg = <0x3b>;
|
||||
interrupt-parent = <&gpio>;
|
||||
interrupts = <3 IRQ_TYPE_LEVEL_LOW>;
|
||||
};
|
||||
};
|
|
@ -0,0 +1,31 @@
|
|||
WM8960 audio CODEC
|
||||
|
||||
This device supports I2C only.
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible : "wlf,wm8960"
|
||||
|
||||
- reg : the I2C address of the device.
|
||||
|
||||
Optional properties:
|
||||
- wlf,shared-lrclk: This is a boolean property. If present, the LRCM bit of
|
||||
R24 (Additional control 2) gets set, indicating that ADCLRC and DACLRC pins
|
||||
will be disabled only when ADC (Left and Right) and DAC (Left and Right)
|
||||
are disabled.
|
||||
When wm8960 works on synchronize mode and DACLRC pin is used to supply
|
||||
frame clock, it will no frame clock for captrue unless enable DAC to enable
|
||||
DACLRC pin. If shared-lrclk is present, no need to enable DAC for captrue.
|
||||
|
||||
- wlf,capless: This is a boolean property. If present, OUT3 pin will be
|
||||
enabled and disabled together with HP_L and HP_R pins in response to jack
|
||||
detect events.
|
||||
|
||||
Example:
|
||||
|
||||
codec: wm8960@1a {
|
||||
compatible = "wlf,wm8960";
|
||||
reg = <0x1a>;
|
||||
|
||||
wlf,shared-lrclk;
|
||||
};
|
|
@ -1,6 +1,6 @@
|
|||
This document describes standard names of mixer controls.
|
||||
|
||||
Syntax: SOURCE [DIRECTION] FUNCTION
|
||||
Syntax: [LOCATION] SOURCE [CHANNEL] [DIRECTION] FUNCTION
|
||||
|
||||
DIRECTION:
|
||||
<nothing> (both directions)
|
||||
|
@ -14,12 +14,29 @@ FUNCTION:
|
|||
Volume
|
||||
Route (route control, hardware specific)
|
||||
|
||||
CHANNEL:
|
||||
<nothing> (channel independent, or applies to all channels)
|
||||
Front
|
||||
Surround (rear left/right in 4.0/5.1 surround)
|
||||
CLFE
|
||||
Center
|
||||
LFE
|
||||
Side (side left/right for 7.1 surround)
|
||||
|
||||
LOCATION: (physical location of source)
|
||||
Front
|
||||
Rear
|
||||
Dock (docking station)
|
||||
Internal
|
||||
|
||||
SOURCE:
|
||||
Master
|
||||
Master Mono
|
||||
Hardware Master
|
||||
Speaker (internal speaker)
|
||||
Bass Speaker (internal LFE speaker)
|
||||
Headphone
|
||||
Line Out
|
||||
Beep (beep generator)
|
||||
Phone
|
||||
Phone Input
|
||||
|
@ -27,14 +44,14 @@ SOURCE:
|
|||
Synth
|
||||
FM
|
||||
Mic
|
||||
Line
|
||||
Headset Mic (mic part of combined headset jack - 4-pin headphone + mic)
|
||||
Headphone Mic (mic part of either/or - 3-pin headphone or mic)
|
||||
Line (input only, use "Line Out" for output)
|
||||
CD
|
||||
Video
|
||||
Zoom Video
|
||||
Aux
|
||||
PCM
|
||||
PCM Front
|
||||
PCM Rear
|
||||
PCM Pan
|
||||
Loopback
|
||||
Analog Loopback (D/A -> A/D loopback)
|
||||
|
@ -47,8 +64,13 @@ SOURCE:
|
|||
Music
|
||||
I2S
|
||||
IEC958
|
||||
HDMI
|
||||
SPDIF (output only)
|
||||
SPDIF In
|
||||
Digital In
|
||||
HDMI/DP (either HDMI or DisplayPort)
|
||||
|
||||
Exceptions:
|
||||
Exceptions (deprecated):
|
||||
[Digital] Capture Source
|
||||
[Digital] Capture Switch (aka input gain switch)
|
||||
[Digital] Capture Volume (aka input gain volume)
|
||||
|
|
|
@ -113,14 +113,10 @@ AD1984
|
|||
|
||||
AD1986A
|
||||
=======
|
||||
6stack 6-jack, separate surrounds (default)
|
||||
3stack 3-stack, shared surrounds
|
||||
laptop 2-channel only (FSC V2060, Samsung M50)
|
||||
laptop-eapd 2-channel with EAPD (ASUS A6J)
|
||||
laptop-automute 2-channel with EAPD and HP-automute (Lenovo N100)
|
||||
ultra 2-channel with EAPD (Samsung Ultra tablet PC)
|
||||
samsung 2-channel with EAPD (Samsung R65)
|
||||
samsung-p50 2-channel with HP-automute (Samsung P50)
|
||||
laptop-imic 2-channel with built-in mic
|
||||
eapd Turn on EAPD constantly
|
||||
|
||||
AD1988/AD1988B/AD1989A/AD1989B
|
||||
==============================
|
||||
|
|
|
@ -101,10 +101,6 @@ card*/pcm*/xrun_debug
|
|||
bit 0 = Enable XRUN/jiffies debug messages
|
||||
bit 1 = Show stack trace at XRUN / jiffies check
|
||||
bit 2 = Enable additional jiffies check
|
||||
bit 3 = Log hwptr update at each period interrupt
|
||||
bit 4 = Log hwptr update at each snd_pcm_update_hw_ptr()
|
||||
bit 5 = Show last 10 positions on error
|
||||
bit 6 = Do above only once
|
||||
|
||||
When the bit 0 is set, the driver will show the messages to
|
||||
kernel log when an xrun is detected. The debug message is
|
||||
|
@ -121,15 +117,6 @@ card*/pcm*/xrun_debug
|
|||
buggy) hardware that doesn't give smooth pointer updates.
|
||||
This feature is enabled via the bit 2.
|
||||
|
||||
Bits 3 and 4 are for logging the hwptr records. Note that
|
||||
these will give flood of kernel messages.
|
||||
|
||||
When bit 5 is set, the driver logs the last 10 xrun errors and
|
||||
the proc file shows each jiffies, position, period_size,
|
||||
buffer_size, old_hw_ptr, and hw_ptr_base values.
|
||||
|
||||
When bit 6 is set, the full xrun log is shown only once.
|
||||
|
||||
card*/pcm*/sub*/info
|
||||
The general information of this PCM sub-stream.
|
||||
|
||||
|
@ -146,6 +133,10 @@ card*/pcm*/sub*/sw_params
|
|||
card*/pcm*/sub*/prealloc
|
||||
The buffer pre-allocation information.
|
||||
|
||||
card*/pcm*/sub*/xrun_injection
|
||||
Triggers an XRUN to the running stream when any value is
|
||||
written to this proc file. Used for fault injection.
|
||||
This entry is write-only.
|
||||
|
||||
AC97 Codec Information
|
||||
----------------------
|
||||
|
|
|
@ -6682,6 +6682,12 @@ S: Supported
|
|||
F: drivers/gpu/drm/i2c/tda998x_drv.c
|
||||
F: include/drm/i2c/tda998x.h
|
||||
|
||||
NXP TFA9879 DRIVER
|
||||
M: Peter Rosin <peda@axentia.se>
|
||||
L: alsa-devel@alsa-project.org (moderated for non-subscribers)
|
||||
S: Maintained
|
||||
F: sound/soc/codecs/tfa9879*
|
||||
|
||||
OMAP SUPPORT
|
||||
M: Tony Lindgren <tony@atomide.com>
|
||||
L: linux-omap@vger.kernel.org
|
||||
|
|
|
@ -923,6 +923,14 @@ static void __init spitz_i2c_init(void)
|
|||
static inline void spitz_i2c_init(void) {}
|
||||
#endif
|
||||
|
||||
/******************************************************************************
|
||||
* Audio devices
|
||||
******************************************************************************/
|
||||
static inline void spitz_audio_init(void)
|
||||
{
|
||||
platform_device_register_simple("spitz-audio", -1, NULL, 0);
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* Machine init
|
||||
******************************************************************************/
|
||||
|
@ -970,6 +978,7 @@ static void __init spitz_init(void)
|
|||
spitz_nor_init();
|
||||
spitz_nand_init();
|
||||
spitz_i2c_init();
|
||||
spitz_audio_init();
|
||||
}
|
||||
|
||||
static void __init spitz_fixup(struct tag *tags, char **cmdline)
|
||||
|
|
|
@ -16,6 +16,9 @@
|
|||
|
||||
#include <linux/sfi.h>
|
||||
|
||||
#define MAX_NUM_STREAMS_MRFLD 25
|
||||
#define MAX_NUM_STREAMS MAX_NUM_STREAMS_MRFLD
|
||||
|
||||
enum sst_audio_task_id_mrfld {
|
||||
SST_TASK_ID_NONE = 0,
|
||||
SST_TASK_ID_SBA = 1,
|
||||
|
@ -73,6 +76,65 @@ struct sst_platform_data {
|
|||
unsigned int strm_map_size;
|
||||
};
|
||||
|
||||
struct sst_info {
|
||||
u32 iram_start;
|
||||
u32 iram_end;
|
||||
bool iram_use;
|
||||
u32 dram_start;
|
||||
u32 dram_end;
|
||||
bool dram_use;
|
||||
u32 imr_start;
|
||||
u32 imr_end;
|
||||
bool imr_use;
|
||||
u32 mailbox_start;
|
||||
bool use_elf;
|
||||
bool lpe_viewpt_rqd;
|
||||
unsigned int max_streams;
|
||||
u32 dma_max_len;
|
||||
u8 num_probes;
|
||||
};
|
||||
|
||||
struct sst_lib_dnld_info {
|
||||
unsigned int mod_base;
|
||||
unsigned int mod_end;
|
||||
unsigned int mod_table_offset;
|
||||
unsigned int mod_table_size;
|
||||
bool mod_ddr_dnld;
|
||||
};
|
||||
|
||||
struct sst_res_info {
|
||||
unsigned int shim_offset;
|
||||
unsigned int shim_size;
|
||||
unsigned int shim_phy_addr;
|
||||
unsigned int ssp0_offset;
|
||||
unsigned int ssp0_size;
|
||||
unsigned int dma0_offset;
|
||||
unsigned int dma0_size;
|
||||
unsigned int dma1_offset;
|
||||
unsigned int dma1_size;
|
||||
unsigned int iram_offset;
|
||||
unsigned int iram_size;
|
||||
unsigned int dram_offset;
|
||||
unsigned int dram_size;
|
||||
unsigned int mbox_offset;
|
||||
unsigned int mbox_size;
|
||||
unsigned int acpi_lpe_res_index;
|
||||
unsigned int acpi_ddr_index;
|
||||
unsigned int acpi_ipc_irq_index;
|
||||
};
|
||||
|
||||
struct sst_ipc_info {
|
||||
int ipc_offset;
|
||||
unsigned int mbox_recv_off;
|
||||
};
|
||||
|
||||
struct sst_platform_info {
|
||||
const struct sst_info *probe_data;
|
||||
const struct sst_ipc_info *ipc_info;
|
||||
const struct sst_res_info *res_info;
|
||||
const struct sst_lib_dnld_info *lib_info;
|
||||
const char *platform;
|
||||
};
|
||||
int add_sst_platform_device(void);
|
||||
#endif
|
||||
|
||||
|
|
|
@ -173,9 +173,7 @@ static void saa7134_irq_alsa_done(struct saa7134_dev *dev,
|
|||
dprintk("irq: overrun [full=%d/%d] - Blocks in %d\n",dev->dmasound.read_count,
|
||||
dev->dmasound.bufsize, dev->dmasound.blocks);
|
||||
spin_unlock(&dev->slock);
|
||||
snd_pcm_stream_lock(dev->dmasound.substream);
|
||||
snd_pcm_stop(dev->dmasound.substream,SNDRV_PCM_STATE_XRUN);
|
||||
snd_pcm_stream_unlock(dev->dmasound.substream);
|
||||
snd_pcm_stop_xrun(dev->dmasound.substream);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -141,6 +141,7 @@ struct arizona {
|
|||
|
||||
uint16_t dac_comp_coeff;
|
||||
uint8_t dac_comp_enabled;
|
||||
struct mutex dac_comp_lock;
|
||||
};
|
||||
|
||||
int arizona_clk32k_enable(struct arizona *arizona);
|
||||
|
|
|
@ -99,12 +99,6 @@ struct davinci_vcif {
|
|||
dma_addr_t dma_rx_addr;
|
||||
};
|
||||
|
||||
struct cq93vc {
|
||||
struct platform_device *pdev;
|
||||
struct snd_soc_codec *codec;
|
||||
u32 sysclk;
|
||||
};
|
||||
|
||||
struct davinci_vc;
|
||||
|
||||
struct davinci_vc {
|
||||
|
@ -122,7 +116,6 @@ struct davinci_vc {
|
|||
|
||||
/* Client devices */
|
||||
struct davinci_vcif davinci_vcif;
|
||||
struct cq93vc cq93vc;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -27,6 +27,7 @@ struct samsung_i2s {
|
|||
#define QUIRK_NO_MUXPSR (1 << 2)
|
||||
#define QUIRK_NEED_RSTCLR (1 << 3)
|
||||
#define QUIRK_SUPPORTS_TDM (1 << 4)
|
||||
#define QUIRK_SUPPORTS_IDMA (1 << 5)
|
||||
/* Quirks of the I2S controller */
|
||||
u32 quirks;
|
||||
dma_addr_t idma_addr;
|
||||
|
|
|
@ -42,12 +42,11 @@ struct snd_compr_ops;
|
|||
* @buffer_size: size of the above buffer
|
||||
* @fragment_size: size of buffer fragment in bytes
|
||||
* @fragments: number of such fragments
|
||||
* @hw_pointer: offset of last location in buffer where DSP copied data
|
||||
* @app_pointer: offset of last location in buffer where app wrote data
|
||||
* @total_bytes_available: cumulative number of bytes made available in
|
||||
* the ring buffer
|
||||
* @total_bytes_transferred: cumulative bytes transferred by offload DSP
|
||||
* @sleep: poll sleep
|
||||
* @private_data: driver private data pointer
|
||||
*/
|
||||
struct snd_compr_runtime {
|
||||
snd_pcm_state_t state;
|
||||
|
@ -94,6 +93,8 @@ struct snd_compr_stream {
|
|||
* This can be called in during stream creation only to set codec params
|
||||
* and the stream properties
|
||||
* @get_params: retrieve the codec parameters, mandatory
|
||||
* @set_metadata: Set the metadata values for a stream
|
||||
* @get_metadata: retreives the requested metadata values from stream
|
||||
* @trigger: Trigger operations like start, pause, resume, drain, stop.
|
||||
* This callback is mandatory
|
||||
* @pointer: Retrieve current h/w pointer information. Mandatory
|
||||
|
|
|
@ -28,8 +28,23 @@
|
|||
struct input_dev;
|
||||
|
||||
/**
|
||||
* Jack types which can be reported. These values are used as a
|
||||
* bitmask.
|
||||
* enum snd_jack_types - Jack types which can be reported
|
||||
* @SND_JACK_HEADPHONE: Headphone
|
||||
* @SND_JACK_MICROPHONE: Microphone
|
||||
* @SND_JACK_HEADSET: Headset
|
||||
* @SND_JACK_LINEOUT: Line out
|
||||
* @SND_JACK_MECHANICAL: Mechanical switch
|
||||
* @SND_JACK_VIDEOOUT: Video out
|
||||
* @SND_JACK_AVOUT: AV (Audio Video) out
|
||||
* @SND_JACK_LINEIN: Line in
|
||||
* @SND_JACK_BTN_0: Button 0
|
||||
* @SND_JACK_BTN_1: Button 1
|
||||
* @SND_JACK_BTN_2: Button 2
|
||||
* @SND_JACK_BTN_3: Button 3
|
||||
* @SND_JACK_BTN_4: Button 4
|
||||
* @SND_JACK_BTN_5: Button 5
|
||||
*
|
||||
* These values are used as a bitmask.
|
||||
*
|
||||
* Note that this must be kept in sync with the lookup table in
|
||||
* sound/core/jack.c.
|
||||
|
@ -90,6 +105,13 @@ static inline void snd_jack_set_parent(struct snd_jack *jack,
|
|||
{
|
||||
}
|
||||
|
||||
static inline int snd_jack_set_key(struct snd_jack *jack,
|
||||
enum snd_jack_types type,
|
||||
int keytype)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void snd_jack_report(struct snd_jack *jack, int status)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -418,7 +418,10 @@ struct snd_pcm_substream {
|
|||
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;
|
||||
};
|
||||
|
@ -505,6 +508,7 @@ int snd_pcm_status(struct snd_pcm_substream *substream,
|
|||
int snd_pcm_start(struct snd_pcm_substream *substream);
|
||||
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);
|
||||
|
@ -535,6 +539,12 @@ snd_pcm_debug_name(struct snd_pcm_substream *substream, char *buf, size_t size)
|
|||
* PCM library
|
||||
*/
|
||||
|
||||
/**
|
||||
* snd_pcm_stream_linked - Check whether the substream is linked with others
|
||||
* @substream: substream to check
|
||||
*
|
||||
* Returns true if the given substream is being linked with others.
|
||||
*/
|
||||
static inline int snd_pcm_stream_linked(struct snd_pcm_substream *substream)
|
||||
{
|
||||
return substream->group != &substream->self_group;
|
||||
|
@ -545,6 +555,16 @@ void snd_pcm_stream_unlock(struct snd_pcm_substream *substream);
|
|||
void snd_pcm_stream_lock_irq(struct snd_pcm_substream *substream);
|
||||
void snd_pcm_stream_unlock_irq(struct snd_pcm_substream *substream);
|
||||
unsigned long _snd_pcm_stream_lock_irqsave(struct snd_pcm_substream *substream);
|
||||
|
||||
/**
|
||||
* snd_pcm_stream_lock_irqsave - Lock the PCM stream
|
||||
* @substream: PCM substream
|
||||
* @flags: irq flags
|
||||
*
|
||||
* This locks the PCM stream like snd_pcm_stream_lock() but with the local
|
||||
* IRQ (only when nonatomic is false). In nonatomic case, this is identical
|
||||
* as snd_pcm_stream_lock().
|
||||
*/
|
||||
#define snd_pcm_stream_lock_irqsave(substream, flags) \
|
||||
do { \
|
||||
typecheck(unsigned long, flags); \
|
||||
|
@ -553,9 +573,25 @@ unsigned long _snd_pcm_stream_lock_irqsave(struct snd_pcm_substream *substream);
|
|||
void snd_pcm_stream_unlock_irqrestore(struct snd_pcm_substream *substream,
|
||||
unsigned long flags);
|
||||
|
||||
/**
|
||||
* snd_pcm_group_for_each_entry - iterate over the linked substreams
|
||||
* @s: the iterator
|
||||
* @substream: the substream
|
||||
*
|
||||
* Iterate over the all linked substreams to the given @substream.
|
||||
* When @substream isn't linked with any others, this gives returns @substream
|
||||
* itself once.
|
||||
*/
|
||||
#define snd_pcm_group_for_each_entry(s, substream) \
|
||||
list_for_each_entry(s, &substream->group->substreams, link_list)
|
||||
|
||||
/**
|
||||
* snd_pcm_running - Check whether the substream is in a running state
|
||||
* @substream: substream to check
|
||||
*
|
||||
* Returns true if the given substream is in the state RUNNING, or in the
|
||||
* state DRAINING for playback.
|
||||
*/
|
||||
static inline int snd_pcm_running(struct snd_pcm_substream *substream)
|
||||
{
|
||||
return (substream->runtime->status->state == SNDRV_PCM_STATE_RUNNING ||
|
||||
|
@ -563,45 +599,81 @@ static inline int snd_pcm_running(struct snd_pcm_substream *substream)
|
|||
substream->stream == SNDRV_PCM_STREAM_PLAYBACK));
|
||||
}
|
||||
|
||||
/**
|
||||
* bytes_to_samples - Unit conversion of the size from bytes to samples
|
||||
* @runtime: PCM runtime instance
|
||||
* @size: size in bytes
|
||||
*/
|
||||
static inline ssize_t bytes_to_samples(struct snd_pcm_runtime *runtime, ssize_t size)
|
||||
{
|
||||
return size * 8 / runtime->sample_bits;
|
||||
}
|
||||
|
||||
/**
|
||||
* bytes_to_frames - Unit conversion of the size from bytes to frames
|
||||
* @runtime: PCM runtime instance
|
||||
* @size: size in bytes
|
||||
*/
|
||||
static inline snd_pcm_sframes_t bytes_to_frames(struct snd_pcm_runtime *runtime, ssize_t size)
|
||||
{
|
||||
return size * 8 / runtime->frame_bits;
|
||||
}
|
||||
|
||||
/**
|
||||
* samples_to_bytes - Unit conversion of the size from samples to bytes
|
||||
* @runtime: PCM runtime instance
|
||||
* @size: size in samples
|
||||
*/
|
||||
static inline ssize_t samples_to_bytes(struct snd_pcm_runtime *runtime, ssize_t size)
|
||||
{
|
||||
return size * runtime->sample_bits / 8;
|
||||
}
|
||||
|
||||
/**
|
||||
* frames_to_bytes - Unit conversion of the size from frames to bytes
|
||||
* @runtime: PCM runtime instance
|
||||
* @size: size in frames
|
||||
*/
|
||||
static inline ssize_t frames_to_bytes(struct snd_pcm_runtime *runtime, snd_pcm_sframes_t size)
|
||||
{
|
||||
return size * runtime->frame_bits / 8;
|
||||
}
|
||||
|
||||
/**
|
||||
* frame_aligned - Check whether the byte size is aligned to frames
|
||||
* @runtime: PCM runtime instance
|
||||
* @bytes: size in bytes
|
||||
*/
|
||||
static inline int frame_aligned(struct snd_pcm_runtime *runtime, ssize_t bytes)
|
||||
{
|
||||
return bytes % runtime->byte_align == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_pcm_lib_buffer_bytes - Get the buffer size of the current PCM in bytes
|
||||
* @substream: PCM substream
|
||||
*/
|
||||
static inline size_t snd_pcm_lib_buffer_bytes(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
return frames_to_bytes(runtime, runtime->buffer_size);
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_pcm_lib_period_bytes - Get the period size of the current PCM in bytes
|
||||
* @substream: PCM substream
|
||||
*/
|
||||
static inline size_t snd_pcm_lib_period_bytes(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
return frames_to_bytes(runtime, runtime->period_size);
|
||||
}
|
||||
|
||||
/*
|
||||
* result is: 0 ... (boundary - 1)
|
||||
/**
|
||||
* snd_pcm_playback_avail - Get the available (writable) space for playback
|
||||
* @runtime: PCM runtime instance
|
||||
*
|
||||
* Result is between 0 ... (boundary - 1)
|
||||
*/
|
||||
static inline snd_pcm_uframes_t snd_pcm_playback_avail(struct snd_pcm_runtime *runtime)
|
||||
{
|
||||
|
@ -613,8 +685,11 @@ static inline snd_pcm_uframes_t snd_pcm_playback_avail(struct snd_pcm_runtime *r
|
|||
return avail;
|
||||
}
|
||||
|
||||
/*
|
||||
* result is: 0 ... (boundary - 1)
|
||||
/**
|
||||
* snd_pcm_playback_avail - Get the available (readable) space for capture
|
||||
* @runtime: PCM runtime instance
|
||||
*
|
||||
* Result is between 0 ... (boundary - 1)
|
||||
*/
|
||||
static inline snd_pcm_uframes_t snd_pcm_capture_avail(struct snd_pcm_runtime *runtime)
|
||||
{
|
||||
|
@ -624,11 +699,19 @@ static inline snd_pcm_uframes_t snd_pcm_capture_avail(struct snd_pcm_runtime *ru
|
|||
return avail;
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_pcm_playback_hw_avail - Get the queued space for playback
|
||||
* @runtime: PCM runtime instance
|
||||
*/
|
||||
static inline snd_pcm_sframes_t snd_pcm_playback_hw_avail(struct snd_pcm_runtime *runtime)
|
||||
{
|
||||
return runtime->buffer_size - snd_pcm_playback_avail(runtime);
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_pcm_capture_hw_avail - Get the free space for capture
|
||||
* @runtime: PCM runtime instance
|
||||
*/
|
||||
static inline snd_pcm_sframes_t snd_pcm_capture_hw_avail(struct snd_pcm_runtime *runtime)
|
||||
{
|
||||
return runtime->buffer_size - snd_pcm_capture_avail(runtime);
|
||||
|
@ -708,6 +791,20 @@ static inline int snd_pcm_capture_empty(struct snd_pcm_substream *substream)
|
|||
return snd_pcm_capture_avail(runtime) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_pcm_trigger_done - Mark the master substream
|
||||
* @substream: the pcm substream instance
|
||||
* @master: the linked master substream
|
||||
*
|
||||
* When multiple substreams of the same card are linked and the hardware
|
||||
* supports the single-shot operation, the driver calls this in the loop
|
||||
* in snd_pcm_group_for_each_entry() for marking the substream as "done".
|
||||
* Then most of trigger operations are performed only to the given master
|
||||
* substream.
|
||||
*
|
||||
* The trigger_master mark is cleared at timestamp updates at the end
|
||||
* of trigger operations.
|
||||
*/
|
||||
static inline void snd_pcm_trigger_done(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_substream *master)
|
||||
{
|
||||
|
@ -750,18 +847,59 @@ static inline const struct snd_interval *hw_param_interval_c(const struct snd_pc
|
|||
return ¶ms->intervals[var - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL];
|
||||
}
|
||||
|
||||
#define params_channels(p) \
|
||||
(hw_param_interval_c((p), SNDRV_PCM_HW_PARAM_CHANNELS)->min)
|
||||
#define params_rate(p) \
|
||||
(hw_param_interval_c((p), SNDRV_PCM_HW_PARAM_RATE)->min)
|
||||
#define params_period_size(p) \
|
||||
(hw_param_interval_c((p), SNDRV_PCM_HW_PARAM_PERIOD_SIZE)->min)
|
||||
#define params_periods(p) \
|
||||
(hw_param_interval_c((p), SNDRV_PCM_HW_PARAM_PERIODS)->min)
|
||||
#define params_buffer_size(p) \
|
||||
(hw_param_interval_c((p), SNDRV_PCM_HW_PARAM_BUFFER_SIZE)->min)
|
||||
#define params_buffer_bytes(p) \
|
||||
(hw_param_interval_c((p), SNDRV_PCM_HW_PARAM_BUFFER_BYTES)->min)
|
||||
/**
|
||||
* params_channels - Get the number of channels from the hw params
|
||||
* @p: hw params
|
||||
*/
|
||||
static inline unsigned int params_channels(const struct snd_pcm_hw_params *p)
|
||||
{
|
||||
return hw_param_interval_c(p, SNDRV_PCM_HW_PARAM_CHANNELS)->min;
|
||||
}
|
||||
|
||||
/**
|
||||
* params_channels - Get the sample rate from the hw params
|
||||
* @p: hw params
|
||||
*/
|
||||
static inline unsigned int params_rate(const struct snd_pcm_hw_params *p)
|
||||
{
|
||||
return hw_param_interval_c(p, SNDRV_PCM_HW_PARAM_RATE)->min;
|
||||
}
|
||||
|
||||
/**
|
||||
* params_channels - Get the period size (in frames) from the hw params
|
||||
* @p: hw params
|
||||
*/
|
||||
static inline unsigned int params_period_size(const struct snd_pcm_hw_params *p)
|
||||
{
|
||||
return hw_param_interval_c(p, SNDRV_PCM_HW_PARAM_PERIOD_SIZE)->min;
|
||||
}
|
||||
|
||||
/**
|
||||
* params_channels - Get the number of periods from the hw params
|
||||
* @p: hw params
|
||||
*/
|
||||
static inline unsigned int params_periods(const struct snd_pcm_hw_params *p)
|
||||
{
|
||||
return hw_param_interval_c(p, SNDRV_PCM_HW_PARAM_PERIODS)->min;
|
||||
}
|
||||
|
||||
/**
|
||||
* params_channels - Get the buffer size (in frames) from the hw params
|
||||
* @p: hw params
|
||||
*/
|
||||
static inline unsigned int params_buffer_size(const struct snd_pcm_hw_params *p)
|
||||
{
|
||||
return hw_param_interval_c(p, SNDRV_PCM_HW_PARAM_BUFFER_SIZE)->min;
|
||||
}
|
||||
|
||||
/**
|
||||
* params_channels - Get the buffer size (in bytes) from the hw params
|
||||
* @p: hw params
|
||||
*/
|
||||
static inline unsigned int params_buffer_bytes(const struct snd_pcm_hw_params *p)
|
||||
{
|
||||
return hw_param_interval_c(p, SNDRV_PCM_HW_PARAM_BUFFER_BYTES)->min;
|
||||
}
|
||||
|
||||
int snd_interval_refine(struct snd_interval *i, const struct snd_interval *v);
|
||||
void snd_interval_mul(const struct snd_interval *a, const struct snd_interval *b, struct snd_interval *c);
|
||||
|
@ -883,6 +1021,14 @@ unsigned int snd_pcm_rate_bit_to_rate(unsigned int rate_bit);
|
|||
unsigned int snd_pcm_rate_mask_intersect(unsigned int rates_a,
|
||||
unsigned int rates_b);
|
||||
|
||||
/**
|
||||
* snd_pcm_set_runtime_buffer - Set the PCM runtime buffer
|
||||
* @substream: PCM substream to set
|
||||
* @bufp: the buffer information, NULL to clear
|
||||
*
|
||||
* Copy the buffer information to runtime->dma_buffer when @bufp is non-NULL.
|
||||
* Otherwise it clears the current buffer information.
|
||||
*/
|
||||
static inline void snd_pcm_set_runtime_buffer(struct snd_pcm_substream *substream,
|
||||
struct snd_dma_buffer *bufp)
|
||||
{
|
||||
|
@ -908,6 +1054,11 @@ void snd_pcm_timer_resolution_change(struct snd_pcm_substream *substream);
|
|||
void snd_pcm_timer_init(struct snd_pcm_substream *substream);
|
||||
void snd_pcm_timer_done(struct snd_pcm_substream *substream);
|
||||
|
||||
/**
|
||||
* snd_pcm_gettime - Fill the timespec depending on the timestamp mode
|
||||
* @runtime: PCM runtime instance
|
||||
* @tv: timespec to fill
|
||||
*/
|
||||
static inline void snd_pcm_gettime(struct snd_pcm_runtime *runtime,
|
||||
struct timespec *tv)
|
||||
{
|
||||
|
@ -944,7 +1095,6 @@ int _snd_pcm_lib_alloc_vmalloc_buffer(struct snd_pcm_substream *substream,
|
|||
int snd_pcm_lib_free_vmalloc_buffer(struct snd_pcm_substream *substream);
|
||||
struct page *snd_pcm_lib_get_vmalloc_page(struct snd_pcm_substream *substream,
|
||||
unsigned long offset);
|
||||
#if 0 /* for kernel-doc */
|
||||
/**
|
||||
* snd_pcm_lib_alloc_vmalloc_buffer - allocate virtual DMA buffer
|
||||
* @substream: the substream to allocate the buffer to
|
||||
|
@ -957,8 +1107,13 @@ struct page *snd_pcm_lib_get_vmalloc_page(struct snd_pcm_substream *substream,
|
|||
* Return: 1 if the buffer was changed, 0 if not changed, or a negative error
|
||||
* code.
|
||||
*/
|
||||
static int snd_pcm_lib_alloc_vmalloc_buffer
|
||||
(struct snd_pcm_substream *substream, size_t size);
|
||||
static inline int snd_pcm_lib_alloc_vmalloc_buffer
|
||||
(struct snd_pcm_substream *substream, size_t size)
|
||||
{
|
||||
return _snd_pcm_lib_alloc_vmalloc_buffer(substream, size,
|
||||
GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO);
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_pcm_lib_alloc_vmalloc_32_buffer - allocate 32-bit-addressable buffer
|
||||
* @substream: the substream to allocate the buffer to
|
||||
|
@ -970,15 +1125,12 @@ static int snd_pcm_lib_alloc_vmalloc_buffer
|
|||
* Return: 1 if the buffer was changed, 0 if not changed, or a negative error
|
||||
* code.
|
||||
*/
|
||||
static int snd_pcm_lib_alloc_vmalloc_32_buffer
|
||||
(struct snd_pcm_substream *substream, size_t size);
|
||||
#endif
|
||||
#define snd_pcm_lib_alloc_vmalloc_buffer(subs, size) \
|
||||
_snd_pcm_lib_alloc_vmalloc_buffer \
|
||||
(subs, size, GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO)
|
||||
#define snd_pcm_lib_alloc_vmalloc_32_buffer(subs, size) \
|
||||
_snd_pcm_lib_alloc_vmalloc_buffer \
|
||||
(subs, size, GFP_KERNEL | GFP_DMA32 | __GFP_ZERO)
|
||||
static inline int snd_pcm_lib_alloc_vmalloc_32_buffer
|
||||
(struct snd_pcm_substream *substream, size_t size)
|
||||
{
|
||||
return _snd_pcm_lib_alloc_vmalloc_buffer(substream, size,
|
||||
GFP_KERNEL | GFP_DMA32 | __GFP_ZERO);
|
||||
}
|
||||
|
||||
#define snd_pcm_get_dma_buf(substream) ((substream)->runtime->dma_buffer_p)
|
||||
|
||||
|
@ -998,18 +1150,35 @@ struct page *snd_pcm_sgbuf_ops_page(struct snd_pcm_substream *substream,
|
|||
#define snd_pcm_sgbuf_ops_page NULL
|
||||
#endif /* SND_DMA_SGBUF */
|
||||
|
||||
/**
|
||||
* snd_pcm_sgbuf_get_addr - Get the DMA address at the corresponding offset
|
||||
* @substream: PCM substream
|
||||
* @ofs: byte offset
|
||||
*/
|
||||
static inline dma_addr_t
|
||||
snd_pcm_sgbuf_get_addr(struct snd_pcm_substream *substream, unsigned int ofs)
|
||||
{
|
||||
return snd_sgbuf_get_addr(snd_pcm_get_dma_buf(substream), ofs);
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_pcm_sgbuf_get_ptr - Get the virtual address at the corresponding offset
|
||||
* @substream: PCM substream
|
||||
* @ofs: byte offset
|
||||
*/
|
||||
static inline void *
|
||||
snd_pcm_sgbuf_get_ptr(struct snd_pcm_substream *substream, unsigned int ofs)
|
||||
{
|
||||
return snd_sgbuf_get_ptr(snd_pcm_get_dma_buf(substream), ofs);
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_pcm_sgbuf_chunk_size - Compute the max size that fits within the contig.
|
||||
* page from the given size
|
||||
* @substream: PCM substream
|
||||
* @ofs: byte offset
|
||||
* @size: byte size to examine
|
||||
*/
|
||||
static inline unsigned int
|
||||
snd_pcm_sgbuf_get_chunk_size(struct snd_pcm_substream *substream,
|
||||
unsigned int ofs, unsigned int size)
|
||||
|
@ -1017,13 +1186,24 @@ snd_pcm_sgbuf_get_chunk_size(struct snd_pcm_substream *substream,
|
|||
return snd_sgbuf_get_chunk_size(snd_pcm_get_dma_buf(substream), ofs, size);
|
||||
}
|
||||
|
||||
/* handle mmap counter - PCM mmap callback should handle this counter properly */
|
||||
/**
|
||||
* snd_pcm_mmap_data_open - increase the mmap counter
|
||||
* @area: VMA
|
||||
*
|
||||
* PCM mmap callback should handle this counter properly
|
||||
*/
|
||||
static inline void snd_pcm_mmap_data_open(struct vm_area_struct *area)
|
||||
{
|
||||
struct snd_pcm_substream *substream = (struct snd_pcm_substream *)area->vm_private_data;
|
||||
atomic_inc(&substream->mmap_count);
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_pcm_mmap_data_close - decrease the mmap counter
|
||||
* @area: VMA
|
||||
*
|
||||
* PCM mmap callback should handle this counter properly
|
||||
*/
|
||||
static inline void snd_pcm_mmap_data_close(struct vm_area_struct *area)
|
||||
{
|
||||
struct snd_pcm_substream *substream = (struct snd_pcm_substream *)area->vm_private_data;
|
||||
|
@ -1043,6 +1223,11 @@ int snd_pcm_lib_mmap_iomem(struct snd_pcm_substream *substream, struct vm_area_s
|
|||
|
||||
#define snd_pcm_lib_mmap_vmalloc NULL
|
||||
|
||||
/**
|
||||
* snd_pcm_limit_isa_dma_size - Get the max size fitting with ISA DMA transfer
|
||||
* @dma: DMA number
|
||||
* @max: pointer to store the max size
|
||||
*/
|
||||
static inline void snd_pcm_limit_isa_dma_size(int dma, size_t *max)
|
||||
{
|
||||
*max = dma < 4 ? 64 * 1024 : 128 * 1024;
|
||||
|
@ -1095,7 +1280,11 @@ struct snd_pcm_chmap {
|
|||
void *private_data; /* optional: private data pointer */
|
||||
};
|
||||
|
||||
/* get the PCM substream assigned to the given chmap info */
|
||||
/**
|
||||
* snd_pcm_chmap_substream - get the PCM substream assigned to the given chmap info
|
||||
* @info: chmap information
|
||||
* @idx: the substream number index
|
||||
*/
|
||||
static inline struct snd_pcm_substream *
|
||||
snd_pcm_chmap_substream(struct snd_pcm_chmap *info, unsigned int idx)
|
||||
{
|
||||
|
@ -1122,7 +1311,10 @@ int snd_pcm_add_chmap_ctls(struct snd_pcm *pcm, int stream,
|
|||
unsigned long private_value,
|
||||
struct snd_pcm_chmap **info_ret);
|
||||
|
||||
/* Strong-typed conversion of pcm_format to bitwise */
|
||||
/**
|
||||
* pcm_format_to_bits - Strong-typed conversion of pcm_format to bitwise
|
||||
* @pcm_format: PCM format
|
||||
*/
|
||||
static inline u64 pcm_format_to_bits(snd_pcm_format_t pcm_format)
|
||||
{
|
||||
return 1ULL << (__force int) pcm_format;
|
||||
|
|
|
@ -36,14 +36,14 @@
|
|||
#define RSND_SSI_CLK_PIN_SHARE (1 << 31)
|
||||
#define RSND_SSI_NO_BUSIF (1 << 30) /* SSI+DMA without BUSIF */
|
||||
|
||||
#define RSND_SSI(_dma_id, _pio_irq, _flags) \
|
||||
{ .dma_id = _dma_id, .pio_irq = _pio_irq, .flags = _flags }
|
||||
#define RSND_SSI(_dma_id, _irq, _flags) \
|
||||
{ .dma_id = _dma_id, .irq = _irq, .flags = _flags }
|
||||
#define RSND_SSI_UNUSED \
|
||||
{ .dma_id = -1, .pio_irq = -1, .flags = 0 }
|
||||
{ .dma_id = -1, .irq = -1, .flags = 0 }
|
||||
|
||||
struct rsnd_ssi_platform_info {
|
||||
int dma_id;
|
||||
int pio_irq;
|
||||
int irq;
|
||||
u32 flags;
|
||||
};
|
||||
|
||||
|
|
|
@ -23,6 +23,10 @@ struct rt5645_platform_data {
|
|||
|
||||
unsigned int hp_det_gpio;
|
||||
bool gpio_hp_det_active_high;
|
||||
|
||||
/* true if codec's jd function is used */
|
||||
bool en_jd_func;
|
||||
unsigned int jd_mode;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -27,6 +27,16 @@ struct rt5677_platform_data {
|
|||
bool lout3_diff;
|
||||
/* DMIC2 clock source selection */
|
||||
enum rt5677_dmic2_clk dmic2_clk_pin;
|
||||
|
||||
/* configures GPIO, 0 - floating, 1 - pulldown, 2 - pullup */
|
||||
u8 gpio_config[6];
|
||||
|
||||
/* jd1 can select 0 ~ 3 as OFF, GPIO1, GPIO2 and GPIO3 respectively */
|
||||
unsigned int jd1_gpio;
|
||||
/* jd2 and jd3 can select 0 ~ 3 as
|
||||
OFF, GPIO4, GPIO5 and GPIO6 respectively */
|
||||
unsigned int jd2_gpio;
|
||||
unsigned int jd3_gpio;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -108,9 +108,13 @@ int snd_seq_event_port_detach(int client, int port);
|
|||
#ifdef CONFIG_MODULES
|
||||
void snd_seq_autoload_lock(void);
|
||||
void snd_seq_autoload_unlock(void);
|
||||
void snd_seq_autoload_init(void);
|
||||
#define snd_seq_autoload_exit() snd_seq_autoload_lock()
|
||||
#else
|
||||
#define snd_seq_autoload_lock()
|
||||
#define snd_seq_autoload_unlock()
|
||||
#define snd_seq_autoload_init()
|
||||
#define snd_seq_autoload_exit()
|
||||
#endif
|
||||
|
||||
#endif /* __SOUND_SEQ_KERNEL_H */
|
||||
|
|
|
@ -206,7 +206,6 @@ struct snd_soc_dai_driver {
|
|||
/* DAI description */
|
||||
const char *name;
|
||||
unsigned int id;
|
||||
int ac97_control;
|
||||
unsigned int base;
|
||||
|
||||
/* DAI driver callbacks */
|
||||
|
@ -216,6 +215,8 @@ struct snd_soc_dai_driver {
|
|||
int (*resume)(struct snd_soc_dai *dai);
|
||||
/* compress dai */
|
||||
bool compress_dai;
|
||||
/* DAI is also used for the control bus */
|
||||
bool bus_control;
|
||||
|
||||
/* ops */
|
||||
const struct snd_soc_dai_ops *ops;
|
||||
|
@ -241,7 +242,6 @@ struct snd_soc_dai {
|
|||
const char *name;
|
||||
int id;
|
||||
struct device *dev;
|
||||
void *ac97_pdata; /* platform_data for the ac97 codec */
|
||||
|
||||
/* driver ops */
|
||||
struct snd_soc_dai_driver *driver;
|
||||
|
@ -268,7 +268,6 @@ struct snd_soc_dai {
|
|||
unsigned int sample_bits;
|
||||
|
||||
/* parent platform/codec */
|
||||
struct snd_soc_platform *platform;
|
||||
struct snd_soc_codec *codec;
|
||||
struct snd_soc_component *component;
|
||||
|
||||
|
@ -276,8 +275,6 @@ struct snd_soc_dai {
|
|||
unsigned int tx_mask;
|
||||
unsigned int rx_mask;
|
||||
|
||||
struct snd_soc_card *card;
|
||||
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
|
|
|
@ -435,7 +435,7 @@ void snd_soc_dapm_auto_nc_pins(struct snd_soc_card *card);
|
|||
unsigned int dapm_kcontrol_get_value(const struct snd_kcontrol *kcontrol);
|
||||
|
||||
/* Mostly internal - should not normally be used */
|
||||
void dapm_mark_io_dirty(struct snd_soc_dapm_context *dapm);
|
||||
void dapm_mark_endpoints_dirty(struct snd_soc_card *card);
|
||||
|
||||
/* dapm path query */
|
||||
int snd_soc_dapm_dai_get_connected_widgets(struct snd_soc_dai *dai, int stream,
|
||||
|
@ -508,9 +508,9 @@ struct snd_soc_dapm_path {
|
|||
|
||||
/* status */
|
||||
u32 connect:1; /* source and sink widgets are connected */
|
||||
u32 walked:1; /* path has been walked */
|
||||
u32 walking:1; /* path is in the process of being walked */
|
||||
u32 weak:1; /* path ignored for power management */
|
||||
u32 is_supply:1; /* At least one of the connected widgets is a supply */
|
||||
|
||||
int (*connected)(struct snd_soc_dapm_widget *source,
|
||||
struct snd_soc_dapm_widget *sink);
|
||||
|
@ -544,11 +544,13 @@ struct snd_soc_dapm_widget {
|
|||
unsigned char active:1; /* active stream on DAC, ADC's */
|
||||
unsigned char connected:1; /* connected codec pin */
|
||||
unsigned char new:1; /* cnew complete */
|
||||
unsigned char ext:1; /* has external widgets */
|
||||
unsigned char force:1; /* force state */
|
||||
unsigned char ignore_suspend:1; /* kept enabled over suspend */
|
||||
unsigned char new_power:1; /* power from this run */
|
||||
unsigned char power_checked:1; /* power checked this run */
|
||||
unsigned char is_supply:1; /* Widget is a supply type widget */
|
||||
unsigned char is_sink:1; /* Widget is a sink type widget */
|
||||
unsigned char is_source:1; /* Widget is a source type widget */
|
||||
int subseq; /* sort within widget type */
|
||||
|
||||
int (*power_check)(struct snd_soc_dapm_widget *w);
|
||||
|
@ -567,6 +569,7 @@ struct snd_soc_dapm_widget {
|
|||
struct list_head sinks;
|
||||
|
||||
/* used during DAPM updates */
|
||||
struct list_head work_list;
|
||||
struct list_head power_list;
|
||||
struct list_head dirty;
|
||||
int inputs;
|
||||
|
|
|
@ -36,6 +36,11 @@
|
|||
{.reg = xreg, .rreg = xreg, .shift = shift_left, \
|
||||
.rshift = shift_right, .max = xmax, .platform_max = xmax, \
|
||||
.invert = xinvert, .autodisable = xautodisable})
|
||||
#define SOC_DOUBLE_S_VALUE(xreg, shift_left, shift_right, xmin, xmax, xsign_bit, xinvert, xautodisable) \
|
||||
((unsigned long)&(struct soc_mixer_control) \
|
||||
{.reg = xreg, .rreg = xreg, .shift = shift_left, \
|
||||
.rshift = shift_right, .min = xmin, .max = xmax, .platform_max = xmax, \
|
||||
.sign_bit = xsign_bit, .invert = xinvert, .autodisable = xautodisable})
|
||||
#define SOC_SINGLE_VALUE(xreg, xshift, xmax, xinvert, xautodisable) \
|
||||
SOC_DOUBLE_VALUE(xreg, xshift, xshift, xmax, xinvert, xautodisable)
|
||||
#define SOC_SINGLE_VALUE_EXT(xreg, xmax, xinvert) \
|
||||
|
@ -171,11 +176,9 @@
|
|||
.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \
|
||||
SNDRV_CTL_ELEM_ACCESS_READWRITE, \
|
||||
.tlv.p = (tlv_array), \
|
||||
.info = snd_soc_info_volsw_s8, .get = snd_soc_get_volsw_s8, \
|
||||
.put = snd_soc_put_volsw_s8, \
|
||||
.private_value = (unsigned long)&(struct soc_mixer_control) \
|
||||
{.reg = xreg, .min = xmin, .max = xmax, \
|
||||
.platform_max = xmax} }
|
||||
.info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\
|
||||
.put = snd_soc_put_volsw, \
|
||||
.private_value = SOC_DOUBLE_S_VALUE(xreg, 0, 8, xmin, xmax, 7, 0, 0) }
|
||||
#define SOC_ENUM_DOUBLE(xreg, xshift_l, xshift_r, xitems, xtexts) \
|
||||
{ .reg = xreg, .shift_l = xshift_l, .shift_r = xshift_r, \
|
||||
.items = xitems, .texts = xtexts, \
|
||||
|
@ -366,8 +369,6 @@ struct snd_soc_jack_gpio;
|
|||
|
||||
typedef int (*hw_write_t)(void *,const char* ,int);
|
||||
|
||||
extern struct snd_ac97_bus_ops *soc_ac97_ops;
|
||||
|
||||
enum snd_soc_pcm_subclass {
|
||||
SND_SOC_PCM_CLASS_PCM = 0,
|
||||
SND_SOC_PCM_CLASS_BE = 1,
|
||||
|
@ -409,13 +410,9 @@ int devm_snd_soc_register_component(struct device *dev,
|
|||
const struct snd_soc_component_driver *cmpnt_drv,
|
||||
struct snd_soc_dai_driver *dai_drv, int num_dai);
|
||||
void snd_soc_unregister_component(struct device *dev);
|
||||
int snd_soc_cache_sync(struct snd_soc_codec *codec);
|
||||
int snd_soc_cache_init(struct snd_soc_codec *codec);
|
||||
int snd_soc_cache_exit(struct snd_soc_codec *codec);
|
||||
int snd_soc_cache_write(struct snd_soc_codec *codec,
|
||||
unsigned int reg, unsigned int value);
|
||||
int snd_soc_cache_read(struct snd_soc_codec *codec,
|
||||
unsigned int reg, unsigned int *value);
|
||||
|
||||
int snd_soc_platform_read(struct snd_soc_platform *platform,
|
||||
unsigned int reg);
|
||||
int snd_soc_platform_write(struct snd_soc_platform *platform,
|
||||
|
@ -500,14 +497,28 @@ int snd_soc_update_bits_locked(struct snd_soc_codec *codec,
|
|||
int snd_soc_test_bits(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int mask, unsigned int value);
|
||||
|
||||
int snd_soc_new_ac97_codec(struct snd_soc_codec *codec,
|
||||
struct snd_ac97_bus_ops *ops, int num);
|
||||
void snd_soc_free_ac97_codec(struct snd_soc_codec *codec);
|
||||
#ifdef CONFIG_SND_SOC_AC97_BUS
|
||||
struct snd_ac97 *snd_soc_new_ac97_codec(struct snd_soc_codec *codec);
|
||||
void snd_soc_free_ac97_codec(struct snd_ac97 *ac97);
|
||||
|
||||
int snd_soc_set_ac97_ops(struct snd_ac97_bus_ops *ops);
|
||||
int snd_soc_set_ac97_ops_of_reset(struct snd_ac97_bus_ops *ops,
|
||||
struct platform_device *pdev);
|
||||
|
||||
extern struct snd_ac97_bus_ops *soc_ac97_ops;
|
||||
#else
|
||||
static inline int snd_soc_set_ac97_ops_of_reset(struct snd_ac97_bus_ops *ops,
|
||||
struct platform_device *pdev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int snd_soc_set_ac97_ops(struct snd_ac97_bus_ops *ops)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
*Controls
|
||||
*/
|
||||
|
@ -545,12 +556,6 @@ int snd_soc_get_volsw_sx(struct snd_kcontrol *kcontrol,
|
|||
struct snd_ctl_elem_value *ucontrol);
|
||||
int snd_soc_put_volsw_sx(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol);
|
||||
int snd_soc_info_volsw_s8(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_info *uinfo);
|
||||
int snd_soc_get_volsw_s8(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol);
|
||||
int snd_soc_put_volsw_s8(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol);
|
||||
int snd_soc_info_volsw_range(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_info *uinfo);
|
||||
int snd_soc_put_volsw_range(struct snd_kcontrol *kcontrol,
|
||||
|
@ -780,24 +785,18 @@ struct snd_soc_codec {
|
|||
struct device *dev;
|
||||
const struct snd_soc_codec_driver *driver;
|
||||
|
||||
struct mutex mutex;
|
||||
struct list_head list;
|
||||
struct list_head card_list;
|
||||
|
||||
/* runtime */
|
||||
struct snd_ac97 *ac97; /* for ad-hoc ac97 devices */
|
||||
unsigned int cache_bypass:1; /* Suppress access to the cache */
|
||||
unsigned int suspended:1; /* Codec is in suspend PM state */
|
||||
unsigned int ac97_registered:1; /* Codec has been AC97 registered */
|
||||
unsigned int ac97_created:1; /* Codec has been created by SoC */
|
||||
unsigned int cache_init:1; /* codec cache has been initialized */
|
||||
u32 cache_sync; /* Cache needs to be synced to hardware */
|
||||
|
||||
/* codec IO */
|
||||
void *control_data; /* codec control (i2c/3wire) data */
|
||||
hw_write_t hw_write;
|
||||
void *reg_cache;
|
||||
struct mutex cache_rw_mutex;
|
||||
|
||||
/* component */
|
||||
struct snd_soc_component component;
|
||||
|
@ -860,8 +859,6 @@ struct snd_soc_platform_driver {
|
|||
|
||||
int (*probe)(struct snd_soc_platform *);
|
||||
int (*remove)(struct snd_soc_platform *);
|
||||
int (*suspend)(struct snd_soc_dai *dai);
|
||||
int (*resume)(struct snd_soc_dai *dai);
|
||||
struct snd_soc_component_driver component_driver;
|
||||
|
||||
/* pcm creation and destruction */
|
||||
|
@ -886,7 +883,7 @@ struct snd_soc_platform_driver {
|
|||
|
||||
struct snd_soc_dai_link_component {
|
||||
const char *name;
|
||||
const struct device_node *of_node;
|
||||
struct device_node *of_node;
|
||||
const char *dai_name;
|
||||
};
|
||||
|
||||
|
@ -894,8 +891,6 @@ struct snd_soc_platform {
|
|||
struct device *dev;
|
||||
const struct snd_soc_platform_driver *driver;
|
||||
|
||||
unsigned int suspended:1; /* platform is suspended */
|
||||
|
||||
struct list_head list;
|
||||
|
||||
struct snd_soc_component component;
|
||||
|
@ -990,7 +985,7 @@ struct snd_soc_codec_conf {
|
|||
* DT/OF node, but not both.
|
||||
*/
|
||||
const char *dev_name;
|
||||
const struct device_node *of_node;
|
||||
struct device_node *of_node;
|
||||
|
||||
/*
|
||||
* optional map of kcontrol, widget and path name prefixes that are
|
||||
|
@ -1007,7 +1002,7 @@ struct snd_soc_aux_dev {
|
|||
* DT/OF node, but not both.
|
||||
*/
|
||||
const char *codec_name;
|
||||
const struct device_node *codec_of_node;
|
||||
struct device_node *codec_of_node;
|
||||
|
||||
/* codec/machine specific init - e.g. add machine controls */
|
||||
int (*init)(struct snd_soc_component *component);
|
||||
|
@ -1264,6 +1259,17 @@ unsigned int snd_soc_read(struct snd_soc_codec *codec, unsigned int reg);
|
|||
int snd_soc_write(struct snd_soc_codec *codec, unsigned int reg,
|
||||
unsigned int val);
|
||||
|
||||
/**
|
||||
* snd_soc_cache_sync() - Sync the register cache with the hardware
|
||||
* @codec: CODEC to sync
|
||||
*
|
||||
* Note: This function will call regcache_sync()
|
||||
*/
|
||||
static inline int snd_soc_cache_sync(struct snd_soc_codec *codec)
|
||||
{
|
||||
return regcache_sync(codec->component.regmap);
|
||||
}
|
||||
|
||||
/* component IO */
|
||||
int snd_soc_component_read(struct snd_soc_component *component,
|
||||
unsigned int reg, unsigned int *val);
|
||||
|
@ -1277,6 +1283,45 @@ void snd_soc_component_async_complete(struct snd_soc_component *component);
|
|||
int snd_soc_component_test_bits(struct snd_soc_component *component,
|
||||
unsigned int reg, unsigned int mask, unsigned int value);
|
||||
|
||||
#ifdef CONFIG_REGMAP
|
||||
|
||||
void snd_soc_component_init_regmap(struct snd_soc_component *component,
|
||||
struct regmap *regmap);
|
||||
void snd_soc_component_exit_regmap(struct snd_soc_component *component);
|
||||
|
||||
/**
|
||||
* snd_soc_codec_init_regmap() - Initialize regmap instance for the CODEC
|
||||
* @codec: The CODEC for which to initialize the regmap instance
|
||||
* @regmap: The regmap instance that should be used by the CODEC
|
||||
*
|
||||
* This function allows deferred assignment of the regmap instance that is
|
||||
* associated with the CODEC. Only use this if the regmap instance is not yet
|
||||
* ready when the CODEC is registered. The function must also be called before
|
||||
* the first IO attempt of the CODEC.
|
||||
*/
|
||||
static inline void snd_soc_codec_init_regmap(struct snd_soc_codec *codec,
|
||||
struct regmap *regmap)
|
||||
{
|
||||
snd_soc_component_init_regmap(&codec->component, regmap);
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_soc_codec_exit_regmap() - De-initialize regmap instance for the CODEC
|
||||
* @codec: The CODEC for which to de-initialize the regmap instance
|
||||
*
|
||||
* Calls regmap_exit() on the regmap instance associated to the CODEC and
|
||||
* removes the regmap instance from the CODEC.
|
||||
*
|
||||
* This function should only be used if snd_soc_codec_init_regmap() was used to
|
||||
* initialize the regmap instance.
|
||||
*/
|
||||
static inline void snd_soc_codec_exit_regmap(struct snd_soc_codec *codec)
|
||||
{
|
||||
snd_soc_component_exit_regmap(&codec->component);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/* device driver data */
|
||||
|
||||
static inline void snd_soc_card_set_drvdata(struct snd_soc_card *card,
|
||||
|
@ -1451,6 +1496,9 @@ unsigned int snd_soc_of_parse_daifmt(struct device_node *np,
|
|||
struct device_node **framemaster);
|
||||
int snd_soc_of_get_dai_name(struct device_node *of_node,
|
||||
const char **dai_name);
|
||||
int snd_soc_of_get_dai_link_codecs(struct device *dev,
|
||||
struct device_node *of_node,
|
||||
struct snd_soc_dai_link *dai_link);
|
||||
|
||||
#include <sound/soc-dai.h>
|
||||
|
||||
|
|
|
@ -18,18 +18,6 @@ struct uda134x_platform_data {
|
|||
struct l3_pins l3;
|
||||
void (*power) (int);
|
||||
int model;
|
||||
/*
|
||||
ALSA SOC usually puts the device in standby mode when it's not used
|
||||
for sometime. If you unset is_powered_on_standby the driver will
|
||||
turn off the ADC/DAC when this callback is invoked and turn it back
|
||||
on when needed. Unfortunately this will result in a very light bump
|
||||
(it can be audible only with good earphones). If this bothers you
|
||||
set is_powered_on_standby, you will have slightly higher power
|
||||
consumption. Please note that sending the L3 command for ADC is
|
||||
enough to make the bump, so it doesn't make difference if you
|
||||
completely take off power from the codec.
|
||||
*/
|
||||
int is_powered_on_standby;
|
||||
#define UDA134X_UDA1340 1
|
||||
#define UDA134X_UDA1341 2
|
||||
#define UDA134X_UDA1344 3
|
||||
|
|
|
@ -288,31 +288,6 @@ TRACE_EVENT(snd_soc_jack_notify,
|
|||
TP_printk("jack=%s %x", __get_str(name), (int)__entry->val)
|
||||
);
|
||||
|
||||
TRACE_EVENT(snd_soc_cache_sync,
|
||||
|
||||
TP_PROTO(struct snd_soc_codec *codec, const char *type,
|
||||
const char *status),
|
||||
|
||||
TP_ARGS(codec, type, status),
|
||||
|
||||
TP_STRUCT__entry(
|
||||
__string( name, codec->component.name)
|
||||
__string( status, status )
|
||||
__string( type, type )
|
||||
__field( int, id )
|
||||
),
|
||||
|
||||
TP_fast_assign(
|
||||
__assign_str(name, codec->component.name);
|
||||
__assign_str(status, status);
|
||||
__assign_str(type, type);
|
||||
__entry->id = codec->component.id;
|
||||
),
|
||||
|
||||
TP_printk("codec=%s.%d type=%s status=%s", __get_str(name),
|
||||
(int)__entry->id, __get_str(type), __get_str(status))
|
||||
);
|
||||
|
||||
#endif /* _TRACE_ASOC_H */
|
||||
|
||||
/* This part must be outside protection */
|
||||
|
|
|
@ -96,9 +96,10 @@ enum {
|
|||
SNDRV_HWDEP_IFACE_FW_DICE, /* TC DICE FireWire device */
|
||||
SNDRV_HWDEP_IFACE_FW_FIREWORKS, /* Echo Audio Fireworks based device */
|
||||
SNDRV_HWDEP_IFACE_FW_BEBOB, /* BridgeCo BeBoB based device */
|
||||
SNDRV_HWDEP_IFACE_FW_OXFW, /* Oxford OXFW970/971 based device */
|
||||
|
||||
/* Don't forget to change the following: */
|
||||
SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_BEBOB
|
||||
SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_OXFW
|
||||
};
|
||||
|
||||
struct snd_hwdep_info {
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
|
||||
#define SNDRV_COMPRESS_VERSION SNDRV_PROTOCOL_VERSION(0, 1, 2)
|
||||
/**
|
||||
* struct snd_compressed_buffer: compressed buffer
|
||||
* struct snd_compressed_buffer - compressed buffer
|
||||
* @fragment_size: size of buffer fragment in bytes
|
||||
* @fragments: number of such fragments
|
||||
*/
|
||||
|
@ -42,7 +42,7 @@ struct snd_compressed_buffer {
|
|||
} __attribute__((packed, aligned(4)));
|
||||
|
||||
/**
|
||||
* struct snd_compr_params: compressed stream params
|
||||
* struct snd_compr_params - compressed stream params
|
||||
* @buffer: buffer description
|
||||
* @codec: codec parameters
|
||||
* @no_wake_mode: dont wake on fragment elapsed
|
||||
|
@ -54,7 +54,7 @@ struct snd_compr_params {
|
|||
} __attribute__((packed, aligned(4)));
|
||||
|
||||
/**
|
||||
* struct snd_compr_tstamp: timestamp descriptor
|
||||
* struct snd_compr_tstamp - timestamp descriptor
|
||||
* @byte_offset: Byte offset in ring buffer to DSP
|
||||
* @copied_total: Total number of bytes copied from/to ring buffer to/by DSP
|
||||
* @pcm_frames: Frames decoded or encoded by DSP. This field will evolve by
|
||||
|
@ -73,7 +73,7 @@ struct snd_compr_tstamp {
|
|||
} __attribute__((packed, aligned(4)));
|
||||
|
||||
/**
|
||||
* struct snd_compr_avail: avail descriptor
|
||||
* struct snd_compr_avail - avail descriptor
|
||||
* @avail: Number of bytes available in ring buffer for writing/reading
|
||||
* @tstamp: timestamp infomation
|
||||
*/
|
||||
|
@ -88,7 +88,7 @@ enum snd_compr_direction {
|
|||
};
|
||||
|
||||
/**
|
||||
* struct snd_compr_caps: caps descriptor
|
||||
* struct snd_compr_caps - caps descriptor
|
||||
* @codecs: pointer to array of codecs
|
||||
* @direction: direction supported. Of type snd_compr_direction
|
||||
* @min_fragment_size: minimum fragment supported by DSP
|
||||
|
@ -110,7 +110,7 @@ struct snd_compr_caps {
|
|||
} __attribute__((packed, aligned(4)));
|
||||
|
||||
/**
|
||||
* struct snd_compr_codec_caps: query capability of codec
|
||||
* struct snd_compr_codec_caps - query capability of codec
|
||||
* @codec: codec for which capability is queried
|
||||
* @num_descriptors: number of codec descriptors
|
||||
* @descriptor: array of codec capability descriptor
|
||||
|
@ -122,18 +122,19 @@ struct snd_compr_codec_caps {
|
|||
} __attribute__((packed, aligned(4)));
|
||||
|
||||
/**
|
||||
* enum sndrv_compress_encoder
|
||||
* @SNDRV_COMPRESS_ENCODER_PADDING: no of samples appended by the encoder at the
|
||||
* end of the track
|
||||
* @SNDRV_COMPRESS_ENCODER_DELAY: no of samples inserted by the encoder at the
|
||||
* beginning of the track
|
||||
*/
|
||||
enum {
|
||||
enum sndrv_compress_encoder {
|
||||
SNDRV_COMPRESS_ENCODER_PADDING = 1,
|
||||
SNDRV_COMPRESS_ENCODER_DELAY = 2,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct snd_compr_metadata: compressed stream metadata
|
||||
* struct snd_compr_metadata - compressed stream metadata
|
||||
* @key: key id
|
||||
* @value: key value
|
||||
*/
|
||||
|
|
|
@ -55,7 +55,8 @@ union snd_firewire_event {
|
|||
#define SNDRV_FIREWIRE_TYPE_DICE 1
|
||||
#define SNDRV_FIREWIRE_TYPE_FIREWORKS 2
|
||||
#define SNDRV_FIREWIRE_TYPE_BEBOB 3
|
||||
/* AV/C, RME, MOTU, ... */
|
||||
#define SNDRV_FIREWIRE_TYPE_OXFW 4
|
||||
/* RME, MOTU, ... */
|
||||
|
||||
struct snd_firewire_get_info {
|
||||
unsigned int type; /* SNDRV_FIREWIRE_TYPE_xxx */
|
||||
|
|
|
@ -74,14 +74,14 @@ struct hdspm_config {
|
|||
#define SNDRV_HDSPM_IOCTL_GET_CONFIG \
|
||||
_IOR('H', 0x41, struct hdspm_config)
|
||||
|
||||
/**
|
||||
/*
|
||||
* If there's a TCO (TimeCode Option) board installed,
|
||||
* there are further options and status data available.
|
||||
* The hdspm_ltc structure contains the current SMPTE
|
||||
* timecode and some status information and can be
|
||||
* obtained via SNDRV_HDSPM_IOCTL_GET_LTC or in the
|
||||
* hdspm_status struct.
|
||||
**/
|
||||
*/
|
||||
|
||||
enum hdspm_ltc_format {
|
||||
format_invalid,
|
||||
|
@ -113,11 +113,11 @@ struct hdspm_ltc {
|
|||
|
||||
#define SNDRV_HDSPM_IOCTL_GET_LTC _IOR('H', 0x46, struct hdspm_ltc)
|
||||
|
||||
/**
|
||||
/*
|
||||
* The status data reflects the device's current state
|
||||
* as determined by the card's configuration and
|
||||
* connection status.
|
||||
**/
|
||||
*/
|
||||
|
||||
enum hdspm_sync {
|
||||
hdspm_sync_no_lock = 0,
|
||||
|
@ -171,9 +171,9 @@ struct hdspm_status {
|
|||
#define SNDRV_HDSPM_IOCTL_GET_STATUS \
|
||||
_IOR('H', 0x47, struct hdspm_status)
|
||||
|
||||
/**
|
||||
/*
|
||||
* Get information about the card and its add-ons.
|
||||
**/
|
||||
*/
|
||||
|
||||
#define HDSPM_ADDON_TCO 1
|
||||
|
||||
|
|
|
@ -243,13 +243,7 @@ static int onyx_snd_capture_source_info(struct snd_kcontrol *kcontrol,
|
|||
{
|
||||
static const char * const texts[] = { "Line-In", "Microphone" };
|
||||
|
||||
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
|
||||
uinfo->count = 1;
|
||||
uinfo->value.enumerated.items = 2;
|
||||
if (uinfo->value.enumerated.item > 1)
|
||||
uinfo->value.enumerated.item = 1;
|
||||
strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
|
||||
return 0;
|
||||
return snd_ctl_enum_info(uinfo, 1, 2, texts);
|
||||
}
|
||||
|
||||
static int onyx_snd_capture_source_get(struct snd_kcontrol *kcontrol,
|
||||
|
|
|
@ -478,15 +478,9 @@ static struct snd_kcontrol_new drc_switch_control = {
|
|||
static int tas_snd_capture_source_info(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_info *uinfo)
|
||||
{
|
||||
static char *texts[] = { "Line-In", "Microphone" };
|
||||
static const char * const texts[] = { "Line-In", "Microphone" };
|
||||
|
||||
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
|
||||
uinfo->count = 1;
|
||||
uinfo->value.enumerated.items = 2;
|
||||
if (uinfo->value.enumerated.item > 1)
|
||||
uinfo->value.enumerated.item = 1;
|
||||
strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
|
||||
return 0;
|
||||
return snd_ctl_enum_info(uinfo, 1, 2, texts);
|
||||
}
|
||||
|
||||
static int tas_snd_capture_source_get(struct snd_kcontrol *kcontrol,
|
||||
|
|
|
@ -79,8 +79,7 @@ static void i2sbus_release_dev(struct device *dev)
|
|||
if (i2sdev->out.dbdma) iounmap(i2sdev->out.dbdma);
|
||||
if (i2sdev->in.dbdma) iounmap(i2sdev->in.dbdma);
|
||||
for (i = aoa_resource_i2smmio; i <= aoa_resource_rxdbdma; i++)
|
||||
if (i2sdev->allocated_resource[i])
|
||||
release_and_free_resource(i2sdev->allocated_resource[i]);
|
||||
release_and_free_resource(i2sdev->allocated_resource[i]);
|
||||
free_dbdma_descriptor_ring(i2sdev, &i2sdev->out.dbdma_ring);
|
||||
free_dbdma_descriptor_ring(i2sdev, &i2sdev->in.dbdma_ring);
|
||||
for (i = aoa_resource_i2smmio; i <= aoa_resource_rxdbdma; i++)
|
||||
|
@ -323,8 +322,7 @@ static int i2sbus_add_dev(struct macio_dev *macio,
|
|||
if (dev->out.dbdma) iounmap(dev->out.dbdma);
|
||||
if (dev->in.dbdma) iounmap(dev->in.dbdma);
|
||||
for (i=0;i<3;i++)
|
||||
if (dev->allocated_resource[i])
|
||||
release_and_free_resource(dev->allocated_resource[i]);
|
||||
release_and_free_resource(dev->allocated_resource[i]);
|
||||
mutex_destroy(&dev->lock);
|
||||
kfree(dev);
|
||||
return 0;
|
||||
|
|
|
@ -200,9 +200,7 @@ void pxa2xx_pcm_dma_irq(int dma_ch, void *dev_id)
|
|||
} else {
|
||||
printk(KERN_ERR "DMA error on channel %d (DCSR=%#x)\n",
|
||||
dma_ch, dcsr);
|
||||
snd_pcm_stream_lock(substream);
|
||||
snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
|
||||
snd_pcm_stream_unlock(substream);
|
||||
snd_pcm_stop_xrun(substream);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(pxa2xx_pcm_dma_irq);
|
||||
|
|
|
@ -242,7 +242,7 @@ static int atmel_abdac_trigger(struct snd_pcm_substream *substream, int cmd)
|
|||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: /* fall through */
|
||||
case SNDRV_PCM_TRIGGER_RESUME: /* fall through */
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
clk_enable(dac->sample_clk);
|
||||
clk_prepare_enable(dac->sample_clk);
|
||||
retval = dw_dma_cyclic_start(dac->dma.chan);
|
||||
if (retval)
|
||||
goto out;
|
||||
|
@ -254,7 +254,7 @@ static int atmel_abdac_trigger(struct snd_pcm_substream *substream, int cmd)
|
|||
dw_dma_cyclic_stop(dac->dma.chan);
|
||||
dac_writel(dac, DATA, 0);
|
||||
dac_writel(dac, CTRL, 0);
|
||||
clk_disable(dac->sample_clk);
|
||||
clk_disable_unprepare(dac->sample_clk);
|
||||
break;
|
||||
default:
|
||||
retval = -EINVAL;
|
||||
|
@ -429,7 +429,7 @@ static int atmel_abdac_probe(struct platform_device *pdev)
|
|||
retval = PTR_ERR(sample_clk);
|
||||
goto out_put_pclk;
|
||||
}
|
||||
clk_enable(pclk);
|
||||
clk_prepare_enable(pclk);
|
||||
|
||||
retval = snd_card_new(&pdev->dev, SNDRV_DEFAULT_IDX1,
|
||||
SNDRV_DEFAULT_STR1, THIS_MODULE,
|
||||
|
@ -528,7 +528,7 @@ out_free_card:
|
|||
snd_card_free(card);
|
||||
out_put_sample_clk:
|
||||
clk_put(sample_clk);
|
||||
clk_disable(pclk);
|
||||
clk_disable_unprepare(pclk);
|
||||
out_put_pclk:
|
||||
clk_put(pclk);
|
||||
return retval;
|
||||
|
@ -541,8 +541,8 @@ static int atmel_abdac_suspend(struct device *pdev)
|
|||
struct atmel_abdac *dac = card->private_data;
|
||||
|
||||
dw_dma_cyclic_stop(dac->dma.chan);
|
||||
clk_disable(dac->sample_clk);
|
||||
clk_disable(dac->pclk);
|
||||
clk_disable_unprepare(dac->sample_clk);
|
||||
clk_disable_unprepare(dac->pclk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -552,8 +552,8 @@ static int atmel_abdac_resume(struct device *pdev)
|
|||
struct snd_card *card = dev_get_drvdata(pdev);
|
||||
struct atmel_abdac *dac = card->private_data;
|
||||
|
||||
clk_enable(dac->pclk);
|
||||
clk_enable(dac->sample_clk);
|
||||
clk_prepare_enable(dac->pclk);
|
||||
clk_prepare_enable(dac->sample_clk);
|
||||
if (test_bit(DMA_READY, &dac->flags))
|
||||
dw_dma_cyclic_start(dac->dma.chan);
|
||||
|
||||
|
@ -572,7 +572,7 @@ static int atmel_abdac_remove(struct platform_device *pdev)
|
|||
struct atmel_abdac *dac = get_dac(card);
|
||||
|
||||
clk_put(dac->sample_clk);
|
||||
clk_disable(dac->pclk);
|
||||
clk_disable_unprepare(dac->pclk);
|
||||
clk_put(dac->pclk);
|
||||
|
||||
dma_release_channel(dac->dma.chan);
|
||||
|
|
|
@ -773,7 +773,7 @@ static int atmel_ac97c_pcm_new(struct atmel_ac97c *chip)
|
|||
return err;
|
||||
}
|
||||
retval = snd_pcm_new(chip->card, chip->card->shortname,
|
||||
chip->pdev->id, playback, capture, &pcm);
|
||||
0, playback, capture, &pcm);
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
|
@ -944,7 +944,7 @@ static int atmel_ac97c_probe(struct platform_device *pdev)
|
|||
dev_dbg(&pdev->dev, "no peripheral clock\n");
|
||||
return PTR_ERR(pclk);
|
||||
}
|
||||
clk_enable(pclk);
|
||||
clk_prepare_enable(pclk);
|
||||
|
||||
retval = snd_card_new(&pdev->dev, SNDRV_DEFAULT_IDX1,
|
||||
SNDRV_DEFAULT_STR1, THIS_MODULE,
|
||||
|
@ -1122,7 +1122,7 @@ err_ioremap:
|
|||
err_request_irq:
|
||||
snd_card_free(card);
|
||||
err_snd_card_new:
|
||||
clk_disable(pclk);
|
||||
clk_disable_unprepare(pclk);
|
||||
clk_put(pclk);
|
||||
return retval;
|
||||
}
|
||||
|
@ -1139,7 +1139,7 @@ static int atmel_ac97c_suspend(struct device *pdev)
|
|||
if (test_bit(DMA_TX_READY, &chip->flags))
|
||||
dw_dma_cyclic_stop(chip->dma.tx_chan);
|
||||
}
|
||||
clk_disable(chip->pclk);
|
||||
clk_disable_unprepare(chip->pclk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1149,7 +1149,7 @@ static int atmel_ac97c_resume(struct device *pdev)
|
|||
struct snd_card *card = dev_get_drvdata(pdev);
|
||||
struct atmel_ac97c *chip = card->private_data;
|
||||
|
||||
clk_enable(chip->pclk);
|
||||
clk_prepare_enable(chip->pclk);
|
||||
if (cpu_is_at32ap7000()) {
|
||||
if (test_bit(DMA_RX_READY, &chip->flags))
|
||||
dw_dma_cyclic_start(chip->dma.rx_chan);
|
||||
|
@ -1177,7 +1177,7 @@ static int atmel_ac97c_remove(struct platform_device *pdev)
|
|||
ac97c_writel(chip, COMR, 0);
|
||||
ac97c_writel(chip, MR, 0);
|
||||
|
||||
clk_disable(chip->pclk);
|
||||
clk_disable_unprepare(chip->pclk);
|
||||
clk_put(chip->pclk);
|
||||
iounmap(chip->regs);
|
||||
free_irq(chip->irq, chip);
|
||||
|
|
|
@ -14,6 +14,9 @@ snd-pcm-y := pcm.o pcm_native.o pcm_lib.o pcm_timer.o pcm_misc.o \
|
|||
pcm_memory.o memalloc.o
|
||||
snd-pcm-$(CONFIG_SND_DMA_SGBUF) += sgbuf.o
|
||||
|
||||
# for trace-points
|
||||
CFLAGS_pcm_lib.o := -I$(src)
|
||||
|
||||
snd-pcm-dmaengine-objs := pcm_dmaengine.o
|
||||
|
||||
snd-rawmidi-objs := rawmidi.o
|
||||
|
|
|
@ -141,6 +141,16 @@ static int snd_ctl_release(struct inode *inode, struct file *file)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_ctl_notify - Send notification to user-space for a control change
|
||||
* @card: the card to send notification
|
||||
* @mask: the event mask, SNDRV_CTL_EVENT_*
|
||||
* @id: the ctl element id to send notification
|
||||
*
|
||||
* This function adds an event record with the given id and mask, appends
|
||||
* to the list and wakes up the user-space for notification. This can be
|
||||
* called in the atomic context.
|
||||
*/
|
||||
void snd_ctl_notify(struct snd_card *card, unsigned int mask,
|
||||
struct snd_ctl_elem_id *id)
|
||||
{
|
||||
|
@ -179,7 +189,6 @@ void snd_ctl_notify(struct snd_card *card, unsigned int mask,
|
|||
}
|
||||
read_unlock(&card->ctl_files_rwlock);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_ctl_notify);
|
||||
|
||||
/**
|
||||
|
@ -261,7 +270,6 @@ struct snd_kcontrol *snd_ctl_new1(const struct snd_kcontrol_new *ncontrol,
|
|||
kctl.private_data = private_data;
|
||||
return snd_ctl_new(&kctl, access);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_ctl_new1);
|
||||
|
||||
/**
|
||||
|
@ -280,7 +288,6 @@ void snd_ctl_free_one(struct snd_kcontrol *kcontrol)
|
|||
kfree(kcontrol);
|
||||
}
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_ctl_free_one);
|
||||
|
||||
static bool snd_ctl_remove_numid_conflict(struct snd_card *card,
|
||||
|
@ -376,7 +383,6 @@ int snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol)
|
|||
snd_ctl_free_one(kcontrol);
|
||||
return err;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_ctl_add);
|
||||
|
||||
/**
|
||||
|
@ -471,7 +477,6 @@ int snd_ctl_remove(struct snd_card *card, struct snd_kcontrol *kcontrol)
|
|||
snd_ctl_free_one(kcontrol);
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_ctl_remove);
|
||||
|
||||
/**
|
||||
|
@ -499,7 +504,6 @@ int snd_ctl_remove_id(struct snd_card *card, struct snd_ctl_elem_id *id)
|
|||
up_write(&card->controls_rwsem);
|
||||
return ret;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_ctl_remove_id);
|
||||
|
||||
/**
|
||||
|
@ -568,7 +572,7 @@ int snd_ctl_activate_id(struct snd_card *card, struct snd_ctl_elem_id *id,
|
|||
ret = -ENOENT;
|
||||
goto unlock;
|
||||
}
|
||||
index_offset = snd_ctl_get_ioff(kctl, &kctl->id);
|
||||
index_offset = snd_ctl_get_ioff(kctl, id);
|
||||
vd = &kctl->vd[index_offset];
|
||||
ret = 0;
|
||||
if (active) {
|
||||
|
@ -617,7 +621,6 @@ int snd_ctl_rename_id(struct snd_card *card, struct snd_ctl_elem_id *src_id,
|
|||
up_write(&card->controls_rwsem);
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_ctl_rename_id);
|
||||
|
||||
/**
|
||||
|
@ -645,7 +648,6 @@ struct snd_kcontrol *snd_ctl_find_numid(struct snd_card *card, unsigned int numi
|
|||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_ctl_find_numid);
|
||||
|
||||
/**
|
||||
|
@ -687,7 +689,6 @@ struct snd_kcontrol *snd_ctl_find_id(struct snd_card *card,
|
|||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_ctl_find_id);
|
||||
|
||||
static int snd_ctl_card_info(struct snd_card *card, struct snd_ctl_file * ctl,
|
||||
|
@ -1526,19 +1527,28 @@ static int _snd_ctl_register_ioctl(snd_kctl_ioctl_func_t fcn, struct list_head *
|
|||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_ctl_register_ioctl - register the device-specific control-ioctls
|
||||
* @fcn: ioctl callback function
|
||||
*
|
||||
* called from each device manager like pcm.c, hwdep.c, etc.
|
||||
*/
|
||||
int snd_ctl_register_ioctl(snd_kctl_ioctl_func_t fcn)
|
||||
{
|
||||
return _snd_ctl_register_ioctl(fcn, &snd_control_ioctls);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_ctl_register_ioctl);
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
/**
|
||||
* snd_ctl_register_ioctl_compat - register the device-specific 32bit compat
|
||||
* control-ioctls
|
||||
* @fcn: ioctl callback function
|
||||
*/
|
||||
int snd_ctl_register_ioctl_compat(snd_kctl_ioctl_func_t fcn)
|
||||
{
|
||||
return _snd_ctl_register_ioctl(fcn, &snd_control_compat_ioctls);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_ctl_register_ioctl_compat);
|
||||
#endif
|
||||
|
||||
|
@ -1566,19 +1576,26 @@ static int _snd_ctl_unregister_ioctl(snd_kctl_ioctl_func_t fcn,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_ctl_unregister_ioctl - de-register the device-specific control-ioctls
|
||||
* @fcn: ioctl callback function to unregister
|
||||
*/
|
||||
int snd_ctl_unregister_ioctl(snd_kctl_ioctl_func_t fcn)
|
||||
{
|
||||
return _snd_ctl_unregister_ioctl(fcn, &snd_control_ioctls);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_ctl_unregister_ioctl);
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
/**
|
||||
* snd_ctl_unregister_ioctl - de-register the device-specific compat 32bit
|
||||
* control-ioctls
|
||||
* @fcn: ioctl callback function to unregister
|
||||
*/
|
||||
int snd_ctl_unregister_ioctl_compat(snd_kctl_ioctl_func_t fcn)
|
||||
{
|
||||
return _snd_ctl_unregister_ioctl(fcn, &snd_control_compat_ioctls);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_ctl_unregister_ioctl_compat);
|
||||
#endif
|
||||
|
||||
|
@ -1702,6 +1719,16 @@ int snd_ctl_create(struct snd_card *card)
|
|||
/*
|
||||
* Frequently used control callbacks/helpers
|
||||
*/
|
||||
|
||||
/**
|
||||
* snd_ctl_boolean_mono_info - Helper function for a standard boolean info
|
||||
* callback with a mono channel
|
||||
* @kcontrol: the kcontrol instance
|
||||
* @uinfo: info to store
|
||||
*
|
||||
* This is a function that can be used as info callback for a standard
|
||||
* boolean control with a single mono channel.
|
||||
*/
|
||||
int snd_ctl_boolean_mono_info(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_info *uinfo)
|
||||
{
|
||||
|
@ -1711,9 +1738,17 @@ int snd_ctl_boolean_mono_info(struct snd_kcontrol *kcontrol,
|
|||
uinfo->value.integer.max = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_ctl_boolean_mono_info);
|
||||
|
||||
/**
|
||||
* snd_ctl_boolean_stereo_info - Helper function for a standard boolean info
|
||||
* callback with stereo two channels
|
||||
* @kcontrol: the kcontrol instance
|
||||
* @uinfo: info to store
|
||||
*
|
||||
* This is a function that can be used as info callback for a standard
|
||||
* boolean control with stereo two channels.
|
||||
*/
|
||||
int snd_ctl_boolean_stereo_info(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_info *uinfo)
|
||||
{
|
||||
|
@ -1723,7 +1758,6 @@ int snd_ctl_boolean_stereo_info(struct snd_kcontrol *kcontrol,
|
|||
uinfo->value.integer.max = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_ctl_boolean_stereo_info);
|
||||
|
||||
/**
|
||||
|
@ -1745,8 +1779,13 @@ int snd_ctl_enum_info(struct snd_ctl_elem_info *info, unsigned int channels,
|
|||
info->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
|
||||
info->count = channels;
|
||||
info->value.enumerated.items = items;
|
||||
if (!items)
|
||||
return 0;
|
||||
if (info->value.enumerated.item >= items)
|
||||
info->value.enumerated.item = items - 1;
|
||||
WARN(strlen(names[info->value.enumerated.item]) >= sizeof(info->value.enumerated.name),
|
||||
"ALSA: too long item name '%s'\n",
|
||||
names[info->value.enumerated.item]);
|
||||
strlcpy(info->value.enumerated.name,
|
||||
names[info->value.enumerated.item],
|
||||
sizeof(info->value.enumerated.name));
|
||||
|
|
|
@ -438,17 +438,6 @@ int snd_card_disconnect(struct snd_card *card)
|
|||
|
||||
EXPORT_SYMBOL(snd_card_disconnect);
|
||||
|
||||
/**
|
||||
* snd_card_free - frees given soundcard structure
|
||||
* @card: soundcard structure
|
||||
*
|
||||
* This function releases the soundcard structure and the all assigned
|
||||
* devices automatically. That is, you don't have to release the devices
|
||||
* by yourself.
|
||||
*
|
||||
* Return: Zero. Frees all associated devices and frees the control
|
||||
* interface associated to given soundcard.
|
||||
*/
|
||||
static int snd_card_do_free(struct snd_card *card)
|
||||
{
|
||||
#if IS_ENABLED(CONFIG_SND_MIXER_OSS)
|
||||
|
@ -469,6 +458,15 @@ static int snd_card_do_free(struct snd_card *card)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_card_free_when_closed - Disconnect the card, free it later eventually
|
||||
* @card: soundcard structure
|
||||
*
|
||||
* Unlike snd_card_free(), this function doesn't try to release the card
|
||||
* resource immediately, but tries to disconnect at first. When the card
|
||||
* is still in use, the function returns before freeing the resources.
|
||||
* The card resources will be freed when the refcount gets to zero.
|
||||
*/
|
||||
int snd_card_free_when_closed(struct snd_card *card)
|
||||
{
|
||||
int ret = snd_card_disconnect(card);
|
||||
|
@ -479,6 +477,19 @@ int snd_card_free_when_closed(struct snd_card *card)
|
|||
}
|
||||
EXPORT_SYMBOL(snd_card_free_when_closed);
|
||||
|
||||
/**
|
||||
* snd_card_free - frees given soundcard structure
|
||||
* @card: soundcard structure
|
||||
*
|
||||
* This function releases the soundcard structure and the all assigned
|
||||
* devices automatically. That is, you don't have to release the devices
|
||||
* by yourself.
|
||||
*
|
||||
* This function waits until the all resources are properly released.
|
||||
*
|
||||
* Return: Zero. Frees all associated devices and frees the control
|
||||
* interface associated to given soundcard.
|
||||
*/
|
||||
int snd_card_free(struct snd_card *card)
|
||||
{
|
||||
struct completion released;
|
||||
|
|
|
@ -220,6 +220,10 @@ static char *snd_pcm_format_names[] = {
|
|||
FORMAT(DSD_U32_BE),
|
||||
};
|
||||
|
||||
/**
|
||||
* snd_pcm_format_name - Return a name string for the given PCM format
|
||||
* @format: PCM format
|
||||
*/
|
||||
const char *snd_pcm_format_name(snd_pcm_format_t format)
|
||||
{
|
||||
if ((__force unsigned int)format >= ARRAY_SIZE(snd_pcm_format_names))
|
||||
|
@ -481,6 +485,19 @@ static void snd_pcm_substream_proc_status_read(struct snd_info_entry *entry,
|
|||
}
|
||||
|
||||
#ifdef CONFIG_SND_PCM_XRUN_DEBUG
|
||||
static void snd_pcm_xrun_injection_write(struct snd_info_entry *entry,
|
||||
struct snd_info_buffer *buffer)
|
||||
{
|
||||
struct snd_pcm_substream *substream = entry->private_data;
|
||||
struct snd_pcm_runtime *runtime;
|
||||
|
||||
snd_pcm_stream_lock_irq(substream);
|
||||
runtime = substream->runtime;
|
||||
if (runtime && runtime->status->state == SNDRV_PCM_STATE_RUNNING)
|
||||
snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
|
||||
snd_pcm_stream_unlock_irq(substream);
|
||||
}
|
||||
|
||||
static void snd_pcm_xrun_debug_read(struct snd_info_entry *entry,
|
||||
struct snd_info_buffer *buffer)
|
||||
{
|
||||
|
@ -612,6 +629,22 @@ static int snd_pcm_substream_proc_init(struct snd_pcm_substream *substream)
|
|||
}
|
||||
substream->proc_status_entry = entry;
|
||||
|
||||
#ifdef CONFIG_SND_PCM_XRUN_DEBUG
|
||||
entry = snd_info_create_card_entry(card, "xrun_injection",
|
||||
substream->proc_root);
|
||||
if (entry) {
|
||||
entry->private_data = substream;
|
||||
entry->c.text.read = NULL;
|
||||
entry->c.text.write = snd_pcm_xrun_injection_write;
|
||||
entry->mode = S_IFREG | S_IWUSR;
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -625,6 +658,10 @@ static int snd_pcm_substream_proc_done(struct snd_pcm_substream *substream)
|
|||
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;
|
||||
|
@ -709,7 +746,6 @@ int snd_pcm_new_stream(struct snd_pcm *pcm, int stream, int substream_count)
|
|||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_pcm_new_stream);
|
||||
|
||||
static int _snd_pcm_new(struct snd_card *card, const char *id, int device,
|
||||
|
@ -1157,6 +1193,15 @@ static int snd_pcm_dev_disconnect(struct snd_device *device)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_pcm_notify - Add/remove the notify list
|
||||
* @notify: PCM notify list
|
||||
* @nfree: 0 = register, 1 = unregister
|
||||
*
|
||||
* This adds the given notifier to the global list so that the callback is
|
||||
* called for each registered PCM devices. This exists only for PCM OSS
|
||||
* emulation, so far.
|
||||
*/
|
||||
int snd_pcm_notify(struct snd_pcm_notify *notify, int nfree)
|
||||
{
|
||||
struct snd_pcm *pcm;
|
||||
|
@ -1179,7 +1224,6 @@ int snd_pcm_notify(struct snd_pcm_notify *notify, int nfree)
|
|||
mutex_unlock(®ister_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_pcm_notify);
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
|
|
|
@ -32,6 +32,15 @@
|
|||
#include <sound/pcm_params.h>
|
||||
#include <sound/timer.h>
|
||||
|
||||
#ifdef CONFIG_SND_PCM_XRUN_DEBUG
|
||||
#define CREATE_TRACE_POINTS
|
||||
#include "pcm_trace.h"
|
||||
#else
|
||||
#define trace_hwptr(substream, pos, in_interrupt)
|
||||
#define trace_xrun(substream)
|
||||
#define trace_hw_ptr_error(substream, reason)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* fill ring buffer with silence
|
||||
* runtime->silence_start: starting pointer to silence area
|
||||
|
@ -146,10 +155,6 @@ EXPORT_SYMBOL(snd_pcm_debug_name);
|
|||
#define XRUN_DEBUG_BASIC (1<<0)
|
||||
#define XRUN_DEBUG_STACK (1<<1) /* dump also stack */
|
||||
#define XRUN_DEBUG_JIFFIESCHECK (1<<2) /* do jiffies check */
|
||||
#define XRUN_DEBUG_PERIODUPDATE (1<<3) /* full period update info */
|
||||
#define XRUN_DEBUG_HWPTRUPDATE (1<<4) /* full hwptr update info */
|
||||
#define XRUN_DEBUG_LOG (1<<5) /* show last 10 positions on err */
|
||||
#define XRUN_DEBUG_LOGONCE (1<<6) /* do above only once */
|
||||
|
||||
#ifdef CONFIG_SND_PCM_XRUN_DEBUG
|
||||
|
||||
|
@ -168,6 +173,7 @@ static void xrun(struct snd_pcm_substream *substream)
|
|||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
|
||||
trace_xrun(substream);
|
||||
if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE)
|
||||
snd_pcm_gettime(runtime, (struct timespec *)&runtime->status->tstamp);
|
||||
snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
|
||||
|
@ -180,97 +186,19 @@ static void xrun(struct snd_pcm_substream *substream)
|
|||
}
|
||||
|
||||
#ifdef CONFIG_SND_PCM_XRUN_DEBUG
|
||||
#define hw_ptr_error(substream, fmt, args...) \
|
||||
#define hw_ptr_error(substream, in_interrupt, reason, fmt, args...) \
|
||||
do { \
|
||||
trace_hw_ptr_error(substream, reason); \
|
||||
if (xrun_debug(substream, XRUN_DEBUG_BASIC)) { \
|
||||
xrun_log_show(substream); \
|
||||
pr_err_ratelimited("ALSA: PCM: " fmt, ##args); \
|
||||
pr_err_ratelimited("ALSA: PCM: [%c] " reason ": " fmt, \
|
||||
(in_interrupt) ? 'Q' : 'P', ##args); \
|
||||
dump_stack_on_xrun(substream); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define XRUN_LOG_CNT 10
|
||||
|
||||
struct hwptr_log_entry {
|
||||
unsigned int in_interrupt;
|
||||
unsigned long jiffies;
|
||||
snd_pcm_uframes_t pos;
|
||||
snd_pcm_uframes_t period_size;
|
||||
snd_pcm_uframes_t buffer_size;
|
||||
snd_pcm_uframes_t old_hw_ptr;
|
||||
snd_pcm_uframes_t hw_ptr_base;
|
||||
};
|
||||
|
||||
struct snd_pcm_hwptr_log {
|
||||
unsigned int idx;
|
||||
unsigned int hit: 1;
|
||||
struct hwptr_log_entry entries[XRUN_LOG_CNT];
|
||||
};
|
||||
|
||||
static void xrun_log(struct snd_pcm_substream *substream,
|
||||
snd_pcm_uframes_t pos, int in_interrupt)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct snd_pcm_hwptr_log *log = runtime->hwptr_log;
|
||||
struct hwptr_log_entry *entry;
|
||||
|
||||
if (log == NULL) {
|
||||
log = kzalloc(sizeof(*log), GFP_ATOMIC);
|
||||
if (log == NULL)
|
||||
return;
|
||||
runtime->hwptr_log = log;
|
||||
} else {
|
||||
if (xrun_debug(substream, XRUN_DEBUG_LOGONCE) && log->hit)
|
||||
return;
|
||||
}
|
||||
entry = &log->entries[log->idx];
|
||||
entry->in_interrupt = in_interrupt;
|
||||
entry->jiffies = jiffies;
|
||||
entry->pos = pos;
|
||||
entry->period_size = runtime->period_size;
|
||||
entry->buffer_size = runtime->buffer_size;
|
||||
entry->old_hw_ptr = runtime->status->hw_ptr;
|
||||
entry->hw_ptr_base = runtime->hw_ptr_base;
|
||||
log->idx = (log->idx + 1) % XRUN_LOG_CNT;
|
||||
}
|
||||
|
||||
static void xrun_log_show(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_hwptr_log *log = substream->runtime->hwptr_log;
|
||||
struct hwptr_log_entry *entry;
|
||||
char name[16];
|
||||
unsigned int idx;
|
||||
int cnt;
|
||||
|
||||
if (log == NULL)
|
||||
return;
|
||||
if (xrun_debug(substream, XRUN_DEBUG_LOGONCE) && log->hit)
|
||||
return;
|
||||
snd_pcm_debug_name(substream, name, sizeof(name));
|
||||
for (cnt = 0, idx = log->idx; cnt < XRUN_LOG_CNT; cnt++) {
|
||||
entry = &log->entries[idx];
|
||||
if (entry->period_size == 0)
|
||||
break;
|
||||
pr_info("hwptr log: %s: %sj=%lu, pos=%ld/%ld/%ld, "
|
||||
"hwptr=%ld/%ld\n",
|
||||
name, entry->in_interrupt ? "[Q] " : "",
|
||||
entry->jiffies,
|
||||
(unsigned long)entry->pos,
|
||||
(unsigned long)entry->period_size,
|
||||
(unsigned long)entry->buffer_size,
|
||||
(unsigned long)entry->old_hw_ptr,
|
||||
(unsigned long)entry->hw_ptr_base);
|
||||
idx++;
|
||||
idx %= XRUN_LOG_CNT;
|
||||
}
|
||||
log->hit = 1;
|
||||
}
|
||||
|
||||
#else /* ! CONFIG_SND_PCM_XRUN_DEBUG */
|
||||
|
||||
#define hw_ptr_error(substream, fmt, args...) do { } while (0)
|
||||
#define xrun_log(substream, pos, in_interrupt) do { } while (0)
|
||||
#define xrun_log_show(substream) do { } while (0)
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -343,17 +271,15 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream,
|
|||
if (printk_ratelimit()) {
|
||||
char name[16];
|
||||
snd_pcm_debug_name(substream, name, sizeof(name));
|
||||
xrun_log_show(substream);
|
||||
pcm_err(substream->pcm,
|
||||
"XRUN: %s, pos = %ld, buffer size = %ld, period size = %ld\n",
|
||||
"BUG: %s, pos = %ld, buffer size = %ld, period size = %ld\n",
|
||||
name, pos, runtime->buffer_size,
|
||||
runtime->period_size);
|
||||
}
|
||||
pos = 0;
|
||||
}
|
||||
pos -= pos % runtime->min_align;
|
||||
if (xrun_debug(substream, XRUN_DEBUG_LOG))
|
||||
xrun_log(substream, pos, in_interrupt);
|
||||
trace_hwptr(substream, pos, in_interrupt);
|
||||
hw_base = runtime->hw_ptr_base;
|
||||
new_hw_ptr = hw_base + pos;
|
||||
if (in_interrupt) {
|
||||
|
@ -388,22 +314,6 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream,
|
|||
delta = new_hw_ptr - old_hw_ptr;
|
||||
if (delta < 0)
|
||||
delta += runtime->boundary;
|
||||
if (xrun_debug(substream, in_interrupt ?
|
||||
XRUN_DEBUG_PERIODUPDATE : XRUN_DEBUG_HWPTRUPDATE)) {
|
||||
char name[16];
|
||||
snd_pcm_debug_name(substream, name, sizeof(name));
|
||||
pcm_dbg(substream->pcm,
|
||||
"%s_update: %s: pos=%u/%u/%u, hwptr=%ld/%ld/%ld/%ld\n",
|
||||
in_interrupt ? "period" : "hwptr",
|
||||
name,
|
||||
(unsigned int)pos,
|
||||
(unsigned int)runtime->period_size,
|
||||
(unsigned int)runtime->buffer_size,
|
||||
(unsigned long)delta,
|
||||
(unsigned long)old_hw_ptr,
|
||||
(unsigned long)new_hw_ptr,
|
||||
(unsigned long)runtime->hw_ptr_base);
|
||||
}
|
||||
|
||||
if (runtime->no_period_wakeup) {
|
||||
snd_pcm_sframes_t xrun_threshold;
|
||||
|
@ -431,13 +341,10 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream,
|
|||
|
||||
/* something must be really wrong */
|
||||
if (delta >= runtime->buffer_size + runtime->period_size) {
|
||||
hw_ptr_error(substream,
|
||||
"Unexpected hw_pointer value %s"
|
||||
"(stream=%i, pos=%ld, new_hw_ptr=%ld, "
|
||||
"old_hw_ptr=%ld)\n",
|
||||
in_interrupt ? "[Q] " : "[P]",
|
||||
substream->stream, (long)pos,
|
||||
(long)new_hw_ptr, (long)old_hw_ptr);
|
||||
hw_ptr_error(substream, in_interrupt, "Unexpected hw_ptr",
|
||||
"(stream=%i, pos=%ld, new_hw_ptr=%ld, old_hw_ptr=%ld)\n",
|
||||
substream->stream, (long)pos,
|
||||
(long)new_hw_ptr, (long)old_hw_ptr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -474,11 +381,8 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream,
|
|||
delta--;
|
||||
}
|
||||
/* align hw_base to buffer_size */
|
||||
hw_ptr_error(substream,
|
||||
"hw_ptr skipping! %s"
|
||||
"(pos=%ld, delta=%ld, period=%ld, "
|
||||
"jdelta=%lu/%lu/%lu, hw_ptr=%ld/%ld)\n",
|
||||
in_interrupt ? "[Q] " : "",
|
||||
hw_ptr_error(substream, in_interrupt, "hw_ptr skipping",
|
||||
"(pos=%ld, delta=%ld, period=%ld, jdelta=%lu/%lu/%lu, hw_ptr=%ld/%ld)\n",
|
||||
(long)pos, (long)hdelta,
|
||||
(long)runtime->period_size, jdelta,
|
||||
((hdelta * HZ) / runtime->rate), hw_base,
|
||||
|
@ -490,11 +394,9 @@ static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream,
|
|||
}
|
||||
no_jiffies_check:
|
||||
if (delta > runtime->period_size + runtime->period_size / 2) {
|
||||
hw_ptr_error(substream,
|
||||
"Lost interrupts? %s"
|
||||
"(stream=%i, delta=%ld, new_hw_ptr=%ld, "
|
||||
"old_hw_ptr=%ld)\n",
|
||||
in_interrupt ? "[Q] " : "",
|
||||
hw_ptr_error(substream, in_interrupt,
|
||||
"Lost interrupts?",
|
||||
"(stream=%i, delta=%ld, new_hw_ptr=%ld, old_hw_ptr=%ld)\n",
|
||||
substream->stream, (long)delta,
|
||||
(long)new_hw_ptr,
|
||||
(long)old_hw_ptr);
|
||||
|
|
|
@ -35,9 +35,6 @@
|
|||
#include <sound/timer.h>
|
||||
#include <sound/minors.h>
|
||||
#include <asm/io.h>
|
||||
#if defined(CONFIG_MIPS) && defined(CONFIG_DMA_NONCOHERENT)
|
||||
#include <dma-coherence.h>
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Compatibility
|
||||
|
@ -77,6 +74,14 @@ 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);
|
||||
|
||||
/**
|
||||
* snd_pcm_stream_lock - Lock the PCM stream
|
||||
* @substream: PCM substream
|
||||
*
|
||||
* This locks the PCM stream's spinlock or mutex depending on the nonatomic
|
||||
* flag of the given substream. This also takes the global link rw lock
|
||||
* (or rw sem), too, for avoiding the race with linked streams.
|
||||
*/
|
||||
void snd_pcm_stream_lock(struct snd_pcm_substream *substream)
|
||||
{
|
||||
if (substream->pcm->nonatomic) {
|
||||
|
@ -89,6 +94,12 @@ void snd_pcm_stream_lock(struct snd_pcm_substream *substream)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(snd_pcm_stream_lock);
|
||||
|
||||
/**
|
||||
* snd_pcm_stream_lock - Unlock the PCM stream
|
||||
* @substream: PCM substream
|
||||
*
|
||||
* This unlocks the PCM stream that has been locked via snd_pcm_stream_lock().
|
||||
*/
|
||||
void snd_pcm_stream_unlock(struct snd_pcm_substream *substream)
|
||||
{
|
||||
if (substream->pcm->nonatomic) {
|
||||
|
@ -101,6 +112,14 @@ void snd_pcm_stream_unlock(struct snd_pcm_substream *substream)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(snd_pcm_stream_unlock);
|
||||
|
||||
/**
|
||||
* snd_pcm_stream_lock_irq - Lock the PCM stream
|
||||
* @substream: PCM substream
|
||||
*
|
||||
* This locks the PCM stream like snd_pcm_stream_lock() and disables the local
|
||||
* IRQ (only when nonatomic is false). In nonatomic case, this is identical
|
||||
* as snd_pcm_stream_lock().
|
||||
*/
|
||||
void snd_pcm_stream_lock_irq(struct snd_pcm_substream *substream)
|
||||
{
|
||||
if (!substream->pcm->nonatomic)
|
||||
|
@ -109,6 +128,12 @@ void snd_pcm_stream_lock_irq(struct snd_pcm_substream *substream)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(snd_pcm_stream_lock_irq);
|
||||
|
||||
/**
|
||||
* snd_pcm_stream_unlock_irq - Unlock the PCM stream
|
||||
* @substream: PCM substream
|
||||
*
|
||||
* This is a counter-part of snd_pcm_stream_lock_irq().
|
||||
*/
|
||||
void snd_pcm_stream_unlock_irq(struct snd_pcm_substream *substream)
|
||||
{
|
||||
snd_pcm_stream_unlock(substream);
|
||||
|
@ -127,6 +152,13 @@ unsigned long _snd_pcm_stream_lock_irqsave(struct snd_pcm_substream *substream)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(_snd_pcm_stream_lock_irqsave);
|
||||
|
||||
/**
|
||||
* snd_pcm_stream_unlock_irqrestore - Unlock the PCM stream
|
||||
* @substream: PCM substream
|
||||
* @flags: irq flags
|
||||
*
|
||||
* This is a counter-part of snd_pcm_stream_lock_irqsave().
|
||||
*/
|
||||
void snd_pcm_stream_unlock_irqrestore(struct snd_pcm_substream *substream,
|
||||
unsigned long flags)
|
||||
{
|
||||
|
@ -195,6 +227,21 @@ int snd_pcm_info_user(struct snd_pcm_substream *substream,
|
|||
return err;
|
||||
}
|
||||
|
||||
static bool hw_support_mmap(struct snd_pcm_substream *substream)
|
||||
{
|
||||
if (!(substream->runtime->hw.info & SNDRV_PCM_INFO_MMAP))
|
||||
return false;
|
||||
/* check architectures that return -EINVAL from dma_mmap_coherent() */
|
||||
/* FIXME: this should be some global flag */
|
||||
#if defined(CONFIG_C6X) || defined(CONFIG_FRV) || defined(CONFIG_MN10300) ||\
|
||||
defined(CONFIG_PARISC) || defined(CONFIG_XTENSA)
|
||||
if (!substream->ops->mmap &&
|
||||
substream->dma_buffer.dev.type == SNDRV_DMA_TYPE_DEV)
|
||||
return false;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
#undef RULES_DEBUG
|
||||
|
||||
#ifdef RULES_DEBUG
|
||||
|
@ -372,8 +419,12 @@ int snd_pcm_hw_refine(struct snd_pcm_substream *substream,
|
|||
}
|
||||
|
||||
hw = &substream->runtime->hw;
|
||||
if (!params->info)
|
||||
if (!params->info) {
|
||||
params->info = hw->info & ~SNDRV_PCM_INFO_FIFO_IN_FRAMES;
|
||||
if (!hw_support_mmap(substream))
|
||||
params->info &= ~(SNDRV_PCM_INFO_MMAP |
|
||||
SNDRV_PCM_INFO_MMAP_VALID);
|
||||
}
|
||||
if (!params->fifo_size) {
|
||||
m = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
|
||||
i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
|
||||
|
@ -849,27 +900,6 @@ static int snd_pcm_action_single(struct action_ops *ops,
|
|||
return res;
|
||||
}
|
||||
|
||||
/* call in mutex-protected context */
|
||||
static int snd_pcm_action_mutex(struct action_ops *ops,
|
||||
struct snd_pcm_substream *substream,
|
||||
int state)
|
||||
{
|
||||
int res;
|
||||
|
||||
if (snd_pcm_stream_linked(substream)) {
|
||||
if (!mutex_trylock(&substream->group->mutex)) {
|
||||
mutex_unlock(&substream->self_group.mutex);
|
||||
mutex_lock(&substream->group->mutex);
|
||||
mutex_lock(&substream->self_group.mutex);
|
||||
}
|
||||
res = snd_pcm_action_group(ops, substream, state, 1);
|
||||
mutex_unlock(&substream->group->mutex);
|
||||
} else {
|
||||
res = snd_pcm_action_single(ops, substream, state);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
* Note: call with stream lock
|
||||
*/
|
||||
|
@ -879,10 +909,18 @@ static int snd_pcm_action(struct action_ops *ops,
|
|||
{
|
||||
int res;
|
||||
|
||||
if (substream->pcm->nonatomic)
|
||||
return snd_pcm_action_mutex(ops, substream, state);
|
||||
if (!snd_pcm_stream_linked(substream))
|
||||
return snd_pcm_action_single(ops, substream, state);
|
||||
|
||||
if (snd_pcm_stream_linked(substream)) {
|
||||
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);
|
||||
}
|
||||
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);
|
||||
|
@ -890,34 +928,10 @@ static int snd_pcm_action(struct action_ops *ops,
|
|||
}
|
||||
res = snd_pcm_action_group(ops, substream, state, 1);
|
||||
spin_unlock(&substream->group->lock);
|
||||
} else {
|
||||
res = snd_pcm_action_single(ops, substream, state);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static int snd_pcm_action_lock_mutex(struct action_ops *ops,
|
||||
struct snd_pcm_substream *substream,
|
||||
int state)
|
||||
{
|
||||
int res;
|
||||
|
||||
down_read(&snd_pcm_link_rwsem);
|
||||
if (snd_pcm_stream_linked(substream)) {
|
||||
mutex_lock(&substream->group->mutex);
|
||||
mutex_lock(&substream->self_group.mutex);
|
||||
res = snd_pcm_action_group(ops, substream, state, 1);
|
||||
mutex_unlock(&substream->self_group.mutex);
|
||||
mutex_unlock(&substream->group->mutex);
|
||||
} else {
|
||||
mutex_lock(&substream->self_group.mutex);
|
||||
res = snd_pcm_action_single(ops, substream, state);
|
||||
mutex_unlock(&substream->self_group.mutex);
|
||||
}
|
||||
up_read(&snd_pcm_link_rwsem);
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
* Note: don't use any locks before
|
||||
*/
|
||||
|
@ -927,22 +941,9 @@ static int snd_pcm_action_lock_irq(struct action_ops *ops,
|
|||
{
|
||||
int res;
|
||||
|
||||
if (substream->pcm->nonatomic)
|
||||
return snd_pcm_action_lock_mutex(ops, substream, state);
|
||||
|
||||
read_lock_irq(&snd_pcm_link_rwlock);
|
||||
if (snd_pcm_stream_linked(substream)) {
|
||||
spin_lock(&substream->group->lock);
|
||||
spin_lock(&substream->self_group.lock);
|
||||
res = snd_pcm_action_group(ops, substream, state, 1);
|
||||
spin_unlock(&substream->self_group.lock);
|
||||
spin_unlock(&substream->group->lock);
|
||||
} else {
|
||||
spin_lock(&substream->self_group.lock);
|
||||
res = snd_pcm_action_single(ops, substream, state);
|
||||
spin_unlock(&substream->self_group.lock);
|
||||
}
|
||||
read_unlock_irq(&snd_pcm_link_rwlock);
|
||||
snd_pcm_stream_lock_irq(substream);
|
||||
res = snd_pcm_action(ops, substream, state);
|
||||
snd_pcm_stream_unlock_irq(substream);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
@ -1051,10 +1052,10 @@ static void snd_pcm_post_stop(struct snd_pcm_substream *substream, int state)
|
|||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
if (runtime->status->state != state) {
|
||||
snd_pcm_trigger_tstamp(substream);
|
||||
runtime->status->state = state;
|
||||
if (substream->timer)
|
||||
snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MSTOP,
|
||||
&runtime->trigger_tstamp);
|
||||
runtime->status->state = state;
|
||||
}
|
||||
wake_up(&runtime->sleep);
|
||||
wake_up(&runtime->tsleep);
|
||||
|
@ -1097,6 +1098,28 @@ int snd_pcm_drain_done(struct snd_pcm_substream *substream)
|
|||
SNDRV_PCM_STATE_SETUP);
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_pcm_stop_xrun - stop the running streams as XRUN
|
||||
* @substream: the PCM substream instance
|
||||
*
|
||||
* This stops the given running substream (and all linked substreams) as XRUN.
|
||||
* Unlike snd_pcm_stop(), this function takes the substream lock by itself.
|
||||
*
|
||||
* Return: Zero if successful, or a negative error code.
|
||||
*/
|
||||
int snd_pcm_stop_xrun(struct snd_pcm_substream *substream)
|
||||
{
|
||||
unsigned long flags;
|
||||
int ret = 0;
|
||||
|
||||
snd_pcm_stream_lock_irqsave(substream, flags);
|
||||
if (snd_pcm_running(substream))
|
||||
ret = snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
|
||||
snd_pcm_stream_unlock_irqrestore(substream, flags);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_pcm_stop_xrun);
|
||||
|
||||
/*
|
||||
* pause callbacks
|
||||
*/
|
||||
|
@ -1203,11 +1226,11 @@ static void snd_pcm_post_suspend(struct snd_pcm_substream *substream, int state)
|
|||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
snd_pcm_trigger_tstamp(substream);
|
||||
runtime->status->suspended_state = runtime->status->state;
|
||||
runtime->status->state = SNDRV_PCM_STATE_SUSPENDED;
|
||||
if (substream->timer)
|
||||
snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MSUSPEND,
|
||||
&runtime->trigger_tstamp);
|
||||
runtime->status->suspended_state = runtime->status->state;
|
||||
runtime->status->state = SNDRV_PCM_STATE_SUSPENDED;
|
||||
wake_up(&runtime->sleep);
|
||||
wake_up(&runtime->tsleep);
|
||||
}
|
||||
|
@ -1310,10 +1333,10 @@ static void snd_pcm_post_resume(struct snd_pcm_substream *substream, int state)
|
|||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
snd_pcm_trigger_tstamp(substream);
|
||||
runtime->status->state = runtime->status->suspended_state;
|
||||
if (substream->timer)
|
||||
snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MRESUME,
|
||||
&runtime->trigger_tstamp);
|
||||
runtime->status->state = runtime->status->suspended_state;
|
||||
}
|
||||
|
||||
static struct action_ops snd_pcm_action_resume = {
|
||||
|
@ -2070,7 +2093,7 @@ int snd_pcm_hw_constraints_complete(struct snd_pcm_substream *substream)
|
|||
mask |= 1 << SNDRV_PCM_ACCESS_RW_INTERLEAVED;
|
||||
if (hw->info & SNDRV_PCM_INFO_NONINTERLEAVED)
|
||||
mask |= 1 << SNDRV_PCM_ACCESS_RW_NONINTERLEAVED;
|
||||
if (hw->info & SNDRV_PCM_INFO_MMAP) {
|
||||
if (hw_support_mmap(substream)) {
|
||||
if (hw->info & SNDRV_PCM_INFO_INTERLEAVED)
|
||||
mask |= 1 << SNDRV_PCM_ACCESS_MMAP_INTERLEAVED;
|
||||
if (hw->info & SNDRV_PCM_INFO_NONINTERLEAVED)
|
||||
|
@ -3249,20 +3272,6 @@ static inline struct page *
|
|||
snd_pcm_default_page_ops(struct snd_pcm_substream *substream, unsigned long ofs)
|
||||
{
|
||||
void *vaddr = substream->runtime->dma_area + ofs;
|
||||
#if defined(CONFIG_MIPS) && defined(CONFIG_DMA_NONCOHERENT)
|
||||
if (substream->dma_buffer.dev.type == SNDRV_DMA_TYPE_DEV)
|
||||
return virt_to_page(CAC_ADDR(vaddr));
|
||||
#endif
|
||||
#if defined(CONFIG_PPC32) && defined(CONFIG_NOT_COHERENT_CACHE)
|
||||
if (substream->dma_buffer.dev.type == SNDRV_DMA_TYPE_DEV) {
|
||||
dma_addr_t addr = substream->runtime->dma_addr + ofs;
|
||||
addr -= get_dma_offset(substream->dma_buffer.dev.dev);
|
||||
/* assume dma_handle set via pfn_to_phys() in
|
||||
* mm/dma-noncoherent.c
|
||||
*/
|
||||
return pfn_to_page(addr >> PAGE_SHIFT);
|
||||
}
|
||||
#endif
|
||||
return virt_to_page(vaddr);
|
||||
}
|
||||
|
||||
|
@ -3307,16 +3316,18 @@ static const struct vm_operations_struct snd_pcm_vm_ops_data_fault = {
|
|||
.fault = snd_pcm_mmap_data_fault,
|
||||
};
|
||||
|
||||
#ifndef ARCH_HAS_DMA_MMAP_COHERENT
|
||||
/* This should be defined / handled globally! */
|
||||
#if defined(CONFIG_ARM) || defined(CONFIG_ARM64)
|
||||
#define ARCH_HAS_DMA_MMAP_COHERENT
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/*
|
||||
* mmap the DMA buffer on RAM
|
||||
*/
|
||||
|
||||
/**
|
||||
* snd_pcm_lib_default_mmap - Default PCM data mmap function
|
||||
* @substream: PCM substream
|
||||
* @area: VMA
|
||||
*
|
||||
* This is the default mmap handler for PCM data. When mmap pcm_ops is NULL,
|
||||
* this function is invoked implicitly.
|
||||
*/
|
||||
int snd_pcm_lib_default_mmap(struct snd_pcm_substream *substream,
|
||||
struct vm_area_struct *area)
|
||||
{
|
||||
|
@ -3329,7 +3340,7 @@ int snd_pcm_lib_default_mmap(struct snd_pcm_substream *substream,
|
|||
area->vm_end - area->vm_start, area->vm_page_prot);
|
||||
}
|
||||
#endif /* CONFIG_GENERIC_ALLOCATOR */
|
||||
#ifdef ARCH_HAS_DMA_MMAP_COHERENT
|
||||
#ifndef CONFIG_X86 /* for avoiding warnings arch/x86/mm/pat.c */
|
||||
if (!substream->ops->page &&
|
||||
substream->dma_buffer.dev.type == SNDRV_DMA_TYPE_DEV)
|
||||
return dma_mmap_coherent(substream->dma_buffer.dev.dev,
|
||||
|
@ -3337,11 +3348,7 @@ int snd_pcm_lib_default_mmap(struct snd_pcm_substream *substream,
|
|||
substream->runtime->dma_area,
|
||||
substream->runtime->dma_addr,
|
||||
area->vm_end - area->vm_start);
|
||||
#elif defined(CONFIG_MIPS) && defined(CONFIG_DMA_NONCOHERENT)
|
||||
if (substream->dma_buffer.dev.type == SNDRV_DMA_TYPE_DEV &&
|
||||
!plat_device_is_coherent(substream->dma_buffer.dev.dev))
|
||||
area->vm_page_prot = pgprot_noncached(area->vm_page_prot);
|
||||
#endif /* ARCH_HAS_DMA_MMAP_COHERENT */
|
||||
#endif /* CONFIG_X86 */
|
||||
/* mmap with fault handler */
|
||||
area->vm_ops = &snd_pcm_vm_ops_data_fault;
|
||||
return 0;
|
||||
|
@ -3352,6 +3359,15 @@ EXPORT_SYMBOL_GPL(snd_pcm_lib_default_mmap);
|
|||
* mmap the DMA buffer on I/O memory area
|
||||
*/
|
||||
#if SNDRV_PCM_INFO_MMAP_IOMEM
|
||||
/**
|
||||
* snd_pcm_lib_mmap_iomem - Default PCM data mmap function for I/O mem
|
||||
* @substream: PCM substream
|
||||
* @area: VMA
|
||||
*
|
||||
* When your hardware uses the iomapped pages as the hardware buffer and
|
||||
* wants to mmap it, pass this function as mmap pcm_ops. Note that this
|
||||
* is supposed to work only on limited architectures.
|
||||
*/
|
||||
int snd_pcm_lib_mmap_iomem(struct snd_pcm_substream *substream,
|
||||
struct vm_area_struct *area)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
#undef TRACE_SYSTEM
|
||||
#define TRACE_SYSTEM snd_pcm
|
||||
#define TRACE_INCLUDE_FILE pcm_trace
|
||||
|
||||
#if !defined(_PCM_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
|
||||
#define _PCM_TRACE_H
|
||||
|
||||
#include <linux/tracepoint.h>
|
||||
|
||||
TRACE_EVENT(hwptr,
|
||||
TP_PROTO(struct snd_pcm_substream *substream, snd_pcm_uframes_t pos, bool irq),
|
||||
TP_ARGS(substream, pos, irq),
|
||||
TP_STRUCT__entry(
|
||||
__field( bool, in_interrupt )
|
||||
__field( unsigned int, card )
|
||||
__field( unsigned int, device )
|
||||
__field( unsigned int, number )
|
||||
__field( unsigned int, stream )
|
||||
__field( snd_pcm_uframes_t, pos )
|
||||
__field( snd_pcm_uframes_t, period_size )
|
||||
__field( snd_pcm_uframes_t, buffer_size )
|
||||
__field( snd_pcm_uframes_t, old_hw_ptr )
|
||||
__field( snd_pcm_uframes_t, hw_ptr_base )
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->in_interrupt = (irq);
|
||||
__entry->card = (substream)->pcm->card->number;
|
||||
__entry->device = (substream)->pcm->device;
|
||||
__entry->number = (substream)->number;
|
||||
__entry->stream = (substream)->stream;
|
||||
__entry->pos = (pos);
|
||||
__entry->period_size = (substream)->runtime->period_size;
|
||||
__entry->buffer_size = (substream)->runtime->buffer_size;
|
||||
__entry->old_hw_ptr = (substream)->runtime->status->hw_ptr;
|
||||
__entry->hw_ptr_base = (substream)->runtime->hw_ptr_base;
|
||||
),
|
||||
TP_printk("pcmC%dD%d%c/sub%d: %s: pos=%lu, old=%lu, base=%lu, period=%lu, buf=%lu",
|
||||
__entry->card, __entry->device,
|
||||
__entry->stream == SNDRV_PCM_STREAM_PLAYBACK ? 'p' : 'c',
|
||||
__entry->number,
|
||||
__entry->in_interrupt ? "IRQ" : "POS",
|
||||
(unsigned long)__entry->pos,
|
||||
(unsigned long)__entry->old_hw_ptr,
|
||||
(unsigned long)__entry->hw_ptr_base,
|
||||
(unsigned long)__entry->period_size,
|
||||
(unsigned long)__entry->buffer_size)
|
||||
);
|
||||
|
||||
TRACE_EVENT(xrun,
|
||||
TP_PROTO(struct snd_pcm_substream *substream),
|
||||
TP_ARGS(substream),
|
||||
TP_STRUCT__entry(
|
||||
__field( unsigned int, card )
|
||||
__field( unsigned int, device )
|
||||
__field( unsigned int, number )
|
||||
__field( unsigned int, stream )
|
||||
__field( snd_pcm_uframes_t, period_size )
|
||||
__field( snd_pcm_uframes_t, buffer_size )
|
||||
__field( snd_pcm_uframes_t, old_hw_ptr )
|
||||
__field( snd_pcm_uframes_t, hw_ptr_base )
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->card = (substream)->pcm->card->number;
|
||||
__entry->device = (substream)->pcm->device;
|
||||
__entry->number = (substream)->number;
|
||||
__entry->stream = (substream)->stream;
|
||||
__entry->period_size = (substream)->runtime->period_size;
|
||||
__entry->buffer_size = (substream)->runtime->buffer_size;
|
||||
__entry->old_hw_ptr = (substream)->runtime->status->hw_ptr;
|
||||
__entry->hw_ptr_base = (substream)->runtime->hw_ptr_base;
|
||||
),
|
||||
TP_printk("pcmC%dD%d%c/sub%d: XRUN: old=%lu, base=%lu, period=%lu, buf=%lu",
|
||||
__entry->card, __entry->device,
|
||||
__entry->stream == SNDRV_PCM_STREAM_PLAYBACK ? 'p' : 'c',
|
||||
__entry->number,
|
||||
(unsigned long)__entry->old_hw_ptr,
|
||||
(unsigned long)__entry->hw_ptr_base,
|
||||
(unsigned long)__entry->period_size,
|
||||
(unsigned long)__entry->buffer_size)
|
||||
);
|
||||
|
||||
TRACE_EVENT(hw_ptr_error,
|
||||
TP_PROTO(struct snd_pcm_substream *substream, const char *why),
|
||||
TP_ARGS(substream, why),
|
||||
TP_STRUCT__entry(
|
||||
__field( unsigned int, card )
|
||||
__field( unsigned int, device )
|
||||
__field( unsigned int, number )
|
||||
__field( unsigned int, stream )
|
||||
__field( const char *, reason )
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->card = (substream)->pcm->card->number;
|
||||
__entry->device = (substream)->pcm->device;
|
||||
__entry->number = (substream)->number;
|
||||
__entry->stream = (substream)->stream;
|
||||
__entry->reason = (why);
|
||||
),
|
||||
TP_printk("pcmC%dD%d%c/sub%d: ERROR: %s",
|
||||
__entry->card, __entry->device,
|
||||
__entry->stream == SNDRV_PCM_STREAM_PLAYBACK ? 'p' : 'c',
|
||||
__entry->number, __entry->reason)
|
||||
);
|
||||
|
||||
#endif /* _PCM_TRACE_H */
|
||||
|
||||
/* This part must be outside protection */
|
||||
#undef TRACE_INCLUDE_PATH
|
||||
#define TRACE_INCLUDE_PATH .
|
||||
#include <trace/define_trace.h>
|
|
@ -403,14 +403,11 @@ free_devinfo(void *private)
|
|||
{
|
||||
struct seq_oss_devinfo *dp = (struct seq_oss_devinfo *)private;
|
||||
|
||||
if (dp->timer)
|
||||
snd_seq_oss_timer_delete(dp->timer);
|
||||
snd_seq_oss_timer_delete(dp->timer);
|
||||
|
||||
if (dp->writeq)
|
||||
snd_seq_oss_writeq_delete(dp->writeq);
|
||||
snd_seq_oss_writeq_delete(dp->writeq);
|
||||
|
||||
if (dp->readq)
|
||||
snd_seq_oss_readq_delete(dp->readq);
|
||||
snd_seq_oss_readq_delete(dp->readq);
|
||||
|
||||
kfree(dp);
|
||||
}
|
||||
|
|
|
@ -86,7 +86,6 @@ static int __init alsa_seq_init(void)
|
|||
{
|
||||
int err;
|
||||
|
||||
snd_seq_autoload_lock();
|
||||
if ((err = client_init_data()) < 0)
|
||||
goto error;
|
||||
|
||||
|
@ -110,8 +109,8 @@ static int __init alsa_seq_init(void)
|
|||
if ((err = snd_seq_system_client_init()) < 0)
|
||||
goto error;
|
||||
|
||||
snd_seq_autoload_init();
|
||||
error:
|
||||
snd_seq_autoload_unlock();
|
||||
return err;
|
||||
}
|
||||
|
||||
|
@ -131,6 +130,8 @@ static void __exit alsa_seq_exit(void)
|
|||
|
||||
/* release event memory */
|
||||
snd_sequencer_memory_done();
|
||||
|
||||
snd_seq_autoload_exit();
|
||||
}
|
||||
|
||||
module_init(alsa_seq_init)
|
||||
|
|
|
@ -56,6 +56,7 @@ MODULE_LICENSE("GPL");
|
|||
#define DRIVER_LOADED (1<<0)
|
||||
#define DRIVER_REQUESTED (1<<1)
|
||||
#define DRIVER_LOCKED (1<<2)
|
||||
#define DRIVER_REQUESTING (1<<3)
|
||||
|
||||
struct ops_list {
|
||||
char id[ID_LEN]; /* driver id */
|
||||
|
@ -127,42 +128,82 @@ static void snd_seq_device_info(struct snd_info_entry *entry,
|
|||
|
||||
#ifdef CONFIG_MODULES
|
||||
/* avoid auto-loading during module_init() */
|
||||
static int snd_seq_in_init;
|
||||
static atomic_t snd_seq_in_init = ATOMIC_INIT(1); /* blocked as default */
|
||||
void snd_seq_autoload_lock(void)
|
||||
{
|
||||
snd_seq_in_init++;
|
||||
atomic_inc(&snd_seq_in_init);
|
||||
}
|
||||
|
||||
void snd_seq_autoload_unlock(void)
|
||||
{
|
||||
snd_seq_in_init--;
|
||||
atomic_dec(&snd_seq_in_init);
|
||||
}
|
||||
|
||||
static void autoload_drivers(void)
|
||||
{
|
||||
/* avoid reentrance */
|
||||
if (atomic_inc_return(&snd_seq_in_init) == 1) {
|
||||
struct ops_list *ops;
|
||||
|
||||
mutex_lock(&ops_mutex);
|
||||
list_for_each_entry(ops, &opslist, list) {
|
||||
if ((ops->driver & DRIVER_REQUESTING) &&
|
||||
!(ops->driver & DRIVER_REQUESTED)) {
|
||||
ops->used++;
|
||||
mutex_unlock(&ops_mutex);
|
||||
ops->driver |= DRIVER_REQUESTED;
|
||||
request_module("snd-%s", ops->id);
|
||||
mutex_lock(&ops_mutex);
|
||||
ops->used--;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&ops_mutex);
|
||||
}
|
||||
atomic_dec(&snd_seq_in_init);
|
||||
}
|
||||
|
||||
static void call_autoload(struct work_struct *work)
|
||||
{
|
||||
autoload_drivers();
|
||||
}
|
||||
|
||||
static DECLARE_WORK(autoload_work, call_autoload);
|
||||
|
||||
static void try_autoload(struct ops_list *ops)
|
||||
{
|
||||
if (!ops->driver) {
|
||||
ops->driver |= DRIVER_REQUESTING;
|
||||
schedule_work(&autoload_work);
|
||||
}
|
||||
}
|
||||
|
||||
static void queue_autoload_drivers(void)
|
||||
{
|
||||
struct ops_list *ops;
|
||||
|
||||
mutex_lock(&ops_mutex);
|
||||
list_for_each_entry(ops, &opslist, list)
|
||||
try_autoload(ops);
|
||||
mutex_unlock(&ops_mutex);
|
||||
}
|
||||
|
||||
void snd_seq_autoload_init(void)
|
||||
{
|
||||
atomic_dec(&snd_seq_in_init);
|
||||
#ifdef CONFIG_SND_SEQUENCER_MODULE
|
||||
/* initial autoload only when snd-seq is a module */
|
||||
queue_autoload_drivers();
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
#define try_autoload(ops) /* NOP */
|
||||
#endif
|
||||
|
||||
void snd_seq_device_load_drivers(void)
|
||||
{
|
||||
#ifdef CONFIG_MODULES
|
||||
struct ops_list *ops;
|
||||
|
||||
/* Calling request_module during module_init()
|
||||
* may cause blocking.
|
||||
*/
|
||||
if (snd_seq_in_init)
|
||||
return;
|
||||
|
||||
mutex_lock(&ops_mutex);
|
||||
list_for_each_entry(ops, &opslist, list) {
|
||||
if (! (ops->driver & DRIVER_LOADED) &&
|
||||
! (ops->driver & DRIVER_REQUESTED)) {
|
||||
ops->used++;
|
||||
mutex_unlock(&ops_mutex);
|
||||
ops->driver |= DRIVER_REQUESTED;
|
||||
request_module("snd-%s", ops->id);
|
||||
mutex_lock(&ops_mutex);
|
||||
ops->used--;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&ops_mutex);
|
||||
queue_autoload_drivers();
|
||||
flush_work(&autoload_work);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -214,13 +255,14 @@ int snd_seq_device_new(struct snd_card *card, int device, char *id, int argsize,
|
|||
ops->num_devices++;
|
||||
mutex_unlock(&ops->reg_mutex);
|
||||
|
||||
unlock_driver(ops);
|
||||
|
||||
if ((err = snd_device_new(card, SNDRV_DEV_SEQUENCER, dev, &dops)) < 0) {
|
||||
snd_seq_device_free(dev);
|
||||
return err;
|
||||
}
|
||||
|
||||
try_autoload(ops);
|
||||
unlock_driver(ops);
|
||||
|
||||
if (result)
|
||||
*result = dev;
|
||||
|
||||
|
@ -318,16 +360,12 @@ int snd_seq_device_register_driver(char *id, struct snd_seq_dev_ops *entry,
|
|||
entry->init_device == NULL || entry->free_device == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
snd_seq_autoload_lock();
|
||||
ops = find_driver(id, 1);
|
||||
if (ops == NULL) {
|
||||
snd_seq_autoload_unlock();
|
||||
if (ops == NULL)
|
||||
return -ENOMEM;
|
||||
}
|
||||
if (ops->driver & DRIVER_LOADED) {
|
||||
pr_warn("ALSA: seq: driver_register: driver '%s' already exists\n", id);
|
||||
unlock_driver(ops);
|
||||
snd_seq_autoload_unlock();
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
|
@ -344,7 +382,6 @@ int snd_seq_device_register_driver(char *id, struct snd_seq_dev_ops *entry,
|
|||
mutex_unlock(&ops->reg_mutex);
|
||||
|
||||
unlock_driver(ops);
|
||||
snd_seq_autoload_unlock();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -554,6 +591,9 @@ static int __init alsa_seq_device_init(void)
|
|||
|
||||
static void __exit alsa_seq_device_exit(void)
|
||||
{
|
||||
#ifdef CONFIG_MODULES
|
||||
cancel_work_sync(&autoload_work);
|
||||
#endif
|
||||
remove_drivers();
|
||||
#ifdef CONFIG_PROC_FS
|
||||
snd_info_free_entry(info_entry);
|
||||
|
@ -570,6 +610,7 @@ EXPORT_SYMBOL(snd_seq_device_new);
|
|||
EXPORT_SYMBOL(snd_seq_device_register_driver);
|
||||
EXPORT_SYMBOL(snd_seq_device_unregister_driver);
|
||||
#ifdef CONFIG_MODULES
|
||||
EXPORT_SYMBOL(snd_seq_autoload_init);
|
||||
EXPORT_SYMBOL(snd_seq_autoload_lock);
|
||||
EXPORT_SYMBOL(snd_seq_autoload_unlock);
|
||||
#endif
|
||||
|
|
|
@ -39,8 +39,7 @@ int snd_free_sgbuf_pages(struct snd_dma_buffer *dmab)
|
|||
if (! sgbuf)
|
||||
return -EINVAL;
|
||||
|
||||
if (dmab->area)
|
||||
vunmap(dmab->area);
|
||||
vunmap(dmab->area);
|
||||
dmab->area = NULL;
|
||||
|
||||
tmpb.dev.type = SNDRV_DMA_TYPE_DEV;
|
||||
|
|
|
@ -355,8 +355,13 @@ int snd_unregister_device(int type, struct snd_card *card, int dev)
|
|||
|
||||
EXPORT_SYMBOL(snd_unregister_device);
|
||||
|
||||
/* get the assigned device to the given type and device number;
|
||||
* the caller needs to release it via put_device() after using it
|
||||
/**
|
||||
* snd_get_device - get the assigned device to the given type and device number
|
||||
* @type: the device type, SNDRV_DEVICE_TYPE_XXX
|
||||
* @card:the card instance
|
||||
* @dev: the device index
|
||||
*
|
||||
* The caller needs to release it via put_device() after using it.
|
||||
*/
|
||||
struct device *snd_get_device(int type, struct snd_card *card, int dev)
|
||||
{
|
||||
|
|
|
@ -604,21 +604,11 @@ static struct snd_kcontrol_new mts64_ctl_smpte_time_frames = {
|
|||
static int snd_mts64_ctl_smpte_fps_info(struct snd_kcontrol *kctl,
|
||||
struct snd_ctl_elem_info *uinfo)
|
||||
{
|
||||
static char *texts[5] = { "24",
|
||||
"25",
|
||||
"29.97",
|
||||
"30D",
|
||||
"30" };
|
||||
static const char * const texts[5] = {
|
||||
"24", "25", "29.97", "30D", "30"
|
||||
};
|
||||
|
||||
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
|
||||
uinfo->count = 1;
|
||||
uinfo->value.enumerated.items = 5;
|
||||
if (uinfo->value.enumerated.item > 4)
|
||||
uinfo->value.enumerated.item = 4;
|
||||
strcpy(uinfo->value.enumerated.name,
|
||||
texts[uinfo->value.enumerated.item]);
|
||||
|
||||
return 0;
|
||||
return snd_ctl_enum_info(uinfo, 1, 5, texts);
|
||||
}
|
||||
|
||||
static int snd_mts64_ctl_smpte_fps_get(struct snd_kcontrol *kctl,
|
||||
|
|
|
@ -99,30 +99,33 @@ static int snd_virmidi_probe(struct platform_device *devptr)
|
|||
|
||||
if (midi_devs[dev] > MAX_MIDI_DEVICES) {
|
||||
snd_printk(KERN_WARNING
|
||||
"too much midi devices for virmidi %d: "
|
||||
"force to use %d\n", dev, MAX_MIDI_DEVICES);
|
||||
"too much midi devices for virmidi %d: force to use %d\n",
|
||||
dev, MAX_MIDI_DEVICES);
|
||||
midi_devs[dev] = MAX_MIDI_DEVICES;
|
||||
}
|
||||
for (idx = 0; idx < midi_devs[dev]; idx++) {
|
||||
struct snd_rawmidi *rmidi;
|
||||
struct snd_virmidi_dev *rdev;
|
||||
if ((err = snd_virmidi_new(card, idx, &rmidi)) < 0)
|
||||
|
||||
err = snd_virmidi_new(card, idx, &rmidi);
|
||||
if (err < 0)
|
||||
goto __nodev;
|
||||
rdev = rmidi->private_data;
|
||||
vmidi->midi[idx] = rmidi;
|
||||
strcpy(rmidi->name, "Virtual Raw MIDI");
|
||||
rdev->seq_mode = SNDRV_VIRMIDI_SEQ_DISPATCH;
|
||||
}
|
||||
|
||||
|
||||
strcpy(card->driver, "VirMIDI");
|
||||
strcpy(card->shortname, "VirMIDI");
|
||||
sprintf(card->longname, "Virtual MIDI Card %i", dev + 1);
|
||||
|
||||
if ((err = snd_card_register(card)) == 0) {
|
||||
err = snd_card_register(card);
|
||||
if (!err) {
|
||||
platform_set_drvdata(devptr, card);
|
||||
return 0;
|
||||
}
|
||||
__nodev:
|
||||
__nodev:
|
||||
snd_card_free(card);
|
||||
return err;
|
||||
}
|
||||
|
@ -157,13 +160,15 @@ static int __init alsa_card_virmidi_init(void)
|
|||
{
|
||||
int i, cards, err;
|
||||
|
||||
if ((err = platform_driver_register(&snd_virmidi_driver)) < 0)
|
||||
err = platform_driver_register(&snd_virmidi_driver);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
cards = 0;
|
||||
for (i = 0; i < SNDRV_CARDS; i++) {
|
||||
struct platform_device *device;
|
||||
if (! enable[i])
|
||||
|
||||
if (!enable[i])
|
||||
continue;
|
||||
device = platform_device_register_simple(SND_VIRMIDI_DRIVER,
|
||||
i, NULL, 0);
|
||||
|
|
|
@ -416,6 +416,7 @@ int vx_send_rih(struct vx_core *chip, int cmd)
|
|||
|
||||
/**
|
||||
* snd_vx_boot_xilinx - boot up the xilinx interface
|
||||
* @chip: VX core instance
|
||||
* @boot: the boot record to load
|
||||
*/
|
||||
int snd_vx_load_boot_image(struct vx_core *chip, const struct firmware *boot)
|
||||
|
@ -538,6 +539,8 @@ EXPORT_SYMBOL(snd_vx_threaded_irq_handler);
|
|||
|
||||
/**
|
||||
* snd_vx_irq_handler - interrupt handler
|
||||
* @irq: irq number
|
||||
* @dev: VX core instance
|
||||
*/
|
||||
irqreturn_t snd_vx_irq_handler(int irq, void *dev)
|
||||
{
|
||||
|
@ -649,6 +652,8 @@ static void vx_proc_init(struct vx_core *chip)
|
|||
|
||||
/**
|
||||
* snd_vx_dsp_boot - load the DSP boot
|
||||
* @chip: VX core instance
|
||||
* @boot: firmware data
|
||||
*/
|
||||
int snd_vx_dsp_boot(struct vx_core *chip, const struct firmware *boot)
|
||||
{
|
||||
|
@ -669,6 +674,8 @@ EXPORT_SYMBOL(snd_vx_dsp_boot);
|
|||
|
||||
/**
|
||||
* snd_vx_dsp_load - load the DSP image
|
||||
* @chip: VX core instance
|
||||
* @dsp: firmware data
|
||||
*/
|
||||
int snd_vx_dsp_load(struct vx_core *chip, const struct firmware *dsp)
|
||||
{
|
||||
|
@ -768,7 +775,10 @@ EXPORT_SYMBOL(snd_vx_resume);
|
|||
|
||||
/**
|
||||
* snd_vx_create - constructor for struct vx_core
|
||||
* @card: card instance
|
||||
* @hw: hardware specific record
|
||||
* @ops: VX ops pointer
|
||||
* @extra_size: extra byte size to allocate appending to chip
|
||||
*
|
||||
* this function allocates the instance and prepare for the hardware
|
||||
* initialization.
|
||||
|
|
|
@ -471,30 +471,18 @@ static struct snd_kcontrol_new vx_control_output_level = {
|
|||
*/
|
||||
static int vx_audio_src_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
|
||||
{
|
||||
static char *texts_mic[3] = {
|
||||
static const char * const texts_mic[3] = {
|
||||
"Digital", "Line", "Mic"
|
||||
};
|
||||
static char *texts_vx2[2] = {
|
||||
static const char * const texts_vx2[2] = {
|
||||
"Digital", "Analog"
|
||||
};
|
||||
struct vx_core *chip = snd_kcontrol_chip(kcontrol);
|
||||
|
||||
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
|
||||
uinfo->count = 1;
|
||||
if (chip->type >= VX_TYPE_VXPOCKET) {
|
||||
uinfo->value.enumerated.items = 3;
|
||||
if (uinfo->value.enumerated.item > 2)
|
||||
uinfo->value.enumerated.item = 2;
|
||||
strcpy(uinfo->value.enumerated.name,
|
||||
texts_mic[uinfo->value.enumerated.item]);
|
||||
} else {
|
||||
uinfo->value.enumerated.items = 2;
|
||||
if (uinfo->value.enumerated.item > 1)
|
||||
uinfo->value.enumerated.item = 1;
|
||||
strcpy(uinfo->value.enumerated.name,
|
||||
texts_vx2[uinfo->value.enumerated.item]);
|
||||
}
|
||||
return 0;
|
||||
if (chip->type >= VX_TYPE_VXPOCKET)
|
||||
return snd_ctl_enum_info(uinfo, 1, 3, texts_mic);
|
||||
else
|
||||
return snd_ctl_enum_info(uinfo, 1, 2, texts_vx2);
|
||||
}
|
||||
|
||||
static int vx_audio_src_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
||||
|
@ -539,18 +527,11 @@ static struct snd_kcontrol_new vx_control_audio_src = {
|
|||
*/
|
||||
static int vx_clock_mode_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
|
||||
{
|
||||
static char *texts[3] = {
|
||||
static const char * const texts[3] = {
|
||||
"Auto", "Internal", "External"
|
||||
};
|
||||
|
||||
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
|
||||
uinfo->count = 1;
|
||||
uinfo->value.enumerated.items = 3;
|
||||
if (uinfo->value.enumerated.item > 2)
|
||||
uinfo->value.enumerated.item = 2;
|
||||
strcpy(uinfo->value.enumerated.name,
|
||||
texts[uinfo->value.enumerated.item]);
|
||||
return 0;
|
||||
return snd_ctl_enum_info(uinfo, 1, 3, texts);
|
||||
}
|
||||
|
||||
static int vx_clock_mode_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
||||
|
|
|
@ -13,28 +13,34 @@ config SND_FIREWIRE_LIB
|
|||
select SND_RAWMIDI
|
||||
|
||||
config SND_DICE
|
||||
tristate "DICE-based DACs (EXPERIMENTAL)"
|
||||
tristate "DICE-based DACs support"
|
||||
select SND_HWDEP
|
||||
select SND_FIREWIRE_LIB
|
||||
help
|
||||
Say Y here to include support for many DACs based on the DICE
|
||||
chip family (DICE-II/Jr/Mini) from TC Applied Technologies.
|
||||
|
||||
At the moment, this driver supports playback only. If you
|
||||
want to use devices that support capturing, use FFADO instead.
|
||||
chip family (DICE-II/Jr/Mini) which TC Applied Technologies produces.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called snd-dice.
|
||||
|
||||
config SND_FIREWIRE_SPEAKERS
|
||||
tristate "FireWire speakers"
|
||||
config SND_OXFW
|
||||
tristate "Oxford Semiconductor FW970/971 chipset support"
|
||||
select SND_FIREWIRE_LIB
|
||||
select SND_HWDEP
|
||||
help
|
||||
Say Y here to include support for the Griffin FireWave Surround
|
||||
and the LaCie FireWire Speakers.
|
||||
Say Y here to include support for FireWire devices based on
|
||||
Oxford Semiconductor FW970/971 chipset.
|
||||
* Griffin Firewave
|
||||
* LaCie Firewire Speakers
|
||||
* Behringer F-Control Audio 202
|
||||
* Mackie(Loud) Onyx-i series (former models)
|
||||
* Mackie(Loud) Onyx Satellite
|
||||
* Mackie(Loud) Tapco Link.Firewire
|
||||
* Mackie(Loud) d.2 pro/d.4 pro
|
||||
* Mackie(Loud) U.420/U.420d
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called snd-firewire-speakers.
|
||||
will be called snd-oxfw.
|
||||
|
||||
config SND_ISIGHT
|
||||
tristate "Apple iSight microphone"
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
snd-firewire-lib-objs := lib.o iso-resources.o packets-buffer.o \
|
||||
fcp.o cmp.o amdtp.o
|
||||
snd-dice-objs := dice.o
|
||||
snd-firewire-speakers-objs := speakers.o
|
||||
snd-oxfw-objs := oxfw.o
|
||||
snd-isight-objs := isight.o
|
||||
snd-scs1x-objs := scs1x.o
|
||||
|
||||
obj-$(CONFIG_SND_FIREWIRE_LIB) += snd-firewire-lib.o
|
||||
obj-$(CONFIG_SND_DICE) += snd-dice.o
|
||||
obj-$(CONFIG_SND_FIREWIRE_SPEAKERS) += snd-firewire-speakers.o
|
||||
obj-$(CONFIG_SND_DICE) += dice/
|
||||
obj-$(CONFIG_SND_OXFW) += oxfw/
|
||||
obj-$(CONFIG_SND_ISIGHT) += snd-isight.o
|
||||
obj-$(CONFIG_SND_SCS1X) += snd-scs1x.o
|
||||
obj-$(CONFIG_SND_FIREWORKS) += fireworks/
|
||||
|
|
|
@ -1006,11 +1006,7 @@ void amdtp_stream_pcm_abort(struct amdtp_stream *s)
|
|||
struct snd_pcm_substream *pcm;
|
||||
|
||||
pcm = ACCESS_ONCE(s->pcm);
|
||||
if (pcm) {
|
||||
snd_pcm_stream_lock_irq(pcm);
|
||||
if (snd_pcm_running(pcm))
|
||||
snd_pcm_stop(pcm, SNDRV_PCM_STATE_XRUN);
|
||||
snd_pcm_stream_unlock_irq(pcm);
|
||||
}
|
||||
if (pcm)
|
||||
snd_pcm_stop_xrun(pcm);
|
||||
}
|
||||
EXPORT_SYMBOL(amdtp_stream_pcm_abort);
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
* corresponds to the end of event in the packet. Out of IEC 61883.
|
||||
* @CIP_WRONG_DBS: Only for in-stream. The value of dbs is wrong in in-packets.
|
||||
* The value of data_block_quadlets is used instead of reported value.
|
||||
* @SKIP_DBC_ZERO_CHECK: Only for in-stream. Packets with zero in dbc is
|
||||
* @CIP_SKIP_DBC_ZERO_CHECK: Only for in-stream. Packets with zero in dbc is
|
||||
* skipped for detecting discontinuity.
|
||||
* @CIP_SKIP_INIT_DBC_CHECK: Only for in-stream. The value of dbc in first
|
||||
* packet is not continuous from an initial value.
|
||||
|
@ -43,7 +43,27 @@ enum cip_flags {
|
|||
};
|
||||
|
||||
/**
|
||||
* enum cip_sfc - a stream's sample rate
|
||||
* enum cip_sfc - supported Sampling Frequency Codes (SFCs)
|
||||
* @CIP_SFC_32000: 32,000 data blocks
|
||||
* @CIP_SFC_44100: 44,100 data blocks
|
||||
* @CIP_SFC_48000: 48,000 data blocks
|
||||
* @CIP_SFC_88200: 88,200 data blocks
|
||||
* @CIP_SFC_96000: 96,000 data blocks
|
||||
* @CIP_SFC_176400: 176,400 data blocks
|
||||
* @CIP_SFC_192000: 192,000 data blocks
|
||||
* @CIP_SFC_COUNT: the number of supported SFCs
|
||||
*
|
||||
* These values are used to show nominal Sampling Frequency Code in
|
||||
* Format Dependent Field (FDF) of AMDTP packet header. In IEC 61883-6:2002,
|
||||
* this code means the number of events per second. Actually the code
|
||||
* represents the number of data blocks transferred per second in an AMDTP
|
||||
* stream.
|
||||
*
|
||||
* In IEC 61883-6:2005, some extensions were added to support more types of
|
||||
* data such as 'One Bit LInear Audio', therefore the meaning of SFC became
|
||||
* different depending on the types.
|
||||
*
|
||||
* Currently our implementation is compatible with IEC 61883-6:2002.
|
||||
*/
|
||||
enum cip_sfc {
|
||||
CIP_SFC_32000 = 0,
|
||||
|
|
|
@ -52,7 +52,7 @@ extern const unsigned int snd_bebob_rate_table[SND_BEBOB_STRM_FMT_ENTRIES];
|
|||
#define SND_BEBOB_CLOCK_INTERNAL "Internal"
|
||||
struct snd_bebob_clock_spec {
|
||||
unsigned int num;
|
||||
char *const *labels;
|
||||
const char *const *labels;
|
||||
int (*get)(struct snd_bebob *bebob, unsigned int *id);
|
||||
};
|
||||
struct snd_bebob_rate_spec {
|
||||
|
@ -61,7 +61,7 @@ struct snd_bebob_rate_spec {
|
|||
};
|
||||
struct snd_bebob_meter_spec {
|
||||
unsigned int num;
|
||||
char *const *labels;
|
||||
const char *const *labels;
|
||||
int (*get)(struct snd_bebob *bebob, u32 *target, unsigned int size);
|
||||
};
|
||||
struct snd_bebob_spec {
|
||||
|
|
|
@ -103,10 +103,10 @@ saffire_write_quad(struct snd_bebob *bebob, u64 offset, u32 value)
|
|||
&data, sizeof(__be32), 0);
|
||||
}
|
||||
|
||||
static char *const saffirepro_10_clk_src_labels[] = {
|
||||
static const char *const saffirepro_10_clk_src_labels[] = {
|
||||
SND_BEBOB_CLOCK_INTERNAL, "S/PDIF", "Word Clock"
|
||||
};
|
||||
static char *const saffirepro_26_clk_src_labels[] = {
|
||||
static const char *const saffirepro_26_clk_src_labels[] = {
|
||||
SND_BEBOB_CLOCK_INTERNAL, "S/PDIF", "ADAT1", "ADAT2", "Word Clock"
|
||||
};
|
||||
/* Value maps between registers and labels for SaffirePro 10/26. */
|
||||
|
@ -195,7 +195,7 @@ end:
|
|||
}
|
||||
|
||||
struct snd_bebob_spec saffire_le_spec;
|
||||
static char *const saffire_both_clk_src_labels[] = {
|
||||
static const char *const saffire_both_clk_src_labels[] = {
|
||||
SND_BEBOB_CLOCK_INTERNAL, "S/PDIF"
|
||||
};
|
||||
static int
|
||||
|
@ -210,12 +210,12 @@ saffire_both_clk_src_get(struct snd_bebob *bebob, unsigned int *id)
|
|||
|
||||
return err;
|
||||
};
|
||||
static char *const saffire_le_meter_labels[] = {
|
||||
static const char *const saffire_le_meter_labels[] = {
|
||||
ANA_IN, ANA_IN, DIG_IN,
|
||||
ANA_OUT, ANA_OUT, ANA_OUT, ANA_OUT,
|
||||
STM_IN, STM_IN
|
||||
};
|
||||
static char *const saffire_meter_labels[] = {
|
||||
static const char *const saffire_meter_labels[] = {
|
||||
ANA_IN, ANA_IN,
|
||||
STM_IN, STM_IN, STM_IN, STM_IN, STM_IN,
|
||||
};
|
||||
|
|
|
@ -340,7 +340,7 @@ end:
|
|||
}
|
||||
|
||||
/* Clock source control for special firmware */
|
||||
static char *const special_clk_labels[] = {
|
||||
static const char *const special_clk_labels[] = {
|
||||
SND_BEBOB_CLOCK_INTERNAL " with Digital Mute", "Digital",
|
||||
"Word Clock", SND_BEBOB_CLOCK_INTERNAL};
|
||||
static int special_clk_get(struct snd_bebob *bebob, unsigned int *id)
|
||||
|
@ -352,17 +352,8 @@ static int special_clk_get(struct snd_bebob *bebob, unsigned int *id)
|
|||
static int special_clk_ctl_info(struct snd_kcontrol *kctl,
|
||||
struct snd_ctl_elem_info *einf)
|
||||
{
|
||||
einf->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
|
||||
einf->count = 1;
|
||||
einf->value.enumerated.items = ARRAY_SIZE(special_clk_labels);
|
||||
|
||||
if (einf->value.enumerated.item >= einf->value.enumerated.items)
|
||||
einf->value.enumerated.item = einf->value.enumerated.items - 1;
|
||||
|
||||
strcpy(einf->value.enumerated.name,
|
||||
special_clk_labels[einf->value.enumerated.item]);
|
||||
|
||||
return 0;
|
||||
return snd_ctl_enum_info(einf, 1, ARRAY_SIZE(special_clk_labels),
|
||||
special_clk_labels);
|
||||
}
|
||||
static int special_clk_ctl_get(struct snd_kcontrol *kctl,
|
||||
struct snd_ctl_elem_value *uval)
|
||||
|
@ -438,23 +429,15 @@ static struct snd_kcontrol_new special_sync_ctl = {
|
|||
};
|
||||
|
||||
/* Digital input interface control for special firmware */
|
||||
static char *const special_dig_in_iface_labels[] = {
|
||||
static const char *const special_dig_in_iface_labels[] = {
|
||||
"S/PDIF Optical", "S/PDIF Coaxial", "ADAT Optical"
|
||||
};
|
||||
static int special_dig_in_iface_ctl_info(struct snd_kcontrol *kctl,
|
||||
struct snd_ctl_elem_info *einf)
|
||||
{
|
||||
einf->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
|
||||
einf->count = 1;
|
||||
einf->value.enumerated.items = ARRAY_SIZE(special_dig_in_iface_labels);
|
||||
|
||||
if (einf->value.enumerated.item >= einf->value.enumerated.items)
|
||||
einf->value.enumerated.item = einf->value.enumerated.items - 1;
|
||||
|
||||
strcpy(einf->value.enumerated.name,
|
||||
special_dig_in_iface_labels[einf->value.enumerated.item]);
|
||||
|
||||
return 0;
|
||||
return snd_ctl_enum_info(einf, 1,
|
||||
ARRAY_SIZE(special_dig_in_iface_labels),
|
||||
special_dig_in_iface_labels);
|
||||
}
|
||||
static int special_dig_in_iface_ctl_get(struct snd_kcontrol *kctl,
|
||||
struct snd_ctl_elem_value *uval)
|
||||
|
@ -539,23 +522,15 @@ static struct snd_kcontrol_new special_dig_in_iface_ctl = {
|
|||
};
|
||||
|
||||
/* Digital output interface control for special firmware */
|
||||
static char *const special_dig_out_iface_labels[] = {
|
||||
static const char *const special_dig_out_iface_labels[] = {
|
||||
"S/PDIF Optical and Coaxial", "ADAT Optical"
|
||||
};
|
||||
static int special_dig_out_iface_ctl_info(struct snd_kcontrol *kctl,
|
||||
struct snd_ctl_elem_info *einf)
|
||||
{
|
||||
einf->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
|
||||
einf->count = 1;
|
||||
einf->value.enumerated.items = ARRAY_SIZE(special_dig_out_iface_labels);
|
||||
|
||||
if (einf->value.enumerated.item >= einf->value.enumerated.items)
|
||||
einf->value.enumerated.item = einf->value.enumerated.items - 1;
|
||||
|
||||
strcpy(einf->value.enumerated.name,
|
||||
special_dig_out_iface_labels[einf->value.enumerated.item]);
|
||||
|
||||
return 0;
|
||||
return snd_ctl_enum_info(einf, 1,
|
||||
ARRAY_SIZE(special_dig_out_iface_labels),
|
||||
special_dig_out_iface_labels);
|
||||
}
|
||||
static int special_dig_out_iface_ctl_get(struct snd_kcontrol *kctl,
|
||||
struct snd_ctl_elem_value *uval)
|
||||
|
@ -631,7 +606,7 @@ end:
|
|||
}
|
||||
|
||||
/* Hardware metering for special firmware */
|
||||
static char *const special_meter_labels[] = {
|
||||
static const char *const special_meter_labels[] = {
|
||||
ANA_IN, ANA_IN, ANA_IN, ANA_IN,
|
||||
SPDIF_IN,
|
||||
ADAT_IN, ADAT_IN, ADAT_IN, ADAT_IN,
|
||||
|
@ -671,30 +646,30 @@ end:
|
|||
}
|
||||
|
||||
/* last 4 bytes are omitted because it's clock info. */
|
||||
static char *const fw410_meter_labels[] = {
|
||||
static const char *const fw410_meter_labels[] = {
|
||||
ANA_IN, DIG_IN,
|
||||
ANA_OUT, ANA_OUT, ANA_OUT, ANA_OUT, DIG_OUT,
|
||||
HP_OUT
|
||||
};
|
||||
static char *const audiophile_meter_labels[] = {
|
||||
static const char *const audiophile_meter_labels[] = {
|
||||
ANA_IN, DIG_IN,
|
||||
ANA_OUT, ANA_OUT, DIG_OUT,
|
||||
HP_OUT, AUX_OUT,
|
||||
};
|
||||
static char *const solo_meter_labels[] = {
|
||||
static const char *const solo_meter_labels[] = {
|
||||
ANA_IN, DIG_IN,
|
||||
STRM_IN, STRM_IN,
|
||||
ANA_OUT, DIG_OUT
|
||||
};
|
||||
|
||||
/* no clock info */
|
||||
static char *const ozonic_meter_labels[] = {
|
||||
static const char *const ozonic_meter_labels[] = {
|
||||
ANA_IN, ANA_IN,
|
||||
STRM_IN, STRM_IN,
|
||||
ANA_OUT, ANA_OUT
|
||||
};
|
||||
/* TODO: need testers. these positions are based on authour's assumption */
|
||||
static char *const nrv10_meter_labels[] = {
|
||||
static const char *const nrv10_meter_labels[] = {
|
||||
ANA_IN, ANA_IN, ANA_IN, ANA_IN,
|
||||
DIG_IN,
|
||||
ANA_OUT, ANA_OUT, ANA_OUT, ANA_OUT,
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
#include "./bebob.h"
|
||||
|
||||
static char *const phase88_rack_clk_src_labels[] = {
|
||||
static const char *const phase88_rack_clk_src_labels[] = {
|
||||
SND_BEBOB_CLOCK_INTERNAL, "Digital In", "Word Clock"
|
||||
};
|
||||
static int
|
||||
|
@ -34,7 +34,7 @@ end:
|
|||
return err;
|
||||
}
|
||||
|
||||
static char *const phase24_series_clk_src_labels[] = {
|
||||
static const char *const phase24_series_clk_src_labels[] = {
|
||||
SND_BEBOB_CLOCK_INTERNAL, "Digital In"
|
||||
};
|
||||
static int
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
* reccomend users to close ffado-mixer at 192.0kHz if mixer is needless.
|
||||
*/
|
||||
|
||||
static char *const clk_src_labels[] = {SND_BEBOB_CLOCK_INTERNAL, "SPDIF"};
|
||||
static const char *const clk_src_labels[] = {SND_BEBOB_CLOCK_INTERNAL, "SPDIF"};
|
||||
static int
|
||||
clk_src_get(struct snd_bebob *bebob, unsigned int *id)
|
||||
{
|
||||
|
|
|
@ -114,6 +114,7 @@ static int pcr_modify(struct cmp_connection *c,
|
|||
* cmp_connection_init - initializes a connection manager
|
||||
* @c: the connection manager to initialize
|
||||
* @unit: a unit of the target device
|
||||
* @direction: input or output
|
||||
* @pcr_index: the index of the iPCR/oPCR on the target device
|
||||
*/
|
||||
int cmp_connection_init(struct cmp_connection *c,
|
||||
|
@ -154,6 +155,7 @@ EXPORT_SYMBOL(cmp_connection_init);
|
|||
/**
|
||||
* cmp_connection_check_used - check connection is already esablished or not
|
||||
* @c: the connection manager to be checked
|
||||
* @used: the pointer to store the result of checking the connection
|
||||
*/
|
||||
int cmp_connection_check_used(struct cmp_connection *c, bool *used)
|
||||
{
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,3 @@
|
|||
snd-dice-objs := dice-transaction.o dice-stream.o dice-proc.o dice-midi.o \
|
||||
dice-pcm.o dice-hwdep.o dice.o
|
||||
obj-m += snd-dice.o
|
|
@ -0,0 +1,190 @@
|
|||
/*
|
||||
* dice_hwdep.c - a part of driver for DICE based devices
|
||||
*
|
||||
* Copyright (c) Clemens Ladisch <clemens@ladisch.de>
|
||||
* Copyright (c) 2014 Takashi Sakamoto <o-takashi@sakamocchi.jp>
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#include "dice.h"
|
||||
|
||||
static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf,
|
||||
long count, loff_t *offset)
|
||||
{
|
||||
struct snd_dice *dice = hwdep->private_data;
|
||||
DEFINE_WAIT(wait);
|
||||
union snd_firewire_event event;
|
||||
|
||||
spin_lock_irq(&dice->lock);
|
||||
|
||||
while (!dice->dev_lock_changed && dice->notification_bits == 0) {
|
||||
prepare_to_wait(&dice->hwdep_wait, &wait, TASK_INTERRUPTIBLE);
|
||||
spin_unlock_irq(&dice->lock);
|
||||
schedule();
|
||||
finish_wait(&dice->hwdep_wait, &wait);
|
||||
if (signal_pending(current))
|
||||
return -ERESTARTSYS;
|
||||
spin_lock_irq(&dice->lock);
|
||||
}
|
||||
|
||||
memset(&event, 0, sizeof(event));
|
||||
if (dice->dev_lock_changed) {
|
||||
event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS;
|
||||
event.lock_status.status = dice->dev_lock_count > 0;
|
||||
dice->dev_lock_changed = false;
|
||||
|
||||
count = min_t(long, count, sizeof(event.lock_status));
|
||||
} else {
|
||||
event.dice_notification.type =
|
||||
SNDRV_FIREWIRE_EVENT_DICE_NOTIFICATION;
|
||||
event.dice_notification.notification = dice->notification_bits;
|
||||
dice->notification_bits = 0;
|
||||
|
||||
count = min_t(long, count, sizeof(event.dice_notification));
|
||||
}
|
||||
|
||||
spin_unlock_irq(&dice->lock);
|
||||
|
||||
if (copy_to_user(buf, &event, count))
|
||||
return -EFAULT;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static unsigned int hwdep_poll(struct snd_hwdep *hwdep, struct file *file,
|
||||
poll_table *wait)
|
||||
{
|
||||
struct snd_dice *dice = hwdep->private_data;
|
||||
unsigned int events;
|
||||
|
||||
poll_wait(file, &dice->hwdep_wait, wait);
|
||||
|
||||
spin_lock_irq(&dice->lock);
|
||||
if (dice->dev_lock_changed || dice->notification_bits != 0)
|
||||
events = POLLIN | POLLRDNORM;
|
||||
else
|
||||
events = 0;
|
||||
spin_unlock_irq(&dice->lock);
|
||||
|
||||
return events;
|
||||
}
|
||||
|
||||
static int hwdep_get_info(struct snd_dice *dice, void __user *arg)
|
||||
{
|
||||
struct fw_device *dev = fw_parent_device(dice->unit);
|
||||
struct snd_firewire_get_info info;
|
||||
|
||||
memset(&info, 0, sizeof(info));
|
||||
info.type = SNDRV_FIREWIRE_TYPE_DICE;
|
||||
info.card = dev->card->index;
|
||||
*(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]);
|
||||
*(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]);
|
||||
strlcpy(info.device_name, dev_name(&dev->device),
|
||||
sizeof(info.device_name));
|
||||
|
||||
if (copy_to_user(arg, &info, sizeof(info)))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hwdep_lock(struct snd_dice *dice)
|
||||
{
|
||||
int err;
|
||||
|
||||
spin_lock_irq(&dice->lock);
|
||||
|
||||
if (dice->dev_lock_count == 0) {
|
||||
dice->dev_lock_count = -1;
|
||||
err = 0;
|
||||
} else {
|
||||
err = -EBUSY;
|
||||
}
|
||||
|
||||
spin_unlock_irq(&dice->lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int hwdep_unlock(struct snd_dice *dice)
|
||||
{
|
||||
int err;
|
||||
|
||||
spin_lock_irq(&dice->lock);
|
||||
|
||||
if (dice->dev_lock_count == -1) {
|
||||
dice->dev_lock_count = 0;
|
||||
err = 0;
|
||||
} else {
|
||||
err = -EBADFD;
|
||||
}
|
||||
|
||||
spin_unlock_irq(&dice->lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int hwdep_release(struct snd_hwdep *hwdep, struct file *file)
|
||||
{
|
||||
struct snd_dice *dice = hwdep->private_data;
|
||||
|
||||
spin_lock_irq(&dice->lock);
|
||||
if (dice->dev_lock_count == -1)
|
||||
dice->dev_lock_count = 0;
|
||||
spin_unlock_irq(&dice->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct snd_dice *dice = hwdep->private_data;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_FIREWIRE_IOCTL_GET_INFO:
|
||||
return hwdep_get_info(dice, (void __user *)arg);
|
||||
case SNDRV_FIREWIRE_IOCTL_LOCK:
|
||||
return hwdep_lock(dice);
|
||||
case SNDRV_FIREWIRE_IOCTL_UNLOCK:
|
||||
return hwdep_unlock(dice);
|
||||
default:
|
||||
return -ENOIOCTLCMD;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
static int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
return hwdep_ioctl(hwdep, file, cmd,
|
||||
(unsigned long)compat_ptr(arg));
|
||||
}
|
||||
#else
|
||||
#define hwdep_compat_ioctl NULL
|
||||
#endif
|
||||
|
||||
int snd_dice_create_hwdep(struct snd_dice *dice)
|
||||
{
|
||||
static const struct snd_hwdep_ops ops = {
|
||||
.read = hwdep_read,
|
||||
.release = hwdep_release,
|
||||
.poll = hwdep_poll,
|
||||
.ioctl = hwdep_ioctl,
|
||||
.ioctl_compat = hwdep_compat_ioctl,
|
||||
};
|
||||
struct snd_hwdep *hwdep;
|
||||
int err;
|
||||
|
||||
err = snd_hwdep_new(dice->card, "DICE", 0, &hwdep);
|
||||
if (err < 0)
|
||||
return err;
|
||||
strcpy(hwdep->name, "DICE");
|
||||
hwdep->iface = SNDRV_HWDEP_IFACE_FW_DICE;
|
||||
hwdep->ops = ops;
|
||||
hwdep->private_data = dice;
|
||||
hwdep->exclusive = true;
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* dice_midi.c - a part of driver for Dice based devices
|
||||
*
|
||||
* Copyright (c) 2014 Takashi Sakamoto
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
#include "dice.h"
|
||||
|
||||
static int midi_open(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
struct snd_dice *dice = substream->rmidi->private_data;
|
||||
int err;
|
||||
|
||||
err = snd_dice_stream_lock_try(dice);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
mutex_lock(&dice->mutex);
|
||||
|
||||
dice->substreams_counter++;
|
||||
err = snd_dice_stream_start_duplex(dice, 0);
|
||||
|
||||
mutex_unlock(&dice->mutex);
|
||||
|
||||
if (err < 0)
|
||||
snd_dice_stream_lock_release(dice);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int midi_close(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
struct snd_dice *dice = substream->rmidi->private_data;
|
||||
|
||||
mutex_lock(&dice->mutex);
|
||||
|
||||
dice->substreams_counter--;
|
||||
snd_dice_stream_stop_duplex(dice);
|
||||
|
||||
mutex_unlock(&dice->mutex);
|
||||
|
||||
snd_dice_stream_lock_release(dice);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up)
|
||||
{
|
||||
struct snd_dice *dice = substrm->rmidi->private_data;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&dice->lock, flags);
|
||||
|
||||
if (up)
|
||||
amdtp_stream_midi_trigger(&dice->tx_stream,
|
||||
substrm->number, substrm);
|
||||
else
|
||||
amdtp_stream_midi_trigger(&dice->tx_stream,
|
||||
substrm->number, NULL);
|
||||
|
||||
spin_unlock_irqrestore(&dice->lock, flags);
|
||||
}
|
||||
|
||||
static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up)
|
||||
{
|
||||
struct snd_dice *dice = substrm->rmidi->private_data;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&dice->lock, flags);
|
||||
|
||||
if (up)
|
||||
amdtp_stream_midi_trigger(&dice->rx_stream,
|
||||
substrm->number, substrm);
|
||||
else
|
||||
amdtp_stream_midi_trigger(&dice->rx_stream,
|
||||
substrm->number, NULL);
|
||||
|
||||
spin_unlock_irqrestore(&dice->lock, flags);
|
||||
}
|
||||
|
||||
static struct snd_rawmidi_ops capture_ops = {
|
||||
.open = midi_open,
|
||||
.close = midi_close,
|
||||
.trigger = midi_capture_trigger,
|
||||
};
|
||||
|
||||
static struct snd_rawmidi_ops playback_ops = {
|
||||
.open = midi_open,
|
||||
.close = midi_close,
|
||||
.trigger = midi_playback_trigger,
|
||||
};
|
||||
|
||||
static void set_midi_substream_names(struct snd_dice *dice,
|
||||
struct snd_rawmidi_str *str)
|
||||
{
|
||||
struct snd_rawmidi_substream *subs;
|
||||
|
||||
list_for_each_entry(subs, &str->substreams, list) {
|
||||
snprintf(subs->name, sizeof(subs->name),
|
||||
"%s MIDI %d", dice->card->shortname, subs->number + 1);
|
||||
}
|
||||
}
|
||||
|
||||
int snd_dice_create_midi(struct snd_dice *dice)
|
||||
{
|
||||
struct snd_rawmidi *rmidi;
|
||||
struct snd_rawmidi_str *str;
|
||||
unsigned int i, midi_in_ports, midi_out_ports;
|
||||
int err;
|
||||
|
||||
midi_in_ports = midi_out_ports = 0;
|
||||
for (i = 0; i < 3; i++) {
|
||||
midi_in_ports = max(dice->tx_midi_ports[i], midi_in_ports);
|
||||
midi_out_ports = max(dice->rx_midi_ports[i], midi_out_ports);
|
||||
}
|
||||
|
||||
if (midi_in_ports + midi_out_ports == 0)
|
||||
return 0;
|
||||
|
||||
/* create midi ports */
|
||||
err = snd_rawmidi_new(dice->card, dice->card->driver, 0,
|
||||
midi_out_ports, midi_in_ports,
|
||||
&rmidi);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
snprintf(rmidi->name, sizeof(rmidi->name),
|
||||
"%s MIDI", dice->card->shortname);
|
||||
rmidi->private_data = dice;
|
||||
|
||||
if (midi_in_ports > 0) {
|
||||
rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
|
||||
|
||||
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
|
||||
&capture_ops);
|
||||
|
||||
str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT];
|
||||
|
||||
set_midi_substream_names(dice, str);
|
||||
}
|
||||
|
||||
if (midi_out_ports > 0) {
|
||||
rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;
|
||||
|
||||
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
|
||||
&playback_ops);
|
||||
|
||||
str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT];
|
||||
|
||||
set_midi_substream_names(dice, str);
|
||||
}
|
||||
|
||||
if ((midi_out_ports > 0) && (midi_in_ports > 0))
|
||||
rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX;
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,422 @@
|
|||
/*
|
||||
* dice_pcm.c - a part of driver for DICE based devices
|
||||
*
|
||||
* Copyright (c) Clemens Ladisch <clemens@ladisch.de>
|
||||
* Copyright (c) 2014 Takashi Sakamoto <o-takashi@sakamocchi.jp>
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#include "dice.h"
|
||||
|
||||
static int dice_rate_constraint(struct snd_pcm_hw_params *params,
|
||||
struct snd_pcm_hw_rule *rule)
|
||||
{
|
||||
struct snd_pcm_substream *substream = rule->private;
|
||||
struct snd_dice *dice = substream->private_data;
|
||||
|
||||
const struct snd_interval *c =
|
||||
hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS);
|
||||
struct snd_interval *r =
|
||||
hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
|
||||
struct snd_interval rates = {
|
||||
.min = UINT_MAX, .max = 0, .integer = 1
|
||||
};
|
||||
unsigned int i, rate, mode, *pcm_channels;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
|
||||
pcm_channels = dice->tx_channels;
|
||||
else
|
||||
pcm_channels = dice->rx_channels;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(snd_dice_rates); ++i) {
|
||||
rate = snd_dice_rates[i];
|
||||
if (snd_dice_stream_get_rate_mode(dice, rate, &mode) < 0)
|
||||
continue;
|
||||
|
||||
if (!snd_interval_test(c, pcm_channels[mode]))
|
||||
continue;
|
||||
|
||||
rates.min = min(rates.min, rate);
|
||||
rates.max = max(rates.max, rate);
|
||||
}
|
||||
|
||||
return snd_interval_refine(r, &rates);
|
||||
}
|
||||
|
||||
static int dice_channels_constraint(struct snd_pcm_hw_params *params,
|
||||
struct snd_pcm_hw_rule *rule)
|
||||
{
|
||||
struct snd_pcm_substream *substream = rule->private;
|
||||
struct snd_dice *dice = substream->private_data;
|
||||
|
||||
const struct snd_interval *r =
|
||||
hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE);
|
||||
struct snd_interval *c =
|
||||
hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
|
||||
struct snd_interval channels = {
|
||||
.min = UINT_MAX, .max = 0, .integer = 1
|
||||
};
|
||||
unsigned int i, rate, mode, *pcm_channels;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
|
||||
pcm_channels = dice->tx_channels;
|
||||
else
|
||||
pcm_channels = dice->rx_channels;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(snd_dice_rates); ++i) {
|
||||
rate = snd_dice_rates[i];
|
||||
if (snd_dice_stream_get_rate_mode(dice, rate, &mode) < 0)
|
||||
continue;
|
||||
|
||||
if (!snd_interval_test(r, rate))
|
||||
continue;
|
||||
|
||||
channels.min = min(channels.min, pcm_channels[mode]);
|
||||
channels.max = max(channels.max, pcm_channels[mode]);
|
||||
}
|
||||
|
||||
return snd_interval_refine(c, &channels);
|
||||
}
|
||||
|
||||
static void limit_channels_and_rates(struct snd_dice *dice,
|
||||
struct snd_pcm_runtime *runtime,
|
||||
unsigned int *pcm_channels)
|
||||
{
|
||||
struct snd_pcm_hardware *hw = &runtime->hw;
|
||||
unsigned int i, rate, mode;
|
||||
|
||||
hw->channels_min = UINT_MAX;
|
||||
hw->channels_max = 0;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(snd_dice_rates); ++i) {
|
||||
rate = snd_dice_rates[i];
|
||||
if (snd_dice_stream_get_rate_mode(dice, rate, &mode) < 0)
|
||||
continue;
|
||||
hw->rates |= snd_pcm_rate_to_rate_bit(rate);
|
||||
|
||||
if (pcm_channels[mode] == 0)
|
||||
continue;
|
||||
hw->channels_min = min(hw->channels_min, pcm_channels[mode]);
|
||||
hw->channels_max = max(hw->channels_max, pcm_channels[mode]);
|
||||
}
|
||||
|
||||
snd_pcm_limit_hw_rates(runtime);
|
||||
}
|
||||
|
||||
static void limit_period_and_buffer(struct snd_pcm_hardware *hw)
|
||||
{
|
||||
hw->periods_min = 2; /* SNDRV_PCM_INFO_BATCH */
|
||||
hw->periods_max = UINT_MAX;
|
||||
|
||||
hw->period_bytes_min = 4 * hw->channels_max; /* byte for a frame */
|
||||
|
||||
/* Just to prevent from allocating much pages. */
|
||||
hw->period_bytes_max = hw->period_bytes_min * 2048;
|
||||
hw->buffer_bytes_max = hw->period_bytes_max * hw->periods_min;
|
||||
}
|
||||
|
||||
static int init_hw_info(struct snd_dice *dice,
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct snd_pcm_hardware *hw = &runtime->hw;
|
||||
struct amdtp_stream *stream;
|
||||
unsigned int *pcm_channels;
|
||||
int err;
|
||||
|
||||
hw->info = SNDRV_PCM_INFO_MMAP |
|
||||
SNDRV_PCM_INFO_MMAP_VALID |
|
||||
SNDRV_PCM_INFO_BATCH |
|
||||
SNDRV_PCM_INFO_INTERLEAVED |
|
||||
SNDRV_PCM_INFO_JOINT_DUPLEX |
|
||||
SNDRV_PCM_INFO_BLOCK_TRANSFER;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
|
||||
hw->formats = AMDTP_IN_PCM_FORMAT_BITS;
|
||||
stream = &dice->tx_stream;
|
||||
pcm_channels = dice->tx_channels;
|
||||
} else {
|
||||
hw->formats = AMDTP_OUT_PCM_FORMAT_BITS;
|
||||
stream = &dice->rx_stream;
|
||||
pcm_channels = dice->rx_channels;
|
||||
}
|
||||
|
||||
limit_channels_and_rates(dice, runtime, pcm_channels);
|
||||
limit_period_and_buffer(hw);
|
||||
|
||||
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
|
||||
dice_rate_constraint, substream,
|
||||
SNDRV_PCM_HW_PARAM_CHANNELS, -1);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
|
||||
dice_channels_constraint, substream,
|
||||
SNDRV_PCM_HW_PARAM_RATE, -1);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
err = amdtp_stream_add_pcm_hw_constraints(stream, runtime);
|
||||
end:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int pcm_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_dice *dice = substream->private_data;
|
||||
unsigned int source, rate;
|
||||
bool internal;
|
||||
int err;
|
||||
|
||||
err = snd_dice_stream_lock_try(dice);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
err = init_hw_info(dice, substream);
|
||||
if (err < 0)
|
||||
goto err_locked;
|
||||
|
||||
err = snd_dice_transaction_get_clock_source(dice, &source);
|
||||
if (err < 0)
|
||||
goto err_locked;
|
||||
switch (source) {
|
||||
case CLOCK_SOURCE_AES1:
|
||||
case CLOCK_SOURCE_AES2:
|
||||
case CLOCK_SOURCE_AES3:
|
||||
case CLOCK_SOURCE_AES4:
|
||||
case CLOCK_SOURCE_AES_ANY:
|
||||
case CLOCK_SOURCE_ADAT:
|
||||
case CLOCK_SOURCE_TDIF:
|
||||
case CLOCK_SOURCE_WC:
|
||||
internal = false;
|
||||
break;
|
||||
default:
|
||||
internal = true;
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* When source of clock is not internal or any PCM streams are running,
|
||||
* available sampling rate is limited at current sampling rate.
|
||||
*/
|
||||
if (!internal ||
|
||||
amdtp_stream_pcm_running(&dice->tx_stream) ||
|
||||
amdtp_stream_pcm_running(&dice->rx_stream)) {
|
||||
err = snd_dice_transaction_get_rate(dice, &rate);
|
||||
if (err < 0)
|
||||
goto err_locked;
|
||||
substream->runtime->hw.rate_min = rate;
|
||||
substream->runtime->hw.rate_max = rate;
|
||||
}
|
||||
|
||||
snd_pcm_set_sync(substream);
|
||||
end:
|
||||
return err;
|
||||
err_locked:
|
||||
snd_dice_stream_lock_release(dice);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int pcm_close(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_dice *dice = substream->private_data;
|
||||
|
||||
snd_dice_stream_lock_release(dice);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int capture_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *hw_params)
|
||||
{
|
||||
struct snd_dice *dice = substream->private_data;
|
||||
|
||||
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
|
||||
mutex_lock(&dice->mutex);
|
||||
dice->substreams_counter++;
|
||||
mutex_unlock(&dice->mutex);
|
||||
}
|
||||
|
||||
amdtp_stream_set_pcm_format(&dice->tx_stream,
|
||||
params_format(hw_params));
|
||||
|
||||
return snd_pcm_lib_alloc_vmalloc_buffer(substream,
|
||||
params_buffer_bytes(hw_params));
|
||||
}
|
||||
static int playback_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *hw_params)
|
||||
{
|
||||
struct snd_dice *dice = substream->private_data;
|
||||
|
||||
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
|
||||
mutex_lock(&dice->mutex);
|
||||
dice->substreams_counter++;
|
||||
mutex_unlock(&dice->mutex);
|
||||
}
|
||||
|
||||
amdtp_stream_set_pcm_format(&dice->rx_stream,
|
||||
params_format(hw_params));
|
||||
|
||||
return snd_pcm_lib_alloc_vmalloc_buffer(substream,
|
||||
params_buffer_bytes(hw_params));
|
||||
}
|
||||
|
||||
static int capture_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_dice *dice = substream->private_data;
|
||||
|
||||
mutex_lock(&dice->mutex);
|
||||
|
||||
if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
|
||||
dice->substreams_counter--;
|
||||
|
||||
snd_dice_stream_stop_duplex(dice);
|
||||
|
||||
mutex_unlock(&dice->mutex);
|
||||
|
||||
return snd_pcm_lib_free_vmalloc_buffer(substream);
|
||||
}
|
||||
|
||||
static int playback_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_dice *dice = substream->private_data;
|
||||
|
||||
mutex_lock(&dice->mutex);
|
||||
|
||||
if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
|
||||
dice->substreams_counter--;
|
||||
|
||||
snd_dice_stream_stop_duplex(dice);
|
||||
|
||||
mutex_unlock(&dice->mutex);
|
||||
|
||||
return snd_pcm_lib_free_vmalloc_buffer(substream);
|
||||
}
|
||||
|
||||
static int capture_prepare(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_dice *dice = substream->private_data;
|
||||
int err;
|
||||
|
||||
mutex_lock(&dice->mutex);
|
||||
err = snd_dice_stream_start_duplex(dice, substream->runtime->rate);
|
||||
mutex_unlock(&dice->mutex);
|
||||
if (err >= 0)
|
||||
amdtp_stream_pcm_prepare(&dice->tx_stream);
|
||||
|
||||
return 0;
|
||||
}
|
||||
static int playback_prepare(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_dice *dice = substream->private_data;
|
||||
int err;
|
||||
|
||||
mutex_lock(&dice->mutex);
|
||||
err = snd_dice_stream_start_duplex(dice, substream->runtime->rate);
|
||||
mutex_unlock(&dice->mutex);
|
||||
if (err >= 0)
|
||||
amdtp_stream_pcm_prepare(&dice->rx_stream);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int capture_trigger(struct snd_pcm_substream *substream, int cmd)
|
||||
{
|
||||
struct snd_dice *dice = substream->private_data;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
amdtp_stream_pcm_trigger(&dice->tx_stream, substream);
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
amdtp_stream_pcm_trigger(&dice->tx_stream, NULL);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
static int playback_trigger(struct snd_pcm_substream *substream, int cmd)
|
||||
{
|
||||
struct snd_dice *dice = substream->private_data;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
amdtp_stream_pcm_trigger(&dice->rx_stream, substream);
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
amdtp_stream_pcm_trigger(&dice->rx_stream, NULL);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t capture_pointer(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_dice *dice = substream->private_data;
|
||||
|
||||
return amdtp_stream_pcm_pointer(&dice->tx_stream);
|
||||
}
|
||||
static snd_pcm_uframes_t playback_pointer(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_dice *dice = substream->private_data;
|
||||
|
||||
return amdtp_stream_pcm_pointer(&dice->rx_stream);
|
||||
}
|
||||
|
||||
int snd_dice_create_pcm(struct snd_dice *dice)
|
||||
{
|
||||
static struct snd_pcm_ops capture_ops = {
|
||||
.open = pcm_open,
|
||||
.close = pcm_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = capture_hw_params,
|
||||
.hw_free = capture_hw_free,
|
||||
.prepare = capture_prepare,
|
||||
.trigger = capture_trigger,
|
||||
.pointer = capture_pointer,
|
||||
.page = snd_pcm_lib_get_vmalloc_page,
|
||||
.mmap = snd_pcm_lib_mmap_vmalloc,
|
||||
};
|
||||
static struct snd_pcm_ops playback_ops = {
|
||||
.open = pcm_open,
|
||||
.close = pcm_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = playback_hw_params,
|
||||
.hw_free = playback_hw_free,
|
||||
.prepare = playback_prepare,
|
||||
.trigger = playback_trigger,
|
||||
.pointer = playback_pointer,
|
||||
.page = snd_pcm_lib_get_vmalloc_page,
|
||||
.mmap = snd_pcm_lib_mmap_vmalloc,
|
||||
};
|
||||
struct snd_pcm *pcm;
|
||||
unsigned int i, capture, playback;
|
||||
int err;
|
||||
|
||||
capture = playback = 0;
|
||||
for (i = 0; i < 3; i++) {
|
||||
if (dice->tx_channels[i] > 0)
|
||||
capture = 1;
|
||||
if (dice->rx_channels[i] > 0)
|
||||
playback = 1;
|
||||
}
|
||||
|
||||
err = snd_pcm_new(dice->card, "DICE", 0, playback, capture, &pcm);
|
||||
if (err < 0)
|
||||
return err;
|
||||
pcm->private_data = dice;
|
||||
strcpy(pcm->name, dice->card->shortname);
|
||||
|
||||
if (capture > 0)
|
||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_ops);
|
||||
|
||||
if (playback > 0)
|
||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,252 @@
|
|||
/*
|
||||
* dice_proc.c - a part of driver for Dice based devices
|
||||
*
|
||||
* Copyright (c) Clemens Ladisch
|
||||
* Copyright (c) 2014 Takashi Sakamoto
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#include "dice.h"
|
||||
|
||||
static int dice_proc_read_mem(struct snd_dice *dice, void *buffer,
|
||||
unsigned int offset_q, unsigned int quadlets)
|
||||
{
|
||||
unsigned int i;
|
||||
int err;
|
||||
|
||||
err = snd_fw_transaction(dice->unit, TCODE_READ_BLOCK_REQUEST,
|
||||
DICE_PRIVATE_SPACE + 4 * offset_q,
|
||||
buffer, 4 * quadlets, 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
for (i = 0; i < quadlets; ++i)
|
||||
be32_to_cpus(&((u32 *)buffer)[i]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char *str_from_array(const char *const strs[], unsigned int count,
|
||||
unsigned int i)
|
||||
{
|
||||
if (i < count)
|
||||
return strs[i];
|
||||
|
||||
return "(unknown)";
|
||||
}
|
||||
|
||||
static void dice_proc_fixup_string(char *s, unsigned int size)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < size; i += 4)
|
||||
cpu_to_le32s((u32 *)(s + i));
|
||||
|
||||
for (i = 0; i < size - 2; ++i) {
|
||||
if (s[i] == '\0')
|
||||
return;
|
||||
if (s[i] == '\\' && s[i + 1] == '\\') {
|
||||
s[i + 2] = '\0';
|
||||
return;
|
||||
}
|
||||
}
|
||||
s[size - 1] = '\0';
|
||||
}
|
||||
|
||||
static void dice_proc_read(struct snd_info_entry *entry,
|
||||
struct snd_info_buffer *buffer)
|
||||
{
|
||||
static const char *const section_names[5] = {
|
||||
"global", "tx", "rx", "ext_sync", "unused2"
|
||||
};
|
||||
static const char *const clock_sources[] = {
|
||||
"aes1", "aes2", "aes3", "aes4", "aes", "adat", "tdif",
|
||||
"wc", "arx1", "arx2", "arx3", "arx4", "internal"
|
||||
};
|
||||
static const char *const rates[] = {
|
||||
"32000", "44100", "48000", "88200", "96000", "176400", "192000",
|
||||
"any low", "any mid", "any high", "none"
|
||||
};
|
||||
struct snd_dice *dice = entry->private_data;
|
||||
u32 sections[ARRAY_SIZE(section_names) * 2];
|
||||
struct {
|
||||
u32 number;
|
||||
u32 size;
|
||||
} tx_rx_header;
|
||||
union {
|
||||
struct {
|
||||
u32 owner_hi, owner_lo;
|
||||
u32 notification;
|
||||
char nick_name[NICK_NAME_SIZE];
|
||||
u32 clock_select;
|
||||
u32 enable;
|
||||
u32 status;
|
||||
u32 extended_status;
|
||||
u32 sample_rate;
|
||||
u32 version;
|
||||
u32 clock_caps;
|
||||
char clock_source_names[CLOCK_SOURCE_NAMES_SIZE];
|
||||
} global;
|
||||
struct {
|
||||
u32 iso;
|
||||
u32 number_audio;
|
||||
u32 number_midi;
|
||||
u32 speed;
|
||||
char names[TX_NAMES_SIZE];
|
||||
u32 ac3_caps;
|
||||
u32 ac3_enable;
|
||||
} tx;
|
||||
struct {
|
||||
u32 iso;
|
||||
u32 seq_start;
|
||||
u32 number_audio;
|
||||
u32 number_midi;
|
||||
char names[RX_NAMES_SIZE];
|
||||
u32 ac3_caps;
|
||||
u32 ac3_enable;
|
||||
} rx;
|
||||
struct {
|
||||
u32 clock_source;
|
||||
u32 locked;
|
||||
u32 rate;
|
||||
u32 adat_user_data;
|
||||
} ext_sync;
|
||||
} buf;
|
||||
unsigned int quadlets, stream, i;
|
||||
|
||||
if (dice_proc_read_mem(dice, sections, 0, ARRAY_SIZE(sections)) < 0)
|
||||
return;
|
||||
snd_iprintf(buffer, "sections:\n");
|
||||
for (i = 0; i < ARRAY_SIZE(section_names); ++i)
|
||||
snd_iprintf(buffer, " %s: offset %u, size %u\n",
|
||||
section_names[i],
|
||||
sections[i * 2], sections[i * 2 + 1]);
|
||||
|
||||
quadlets = min_t(u32, sections[1], sizeof(buf.global) / 4);
|
||||
if (dice_proc_read_mem(dice, &buf.global, sections[0], quadlets) < 0)
|
||||
return;
|
||||
snd_iprintf(buffer, "global:\n");
|
||||
snd_iprintf(buffer, " owner: %04x:%04x%08x\n",
|
||||
buf.global.owner_hi >> 16,
|
||||
buf.global.owner_hi & 0xffff, buf.global.owner_lo);
|
||||
snd_iprintf(buffer, " notification: %08x\n", buf.global.notification);
|
||||
dice_proc_fixup_string(buf.global.nick_name, NICK_NAME_SIZE);
|
||||
snd_iprintf(buffer, " nick name: %s\n", buf.global.nick_name);
|
||||
snd_iprintf(buffer, " clock select: %s %s\n",
|
||||
str_from_array(clock_sources, ARRAY_SIZE(clock_sources),
|
||||
buf.global.clock_select & CLOCK_SOURCE_MASK),
|
||||
str_from_array(rates, ARRAY_SIZE(rates),
|
||||
(buf.global.clock_select & CLOCK_RATE_MASK)
|
||||
>> CLOCK_RATE_SHIFT));
|
||||
snd_iprintf(buffer, " enable: %u\n", buf.global.enable);
|
||||
snd_iprintf(buffer, " status: %slocked %s\n",
|
||||
buf.global.status & STATUS_SOURCE_LOCKED ? "" : "un",
|
||||
str_from_array(rates, ARRAY_SIZE(rates),
|
||||
(buf.global.status &
|
||||
STATUS_NOMINAL_RATE_MASK)
|
||||
>> CLOCK_RATE_SHIFT));
|
||||
snd_iprintf(buffer, " ext status: %08x\n", buf.global.extended_status);
|
||||
snd_iprintf(buffer, " sample rate: %u\n", buf.global.sample_rate);
|
||||
snd_iprintf(buffer, " version: %u.%u.%u.%u\n",
|
||||
(buf.global.version >> 24) & 0xff,
|
||||
(buf.global.version >> 16) & 0xff,
|
||||
(buf.global.version >> 8) & 0xff,
|
||||
(buf.global.version >> 0) & 0xff);
|
||||
if (quadlets >= 90) {
|
||||
snd_iprintf(buffer, " clock caps:");
|
||||
for (i = 0; i <= 6; ++i)
|
||||
if (buf.global.clock_caps & (1 << i))
|
||||
snd_iprintf(buffer, " %s", rates[i]);
|
||||
for (i = 0; i <= 12; ++i)
|
||||
if (buf.global.clock_caps & (1 << (16 + i)))
|
||||
snd_iprintf(buffer, " %s", clock_sources[i]);
|
||||
snd_iprintf(buffer, "\n");
|
||||
dice_proc_fixup_string(buf.global.clock_source_names,
|
||||
CLOCK_SOURCE_NAMES_SIZE);
|
||||
snd_iprintf(buffer, " clock source names: %s\n",
|
||||
buf.global.clock_source_names);
|
||||
}
|
||||
|
||||
if (dice_proc_read_mem(dice, &tx_rx_header, sections[2], 2) < 0)
|
||||
return;
|
||||
quadlets = min_t(u32, tx_rx_header.size, sizeof(buf.tx) / 4);
|
||||
for (stream = 0; stream < tx_rx_header.number; ++stream) {
|
||||
if (dice_proc_read_mem(dice, &buf.tx, sections[2] + 2 +
|
||||
stream * tx_rx_header.size,
|
||||
quadlets) < 0)
|
||||
break;
|
||||
snd_iprintf(buffer, "tx %u:\n", stream);
|
||||
snd_iprintf(buffer, " iso channel: %d\n", (int)buf.tx.iso);
|
||||
snd_iprintf(buffer, " audio channels: %u\n",
|
||||
buf.tx.number_audio);
|
||||
snd_iprintf(buffer, " midi ports: %u\n", buf.tx.number_midi);
|
||||
snd_iprintf(buffer, " speed: S%u\n", 100u << buf.tx.speed);
|
||||
if (quadlets >= 68) {
|
||||
dice_proc_fixup_string(buf.tx.names, TX_NAMES_SIZE);
|
||||
snd_iprintf(buffer, " names: %s\n", buf.tx.names);
|
||||
}
|
||||
if (quadlets >= 70) {
|
||||
snd_iprintf(buffer, " ac3 caps: %08x\n",
|
||||
buf.tx.ac3_caps);
|
||||
snd_iprintf(buffer, " ac3 enable: %08x\n",
|
||||
buf.tx.ac3_enable);
|
||||
}
|
||||
}
|
||||
|
||||
if (dice_proc_read_mem(dice, &tx_rx_header, sections[4], 2) < 0)
|
||||
return;
|
||||
quadlets = min_t(u32, tx_rx_header.size, sizeof(buf.rx) / 4);
|
||||
for (stream = 0; stream < tx_rx_header.number; ++stream) {
|
||||
if (dice_proc_read_mem(dice, &buf.rx, sections[4] + 2 +
|
||||
stream * tx_rx_header.size,
|
||||
quadlets) < 0)
|
||||
break;
|
||||
snd_iprintf(buffer, "rx %u:\n", stream);
|
||||
snd_iprintf(buffer, " iso channel: %d\n", (int)buf.rx.iso);
|
||||
snd_iprintf(buffer, " sequence start: %u\n", buf.rx.seq_start);
|
||||
snd_iprintf(buffer, " audio channels: %u\n",
|
||||
buf.rx.number_audio);
|
||||
snd_iprintf(buffer, " midi ports: %u\n", buf.rx.number_midi);
|
||||
if (quadlets >= 68) {
|
||||
dice_proc_fixup_string(buf.rx.names, RX_NAMES_SIZE);
|
||||
snd_iprintf(buffer, " names: %s\n", buf.rx.names);
|
||||
}
|
||||
if (quadlets >= 70) {
|
||||
snd_iprintf(buffer, " ac3 caps: %08x\n",
|
||||
buf.rx.ac3_caps);
|
||||
snd_iprintf(buffer, " ac3 enable: %08x\n",
|
||||
buf.rx.ac3_enable);
|
||||
}
|
||||
}
|
||||
|
||||
quadlets = min_t(u32, sections[7], sizeof(buf.ext_sync) / 4);
|
||||
if (quadlets >= 4) {
|
||||
if (dice_proc_read_mem(dice, &buf.ext_sync,
|
||||
sections[6], 4) < 0)
|
||||
return;
|
||||
snd_iprintf(buffer, "ext status:\n");
|
||||
snd_iprintf(buffer, " clock source: %s\n",
|
||||
str_from_array(clock_sources,
|
||||
ARRAY_SIZE(clock_sources),
|
||||
buf.ext_sync.clock_source));
|
||||
snd_iprintf(buffer, " locked: %u\n", buf.ext_sync.locked);
|
||||
snd_iprintf(buffer, " rate: %s\n",
|
||||
str_from_array(rates, ARRAY_SIZE(rates),
|
||||
buf.ext_sync.rate));
|
||||
snd_iprintf(buffer, " adat user data: ");
|
||||
if (buf.ext_sync.adat_user_data & ADAT_USER_DATA_NO_DATA)
|
||||
snd_iprintf(buffer, "-\n");
|
||||
else
|
||||
snd_iprintf(buffer, "%x\n",
|
||||
buf.ext_sync.adat_user_data);
|
||||
}
|
||||
}
|
||||
|
||||
void snd_dice_create_proc(struct snd_dice *dice)
|
||||
{
|
||||
struct snd_info_entry *entry;
|
||||
|
||||
if (!snd_card_proc_new(dice->card, "dice", &entry))
|
||||
snd_info_set_text_ops(entry, dice, dice_proc_read);
|
||||
}
|
|
@ -0,0 +1,407 @@
|
|||
/*
|
||||
* dice_stream.c - a part of driver for DICE based devices
|
||||
*
|
||||
* Copyright (c) Clemens Ladisch <clemens@ladisch.de>
|
||||
* Copyright (c) 2014 Takashi Sakamoto <o-takashi@sakamocchi.jp>
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#include "dice.h"
|
||||
|
||||
#define CALLBACK_TIMEOUT 200
|
||||
|
||||
const unsigned int snd_dice_rates[SND_DICE_RATES_COUNT] = {
|
||||
/* mode 0 */
|
||||
[0] = 32000,
|
||||
[1] = 44100,
|
||||
[2] = 48000,
|
||||
/* mode 1 */
|
||||
[3] = 88200,
|
||||
[4] = 96000,
|
||||
/* mode 2 */
|
||||
[5] = 176400,
|
||||
[6] = 192000,
|
||||
};
|
||||
|
||||
int snd_dice_stream_get_rate_mode(struct snd_dice *dice, unsigned int rate,
|
||||
unsigned int *mode)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(snd_dice_rates); i++) {
|
||||
if (!(dice->clock_caps & BIT(i)))
|
||||
continue;
|
||||
if (snd_dice_rates[i] != rate)
|
||||
continue;
|
||||
|
||||
*mode = (i - 1) / 2;
|
||||
return 0;
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static void release_resources(struct snd_dice *dice,
|
||||
struct fw_iso_resources *resources)
|
||||
{
|
||||
unsigned int channel;
|
||||
|
||||
/* Reset channel number */
|
||||
channel = cpu_to_be32((u32)-1);
|
||||
if (resources == &dice->tx_resources)
|
||||
snd_dice_transaction_write_tx(dice, TX_ISOCHRONOUS,
|
||||
&channel, 4);
|
||||
else
|
||||
snd_dice_transaction_write_rx(dice, RX_ISOCHRONOUS,
|
||||
&channel, 4);
|
||||
|
||||
fw_iso_resources_free(resources);
|
||||
}
|
||||
|
||||
static int keep_resources(struct snd_dice *dice,
|
||||
struct fw_iso_resources *resources,
|
||||
unsigned int max_payload_bytes)
|
||||
{
|
||||
unsigned int channel;
|
||||
int err;
|
||||
|
||||
err = fw_iso_resources_allocate(resources, max_payload_bytes,
|
||||
fw_parent_device(dice->unit)->max_speed);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
/* Set channel number */
|
||||
channel = cpu_to_be32(resources->channel);
|
||||
if (resources == &dice->tx_resources)
|
||||
err = snd_dice_transaction_write_tx(dice, TX_ISOCHRONOUS,
|
||||
&channel, 4);
|
||||
else
|
||||
err = snd_dice_transaction_write_rx(dice, RX_ISOCHRONOUS,
|
||||
&channel, 4);
|
||||
if (err < 0)
|
||||
release_resources(dice, resources);
|
||||
end:
|
||||
return err;
|
||||
}
|
||||
|
||||
static void stop_stream(struct snd_dice *dice, struct amdtp_stream *stream)
|
||||
{
|
||||
amdtp_stream_pcm_abort(stream);
|
||||
amdtp_stream_stop(stream);
|
||||
|
||||
if (stream == &dice->tx_stream)
|
||||
release_resources(dice, &dice->tx_resources);
|
||||
else
|
||||
release_resources(dice, &dice->rx_resources);
|
||||
}
|
||||
|
||||
static int start_stream(struct snd_dice *dice, struct amdtp_stream *stream,
|
||||
unsigned int rate)
|
||||
{
|
||||
struct fw_iso_resources *resources;
|
||||
unsigned int i, mode, pcm_chs, midi_ports;
|
||||
int err;
|
||||
|
||||
err = snd_dice_stream_get_rate_mode(dice, rate, &mode);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
if (stream == &dice->tx_stream) {
|
||||
resources = &dice->tx_resources;
|
||||
pcm_chs = dice->tx_channels[mode];
|
||||
midi_ports = dice->tx_midi_ports[mode];
|
||||
} else {
|
||||
resources = &dice->rx_resources;
|
||||
pcm_chs = dice->rx_channels[mode];
|
||||
midi_ports = dice->rx_midi_ports[mode];
|
||||
}
|
||||
|
||||
/*
|
||||
* At 176.4/192.0 kHz, Dice has a quirk to transfer two PCM frames in
|
||||
* one data block of AMDTP packet. Thus sampling transfer frequency is
|
||||
* a half of PCM sampling frequency, i.e. PCM frames at 192.0 kHz are
|
||||
* transferred on AMDTP packets at 96 kHz. Two successive samples of a
|
||||
* channel are stored consecutively in the packet. This quirk is called
|
||||
* as 'Dual Wire'.
|
||||
* For this quirk, blocking mode is required and PCM buffer size should
|
||||
* be aligned to SYT_INTERVAL.
|
||||
*/
|
||||
if (mode > 1) {
|
||||
rate /= 2;
|
||||
pcm_chs *= 2;
|
||||
stream->double_pcm_frames = true;
|
||||
} else {
|
||||
stream->double_pcm_frames = false;
|
||||
}
|
||||
|
||||
amdtp_stream_set_parameters(stream, rate, pcm_chs, midi_ports);
|
||||
if (mode > 1) {
|
||||
pcm_chs /= 2;
|
||||
|
||||
for (i = 0; i < pcm_chs; i++) {
|
||||
stream->pcm_positions[i] = i * 2;
|
||||
stream->pcm_positions[i + pcm_chs] = i * 2 + 1;
|
||||
}
|
||||
}
|
||||
|
||||
err = keep_resources(dice, resources,
|
||||
amdtp_stream_get_max_payload(stream));
|
||||
if (err < 0) {
|
||||
dev_err(&dice->unit->device,
|
||||
"fail to keep isochronous resources\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
err = amdtp_stream_start(stream, resources->channel,
|
||||
fw_parent_device(dice->unit)->max_speed);
|
||||
if (err < 0)
|
||||
release_resources(dice, resources);
|
||||
end:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int get_sync_mode(struct snd_dice *dice, enum cip_flags *sync_mode)
|
||||
{
|
||||
u32 source;
|
||||
int err;
|
||||
|
||||
err = snd_dice_transaction_get_clock_source(dice, &source);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
switch (source) {
|
||||
/* So-called 'SYT Match' modes, sync_to_syt value of packets received */
|
||||
case CLOCK_SOURCE_ARX4: /* in 4th stream */
|
||||
case CLOCK_SOURCE_ARX3: /* in 3rd stream */
|
||||
case CLOCK_SOURCE_ARX2: /* in 2nd stream */
|
||||
err = -ENOSYS;
|
||||
break;
|
||||
case CLOCK_SOURCE_ARX1: /* in 1st stream, which this driver uses */
|
||||
*sync_mode = 0;
|
||||
break;
|
||||
default:
|
||||
*sync_mode = CIP_SYNC_TO_DEVICE;
|
||||
break;
|
||||
}
|
||||
end:
|
||||
return err;
|
||||
}
|
||||
|
||||
int snd_dice_stream_start_duplex(struct snd_dice *dice, unsigned int rate)
|
||||
{
|
||||
struct amdtp_stream *master, *slave;
|
||||
unsigned int curr_rate;
|
||||
enum cip_flags sync_mode;
|
||||
int err = 0;
|
||||
|
||||
if (dice->substreams_counter == 0)
|
||||
goto end;
|
||||
|
||||
err = get_sync_mode(dice, &sync_mode);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
if (sync_mode == CIP_SYNC_TO_DEVICE) {
|
||||
master = &dice->tx_stream;
|
||||
slave = &dice->rx_stream;
|
||||
} else {
|
||||
master = &dice->rx_stream;
|
||||
slave = &dice->tx_stream;
|
||||
}
|
||||
|
||||
/* Some packet queueing errors. */
|
||||
if (amdtp_streaming_error(master) || amdtp_streaming_error(slave))
|
||||
stop_stream(dice, master);
|
||||
|
||||
/* Stop stream if rate is different. */
|
||||
err = snd_dice_transaction_get_rate(dice, &curr_rate);
|
||||
if (err < 0) {
|
||||
dev_err(&dice->unit->device,
|
||||
"fail to get sampling rate\n");
|
||||
goto end;
|
||||
}
|
||||
if (rate == 0)
|
||||
rate = curr_rate;
|
||||
if (rate != curr_rate)
|
||||
stop_stream(dice, master);
|
||||
|
||||
if (!amdtp_stream_running(master)) {
|
||||
stop_stream(dice, slave);
|
||||
snd_dice_transaction_clear_enable(dice);
|
||||
|
||||
amdtp_stream_set_sync(sync_mode, master, slave);
|
||||
|
||||
err = snd_dice_transaction_set_rate(dice, rate);
|
||||
if (err < 0) {
|
||||
dev_err(&dice->unit->device,
|
||||
"fail to set sampling rate\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Start both streams. */
|
||||
err = start_stream(dice, master, rate);
|
||||
if (err < 0) {
|
||||
dev_err(&dice->unit->device,
|
||||
"fail to start AMDTP master stream\n");
|
||||
goto end;
|
||||
}
|
||||
err = start_stream(dice, slave, rate);
|
||||
if (err < 0) {
|
||||
dev_err(&dice->unit->device,
|
||||
"fail to start AMDTP slave stream\n");
|
||||
stop_stream(dice, master);
|
||||
goto end;
|
||||
}
|
||||
err = snd_dice_transaction_set_enable(dice);
|
||||
if (err < 0) {
|
||||
dev_err(&dice->unit->device,
|
||||
"fail to enable interface\n");
|
||||
stop_stream(dice, master);
|
||||
stop_stream(dice, slave);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Wait first callbacks */
|
||||
if (!amdtp_stream_wait_callback(master, CALLBACK_TIMEOUT) ||
|
||||
!amdtp_stream_wait_callback(slave, CALLBACK_TIMEOUT)) {
|
||||
snd_dice_transaction_clear_enable(dice);
|
||||
stop_stream(dice, master);
|
||||
stop_stream(dice, slave);
|
||||
err = -ETIMEDOUT;
|
||||
}
|
||||
}
|
||||
end:
|
||||
return err;
|
||||
}
|
||||
|
||||
void snd_dice_stream_stop_duplex(struct snd_dice *dice)
|
||||
{
|
||||
if (dice->substreams_counter > 0)
|
||||
return;
|
||||
|
||||
snd_dice_transaction_clear_enable(dice);
|
||||
|
||||
stop_stream(dice, &dice->tx_stream);
|
||||
stop_stream(dice, &dice->rx_stream);
|
||||
}
|
||||
|
||||
static int init_stream(struct snd_dice *dice, struct amdtp_stream *stream)
|
||||
{
|
||||
int err;
|
||||
struct fw_iso_resources *resources;
|
||||
enum amdtp_stream_direction dir;
|
||||
|
||||
if (stream == &dice->tx_stream) {
|
||||
resources = &dice->tx_resources;
|
||||
dir = AMDTP_IN_STREAM;
|
||||
} else {
|
||||
resources = &dice->rx_resources;
|
||||
dir = AMDTP_OUT_STREAM;
|
||||
}
|
||||
|
||||
err = fw_iso_resources_init(resources, dice->unit);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
resources->channels_mask = 0x00000000ffffffffuLL;
|
||||
|
||||
err = amdtp_stream_init(stream, dice->unit, dir, CIP_BLOCKING);
|
||||
if (err < 0) {
|
||||
amdtp_stream_destroy(stream);
|
||||
fw_iso_resources_destroy(resources);
|
||||
}
|
||||
end:
|
||||
return err;
|
||||
}
|
||||
|
||||
static void destroy_stream(struct snd_dice *dice, struct amdtp_stream *stream)
|
||||
{
|
||||
amdtp_stream_destroy(stream);
|
||||
|
||||
if (stream == &dice->tx_stream)
|
||||
fw_iso_resources_destroy(&dice->tx_resources);
|
||||
else
|
||||
fw_iso_resources_destroy(&dice->rx_resources);
|
||||
}
|
||||
|
||||
int snd_dice_stream_init_duplex(struct snd_dice *dice)
|
||||
{
|
||||
int err;
|
||||
|
||||
dice->substreams_counter = 0;
|
||||
|
||||
err = init_stream(dice, &dice->tx_stream);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
err = init_stream(dice, &dice->rx_stream);
|
||||
end:
|
||||
return err;
|
||||
}
|
||||
|
||||
void snd_dice_stream_destroy_duplex(struct snd_dice *dice)
|
||||
{
|
||||
snd_dice_transaction_clear_enable(dice);
|
||||
|
||||
stop_stream(dice, &dice->tx_stream);
|
||||
destroy_stream(dice, &dice->tx_stream);
|
||||
|
||||
stop_stream(dice, &dice->rx_stream);
|
||||
destroy_stream(dice, &dice->rx_stream);
|
||||
|
||||
dice->substreams_counter = 0;
|
||||
}
|
||||
|
||||
void snd_dice_stream_update_duplex(struct snd_dice *dice)
|
||||
{
|
||||
/*
|
||||
* On a bus reset, the DICE firmware disables streaming and then goes
|
||||
* off contemplating its own navel for hundreds of milliseconds before
|
||||
* it can react to any of our attempts to reenable streaming. This
|
||||
* means that we lose synchronization anyway, so we force our streams
|
||||
* to stop so that the application can restart them in an orderly
|
||||
* manner.
|
||||
*/
|
||||
dice->global_enabled = false;
|
||||
|
||||
stop_stream(dice, &dice->rx_stream);
|
||||
stop_stream(dice, &dice->tx_stream);
|
||||
|
||||
fw_iso_resources_update(&dice->rx_resources);
|
||||
fw_iso_resources_update(&dice->tx_resources);
|
||||
}
|
||||
|
||||
static void dice_lock_changed(struct snd_dice *dice)
|
||||
{
|
||||
dice->dev_lock_changed = true;
|
||||
wake_up(&dice->hwdep_wait);
|
||||
}
|
||||
|
||||
int snd_dice_stream_lock_try(struct snd_dice *dice)
|
||||
{
|
||||
int err;
|
||||
|
||||
spin_lock_irq(&dice->lock);
|
||||
|
||||
if (dice->dev_lock_count < 0) {
|
||||
err = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (dice->dev_lock_count++ == 0)
|
||||
dice_lock_changed(dice);
|
||||
err = 0;
|
||||
out:
|
||||
spin_unlock_irq(&dice->lock);
|
||||
return err;
|
||||
}
|
||||
|
||||
void snd_dice_stream_lock_release(struct snd_dice *dice)
|
||||
{
|
||||
spin_lock_irq(&dice->lock);
|
||||
|
||||
if (WARN_ON(dice->dev_lock_count <= 0))
|
||||
goto out;
|
||||
|
||||
if (--dice->dev_lock_count == 0)
|
||||
dice_lock_changed(dice);
|
||||
out:
|
||||
spin_unlock_irq(&dice->lock);
|
||||
}
|
|
@ -0,0 +1,382 @@
|
|||
/*
|
||||
* dice_transaction.c - a part of driver for Dice based devices
|
||||
*
|
||||
* Copyright (c) Clemens Ladisch
|
||||
* Copyright (c) 2014 Takashi Sakamoto
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#include "dice.h"
|
||||
|
||||
#define NOTIFICATION_TIMEOUT_MS 100
|
||||
|
||||
static u64 get_subaddr(struct snd_dice *dice, enum snd_dice_addr_type type,
|
||||
u64 offset)
|
||||
{
|
||||
switch (type) {
|
||||
case SND_DICE_ADDR_TYPE_TX:
|
||||
offset += dice->tx_offset;
|
||||
break;
|
||||
case SND_DICE_ADDR_TYPE_RX:
|
||||
offset += dice->rx_offset;
|
||||
break;
|
||||
case SND_DICE_ADDR_TYPE_SYNC:
|
||||
offset += dice->sync_offset;
|
||||
break;
|
||||
case SND_DICE_ADDR_TYPE_RSRV:
|
||||
offset += dice->rsrv_offset;
|
||||
break;
|
||||
case SND_DICE_ADDR_TYPE_GLOBAL:
|
||||
default:
|
||||
offset += dice->global_offset;
|
||||
break;
|
||||
}
|
||||
offset += DICE_PRIVATE_SPACE;
|
||||
return offset;
|
||||
}
|
||||
|
||||
int snd_dice_transaction_write(struct snd_dice *dice,
|
||||
enum snd_dice_addr_type type,
|
||||
unsigned int offset, void *buf, unsigned int len)
|
||||
{
|
||||
return snd_fw_transaction(dice->unit,
|
||||
(len == 4) ? TCODE_WRITE_QUADLET_REQUEST :
|
||||
TCODE_WRITE_BLOCK_REQUEST,
|
||||
get_subaddr(dice, type, offset), buf, len, 0);
|
||||
}
|
||||
|
||||
int snd_dice_transaction_read(struct snd_dice *dice,
|
||||
enum snd_dice_addr_type type, unsigned int offset,
|
||||
void *buf, unsigned int len)
|
||||
{
|
||||
return snd_fw_transaction(dice->unit,
|
||||
(len == 4) ? TCODE_READ_QUADLET_REQUEST :
|
||||
TCODE_READ_BLOCK_REQUEST,
|
||||
get_subaddr(dice, type, offset), buf, len, 0);
|
||||
}
|
||||
|
||||
static unsigned int get_clock_info(struct snd_dice *dice, __be32 *info)
|
||||
{
|
||||
return snd_dice_transaction_read_global(dice, GLOBAL_CLOCK_SELECT,
|
||||
info, 4);
|
||||
}
|
||||
|
||||
static int set_clock_info(struct snd_dice *dice,
|
||||
unsigned int rate, unsigned int source)
|
||||
{
|
||||
unsigned int retries = 3;
|
||||
unsigned int i;
|
||||
__be32 info;
|
||||
u32 mask;
|
||||
u32 clock;
|
||||
int err;
|
||||
retry:
|
||||
err = get_clock_info(dice, &info);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
clock = be32_to_cpu(info);
|
||||
if (source != UINT_MAX) {
|
||||
mask = CLOCK_SOURCE_MASK;
|
||||
clock &= ~mask;
|
||||
clock |= source;
|
||||
}
|
||||
if (rate != UINT_MAX) {
|
||||
for (i = 0; i < ARRAY_SIZE(snd_dice_rates); i++) {
|
||||
if (snd_dice_rates[i] == rate)
|
||||
break;
|
||||
}
|
||||
if (i == ARRAY_SIZE(snd_dice_rates)) {
|
||||
err = -EINVAL;
|
||||
goto end;
|
||||
}
|
||||
|
||||
mask = CLOCK_RATE_MASK;
|
||||
clock &= ~mask;
|
||||
clock |= i << CLOCK_RATE_SHIFT;
|
||||
}
|
||||
info = cpu_to_be32(clock);
|
||||
|
||||
if (completion_done(&dice->clock_accepted))
|
||||
reinit_completion(&dice->clock_accepted);
|
||||
|
||||
err = snd_dice_transaction_write_global(dice, GLOBAL_CLOCK_SELECT,
|
||||
&info, 4);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
/* Timeout means it's invalid request, probably bus reset occurred. */
|
||||
if (wait_for_completion_timeout(&dice->clock_accepted,
|
||||
msecs_to_jiffies(NOTIFICATION_TIMEOUT_MS)) == 0) {
|
||||
if (retries-- == 0) {
|
||||
err = -ETIMEDOUT;
|
||||
goto end;
|
||||
}
|
||||
|
||||
err = snd_dice_transaction_reinit(dice);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
msleep(500); /* arbitrary */
|
||||
goto retry;
|
||||
}
|
||||
end:
|
||||
return err;
|
||||
}
|
||||
|
||||
int snd_dice_transaction_get_clock_source(struct snd_dice *dice,
|
||||
unsigned int *source)
|
||||
{
|
||||
__be32 info;
|
||||
int err;
|
||||
|
||||
err = get_clock_info(dice, &info);
|
||||
if (err >= 0)
|
||||
*source = be32_to_cpu(info) & CLOCK_SOURCE_MASK;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int snd_dice_transaction_get_rate(struct snd_dice *dice, unsigned int *rate)
|
||||
{
|
||||
__be32 info;
|
||||
unsigned int index;
|
||||
int err;
|
||||
|
||||
err = get_clock_info(dice, &info);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
index = (be32_to_cpu(info) & CLOCK_RATE_MASK) >> CLOCK_RATE_SHIFT;
|
||||
if (index >= SND_DICE_RATES_COUNT) {
|
||||
err = -ENOSYS;
|
||||
goto end;
|
||||
}
|
||||
|
||||
*rate = snd_dice_rates[index];
|
||||
end:
|
||||
return err;
|
||||
}
|
||||
int snd_dice_transaction_set_rate(struct snd_dice *dice, unsigned int rate)
|
||||
{
|
||||
return set_clock_info(dice, rate, UINT_MAX);
|
||||
}
|
||||
|
||||
int snd_dice_transaction_set_enable(struct snd_dice *dice)
|
||||
{
|
||||
__be32 value;
|
||||
int err = 0;
|
||||
|
||||
if (dice->global_enabled)
|
||||
goto end;
|
||||
|
||||
value = cpu_to_be32(1);
|
||||
err = snd_fw_transaction(dice->unit, TCODE_WRITE_QUADLET_REQUEST,
|
||||
get_subaddr(dice, SND_DICE_ADDR_TYPE_GLOBAL,
|
||||
GLOBAL_ENABLE),
|
||||
&value, 4,
|
||||
FW_FIXED_GENERATION | dice->owner_generation);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
dice->global_enabled = true;
|
||||
end:
|
||||
return err;
|
||||
}
|
||||
|
||||
void snd_dice_transaction_clear_enable(struct snd_dice *dice)
|
||||
{
|
||||
__be32 value;
|
||||
|
||||
value = 0;
|
||||
snd_fw_transaction(dice->unit, TCODE_WRITE_QUADLET_REQUEST,
|
||||
get_subaddr(dice, SND_DICE_ADDR_TYPE_GLOBAL,
|
||||
GLOBAL_ENABLE),
|
||||
&value, 4, FW_QUIET |
|
||||
FW_FIXED_GENERATION | dice->owner_generation);
|
||||
|
||||
dice->global_enabled = false;
|
||||
}
|
||||
|
||||
static void dice_notification(struct fw_card *card, struct fw_request *request,
|
||||
int tcode, int destination, int source,
|
||||
int generation, unsigned long long offset,
|
||||
void *data, size_t length, void *callback_data)
|
||||
{
|
||||
struct snd_dice *dice = callback_data;
|
||||
u32 bits;
|
||||
unsigned long flags;
|
||||
|
||||
if (tcode != TCODE_WRITE_QUADLET_REQUEST) {
|
||||
fw_send_response(card, request, RCODE_TYPE_ERROR);
|
||||
return;
|
||||
}
|
||||
if ((offset & 3) != 0) {
|
||||
fw_send_response(card, request, RCODE_ADDRESS_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
bits = be32_to_cpup(data);
|
||||
|
||||
spin_lock_irqsave(&dice->lock, flags);
|
||||
dice->notification_bits |= bits;
|
||||
spin_unlock_irqrestore(&dice->lock, flags);
|
||||
|
||||
fw_send_response(card, request, RCODE_COMPLETE);
|
||||
|
||||
if (bits & NOTIFY_CLOCK_ACCEPTED)
|
||||
complete(&dice->clock_accepted);
|
||||
wake_up(&dice->hwdep_wait);
|
||||
}
|
||||
|
||||
static int register_notification_address(struct snd_dice *dice, bool retry)
|
||||
{
|
||||
struct fw_device *device = fw_parent_device(dice->unit);
|
||||
__be64 *buffer;
|
||||
unsigned int retries;
|
||||
int err;
|
||||
|
||||
retries = (retry) ? 3 : 0;
|
||||
|
||||
buffer = kmalloc(2 * 8, GFP_KERNEL);
|
||||
if (!buffer)
|
||||
return -ENOMEM;
|
||||
|
||||
for (;;) {
|
||||
buffer[0] = cpu_to_be64(OWNER_NO_OWNER);
|
||||
buffer[1] = cpu_to_be64(
|
||||
((u64)device->card->node_id << OWNER_NODE_SHIFT) |
|
||||
dice->notification_handler.offset);
|
||||
|
||||
dice->owner_generation = device->generation;
|
||||
smp_rmb(); /* node_id vs. generation */
|
||||
err = snd_fw_transaction(dice->unit, TCODE_LOCK_COMPARE_SWAP,
|
||||
get_subaddr(dice,
|
||||
SND_DICE_ADDR_TYPE_GLOBAL,
|
||||
GLOBAL_OWNER),
|
||||
buffer, 2 * 8,
|
||||
FW_FIXED_GENERATION |
|
||||
dice->owner_generation);
|
||||
if (err == 0) {
|
||||
/* success */
|
||||
if (buffer[0] == cpu_to_be64(OWNER_NO_OWNER))
|
||||
break;
|
||||
/* The address seems to be already registered. */
|
||||
if (buffer[0] == buffer[1])
|
||||
break;
|
||||
|
||||
dev_err(&dice->unit->device,
|
||||
"device is already in use\n");
|
||||
err = -EBUSY;
|
||||
}
|
||||
if (err != -EAGAIN || retries-- > 0)
|
||||
break;
|
||||
|
||||
msleep(20);
|
||||
}
|
||||
|
||||
kfree(buffer);
|
||||
|
||||
if (err < 0)
|
||||
dice->owner_generation = -1;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void unregister_notification_address(struct snd_dice *dice)
|
||||
{
|
||||
struct fw_device *device = fw_parent_device(dice->unit);
|
||||
__be64 *buffer;
|
||||
|
||||
buffer = kmalloc(2 * 8, GFP_KERNEL);
|
||||
if (buffer == NULL)
|
||||
return;
|
||||
|
||||
buffer[0] = cpu_to_be64(
|
||||
((u64)device->card->node_id << OWNER_NODE_SHIFT) |
|
||||
dice->notification_handler.offset);
|
||||
buffer[1] = cpu_to_be64(OWNER_NO_OWNER);
|
||||
snd_fw_transaction(dice->unit, TCODE_LOCK_COMPARE_SWAP,
|
||||
get_subaddr(dice, SND_DICE_ADDR_TYPE_GLOBAL,
|
||||
GLOBAL_OWNER),
|
||||
buffer, 2 * 8, FW_QUIET |
|
||||
FW_FIXED_GENERATION | dice->owner_generation);
|
||||
|
||||
kfree(buffer);
|
||||
|
||||
dice->owner_generation = -1;
|
||||
}
|
||||
|
||||
void snd_dice_transaction_destroy(struct snd_dice *dice)
|
||||
{
|
||||
struct fw_address_handler *handler = &dice->notification_handler;
|
||||
|
||||
if (handler->callback_data == NULL)
|
||||
return;
|
||||
|
||||
unregister_notification_address(dice);
|
||||
|
||||
fw_core_remove_address_handler(handler);
|
||||
handler->callback_data = NULL;
|
||||
}
|
||||
|
||||
int snd_dice_transaction_reinit(struct snd_dice *dice)
|
||||
{
|
||||
struct fw_address_handler *handler = &dice->notification_handler;
|
||||
|
||||
if (handler->callback_data == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
return register_notification_address(dice, false);
|
||||
}
|
||||
|
||||
int snd_dice_transaction_init(struct snd_dice *dice)
|
||||
{
|
||||
struct fw_address_handler *handler = &dice->notification_handler;
|
||||
__be32 *pointers;
|
||||
int err;
|
||||
|
||||
/* Use the same way which dice_interface_check() does. */
|
||||
pointers = kmalloc(sizeof(__be32) * 10, GFP_KERNEL);
|
||||
if (pointers == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Get offsets for sub-addresses */
|
||||
err = snd_fw_transaction(dice->unit, TCODE_READ_BLOCK_REQUEST,
|
||||
DICE_PRIVATE_SPACE,
|
||||
pointers, sizeof(__be32) * 10, 0);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
/* Allocation callback in address space over host controller */
|
||||
handler->length = 4;
|
||||
handler->address_callback = dice_notification;
|
||||
handler->callback_data = dice;
|
||||
err = fw_core_add_address_handler(handler, &fw_high_memory_region);
|
||||
if (err < 0) {
|
||||
handler->callback_data = NULL;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Register the address space */
|
||||
err = register_notification_address(dice, true);
|
||||
if (err < 0) {
|
||||
fw_core_remove_address_handler(handler);
|
||||
handler->callback_data = NULL;
|
||||
goto end;
|
||||
}
|
||||
|
||||
dice->global_offset = be32_to_cpu(pointers[0]) * 4;
|
||||
dice->tx_offset = be32_to_cpu(pointers[2]) * 4;
|
||||
dice->rx_offset = be32_to_cpu(pointers[4]) * 4;
|
||||
dice->sync_offset = be32_to_cpu(pointers[6]) * 4;
|
||||
dice->rsrv_offset = be32_to_cpu(pointers[8]) * 4;
|
||||
|
||||
/* Set up later. */
|
||||
if (be32_to_cpu(pointers[1]) * 4 >= GLOBAL_CLOCK_CAPABILITIES + 4)
|
||||
dice->clock_caps = 1;
|
||||
end:
|
||||
kfree(pointers);
|
||||
return err;
|
||||
}
|
|
@ -0,0 +1,361 @@
|
|||
/*
|
||||
* TC Applied Technologies Digital Interface Communications Engine driver
|
||||
*
|
||||
* Copyright (c) Clemens Ladisch <clemens@ladisch.de>
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#include "dice.h"
|
||||
|
||||
MODULE_DESCRIPTION("DICE driver");
|
||||
MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
||||
#define OUI_WEISS 0x001c6a
|
||||
|
||||
#define DICE_CATEGORY_ID 0x04
|
||||
#define WEISS_CATEGORY_ID 0x00
|
||||
|
||||
static int dice_interface_check(struct fw_unit *unit)
|
||||
{
|
||||
static const int min_values[10] = {
|
||||
10, 0x64 / 4,
|
||||
10, 0x18 / 4,
|
||||
10, 0x18 / 4,
|
||||
0, 0,
|
||||
0, 0,
|
||||
};
|
||||
struct fw_device *device = fw_parent_device(unit);
|
||||
struct fw_csr_iterator it;
|
||||
int key, val, vendor = -1, model = -1, err;
|
||||
unsigned int category, i;
|
||||
__be32 *pointers, value;
|
||||
__be32 version;
|
||||
|
||||
pointers = kmalloc_array(ARRAY_SIZE(min_values), sizeof(__be32),
|
||||
GFP_KERNEL);
|
||||
if (pointers == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
/*
|
||||
* Check that GUID and unit directory are constructed according to DICE
|
||||
* rules, i.e., that the specifier ID is the GUID's OUI, and that the
|
||||
* GUID chip ID consists of the 8-bit category ID, the 10-bit product
|
||||
* ID, and a 22-bit serial number.
|
||||
*/
|
||||
fw_csr_iterator_init(&it, unit->directory);
|
||||
while (fw_csr_iterator_next(&it, &key, &val)) {
|
||||
switch (key) {
|
||||
case CSR_SPECIFIER_ID:
|
||||
vendor = val;
|
||||
break;
|
||||
case CSR_MODEL:
|
||||
model = val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (vendor == OUI_WEISS)
|
||||
category = WEISS_CATEGORY_ID;
|
||||
else
|
||||
category = DICE_CATEGORY_ID;
|
||||
if (device->config_rom[3] != ((vendor << 8) | category) ||
|
||||
device->config_rom[4] >> 22 != model) {
|
||||
err = -ENODEV;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check that the sub address spaces exist and are located inside the
|
||||
* private address space. The minimum values are chosen so that all
|
||||
* minimally required registers are included.
|
||||
*/
|
||||
err = snd_fw_transaction(unit, TCODE_READ_BLOCK_REQUEST,
|
||||
DICE_PRIVATE_SPACE, pointers,
|
||||
sizeof(__be32) * ARRAY_SIZE(min_values), 0);
|
||||
if (err < 0) {
|
||||
err = -ENODEV;
|
||||
goto end;
|
||||
}
|
||||
for (i = 0; i < ARRAY_SIZE(min_values); ++i) {
|
||||
value = be32_to_cpu(pointers[i]);
|
||||
if (value < min_values[i] || value >= 0x40000) {
|
||||
err = -ENODEV;
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Check that the implemented DICE driver specification major version
|
||||
* number matches.
|
||||
*/
|
||||
err = snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST,
|
||||
DICE_PRIVATE_SPACE +
|
||||
be32_to_cpu(pointers[0]) * 4 + GLOBAL_VERSION,
|
||||
&version, 4, 0);
|
||||
if (err < 0) {
|
||||
err = -ENODEV;
|
||||
goto end;
|
||||
}
|
||||
if ((version & cpu_to_be32(0xff000000)) != cpu_to_be32(0x01000000)) {
|
||||
dev_err(&unit->device,
|
||||
"unknown DICE version: 0x%08x\n", be32_to_cpu(version));
|
||||
err = -ENODEV;
|
||||
goto end;
|
||||
}
|
||||
end:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int highest_supported_mode_rate(struct snd_dice *dice,
|
||||
unsigned int mode, unsigned int *rate)
|
||||
{
|
||||
unsigned int i, m;
|
||||
|
||||
for (i = ARRAY_SIZE(snd_dice_rates); i > 0; i--) {
|
||||
*rate = snd_dice_rates[i - 1];
|
||||
if (snd_dice_stream_get_rate_mode(dice, *rate, &m) < 0)
|
||||
continue;
|
||||
if (mode == m)
|
||||
break;
|
||||
}
|
||||
if (i == 0)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dice_read_mode_params(struct snd_dice *dice, unsigned int mode)
|
||||
{
|
||||
__be32 values[2];
|
||||
unsigned int rate;
|
||||
int err;
|
||||
|
||||
if (highest_supported_mode_rate(dice, mode, &rate) < 0) {
|
||||
dice->tx_channels[mode] = 0;
|
||||
dice->tx_midi_ports[mode] = 0;
|
||||
dice->rx_channels[mode] = 0;
|
||||
dice->rx_midi_ports[mode] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
err = snd_dice_transaction_set_rate(dice, rate);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = snd_dice_transaction_read_tx(dice, TX_NUMBER_AUDIO,
|
||||
values, sizeof(values));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
dice->tx_channels[mode] = be32_to_cpu(values[0]);
|
||||
dice->tx_midi_ports[mode] = be32_to_cpu(values[1]);
|
||||
|
||||
err = snd_dice_transaction_read_rx(dice, RX_NUMBER_AUDIO,
|
||||
values, sizeof(values));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
dice->rx_channels[mode] = be32_to_cpu(values[0]);
|
||||
dice->rx_midi_ports[mode] = be32_to_cpu(values[1]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dice_read_params(struct snd_dice *dice)
|
||||
{
|
||||
__be32 value;
|
||||
int mode, err;
|
||||
|
||||
/* some very old firmwares don't tell about their clock support */
|
||||
if (dice->clock_caps > 0) {
|
||||
err = snd_dice_transaction_read_global(dice,
|
||||
GLOBAL_CLOCK_CAPABILITIES,
|
||||
&value, 4);
|
||||
if (err < 0)
|
||||
return err;
|
||||
dice->clock_caps = be32_to_cpu(value);
|
||||
} else {
|
||||
/* this should be supported by any device */
|
||||
dice->clock_caps = CLOCK_CAP_RATE_44100 |
|
||||
CLOCK_CAP_RATE_48000 |
|
||||
CLOCK_CAP_SOURCE_ARX1 |
|
||||
CLOCK_CAP_SOURCE_INTERNAL;
|
||||
}
|
||||
|
||||
for (mode = 2; mode >= 0; --mode) {
|
||||
err = dice_read_mode_params(dice, mode);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dice_card_strings(struct snd_dice *dice)
|
||||
{
|
||||
struct snd_card *card = dice->card;
|
||||
struct fw_device *dev = fw_parent_device(dice->unit);
|
||||
char vendor[32], model[32];
|
||||
unsigned int i;
|
||||
int err;
|
||||
|
||||
strcpy(card->driver, "DICE");
|
||||
|
||||
strcpy(card->shortname, "DICE");
|
||||
BUILD_BUG_ON(NICK_NAME_SIZE < sizeof(card->shortname));
|
||||
err = snd_dice_transaction_read_global(dice, GLOBAL_NICK_NAME,
|
||||
card->shortname,
|
||||
sizeof(card->shortname));
|
||||
if (err >= 0) {
|
||||
/* DICE strings are returned in "always-wrong" endianness */
|
||||
BUILD_BUG_ON(sizeof(card->shortname) % 4 != 0);
|
||||
for (i = 0; i < sizeof(card->shortname); i += 4)
|
||||
swab32s((u32 *)&card->shortname[i]);
|
||||
card->shortname[sizeof(card->shortname) - 1] = '\0';
|
||||
}
|
||||
|
||||
strcpy(vendor, "?");
|
||||
fw_csr_string(dev->config_rom + 5, CSR_VENDOR, vendor, sizeof(vendor));
|
||||
strcpy(model, "?");
|
||||
fw_csr_string(dice->unit->directory, CSR_MODEL, model, sizeof(model));
|
||||
snprintf(card->longname, sizeof(card->longname),
|
||||
"%s %s (serial %u) at %s, S%d",
|
||||
vendor, model, dev->config_rom[4] & 0x3fffff,
|
||||
dev_name(&dice->unit->device), 100 << dev->max_speed);
|
||||
|
||||
strcpy(card->mixername, "DICE");
|
||||
}
|
||||
|
||||
static void dice_card_free(struct snd_card *card)
|
||||
{
|
||||
struct snd_dice *dice = card->private_data;
|
||||
|
||||
snd_dice_transaction_destroy(dice);
|
||||
mutex_destroy(&dice->mutex);
|
||||
}
|
||||
|
||||
static int dice_probe(struct fw_unit *unit, const struct ieee1394_device_id *id)
|
||||
{
|
||||
struct snd_card *card;
|
||||
struct snd_dice *dice;
|
||||
int err;
|
||||
|
||||
err = dice_interface_check(unit);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE,
|
||||
sizeof(*dice), &card);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
dice = card->private_data;
|
||||
dice->card = card;
|
||||
dice->unit = unit;
|
||||
card->private_free = dice_card_free;
|
||||
|
||||
spin_lock_init(&dice->lock);
|
||||
mutex_init(&dice->mutex);
|
||||
init_completion(&dice->clock_accepted);
|
||||
init_waitqueue_head(&dice->hwdep_wait);
|
||||
|
||||
err = snd_dice_transaction_init(dice);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
err = dice_read_params(dice);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
dice_card_strings(dice);
|
||||
|
||||
err = snd_dice_create_pcm(dice);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
err = snd_dice_create_hwdep(dice);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
snd_dice_create_proc(dice);
|
||||
|
||||
err = snd_dice_create_midi(dice);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
err = snd_dice_stream_init_duplex(dice);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
err = snd_card_register(card);
|
||||
if (err < 0) {
|
||||
snd_dice_stream_destroy_duplex(dice);
|
||||
goto error;
|
||||
}
|
||||
|
||||
dev_set_drvdata(&unit->device, dice);
|
||||
end:
|
||||
return err;
|
||||
error:
|
||||
snd_card_free(card);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void dice_remove(struct fw_unit *unit)
|
||||
{
|
||||
struct snd_dice *dice = dev_get_drvdata(&unit->device);
|
||||
|
||||
snd_card_disconnect(dice->card);
|
||||
|
||||
snd_dice_stream_destroy_duplex(dice);
|
||||
|
||||
snd_card_free_when_closed(dice->card);
|
||||
}
|
||||
|
||||
static void dice_bus_reset(struct fw_unit *unit)
|
||||
{
|
||||
struct snd_dice *dice = dev_get_drvdata(&unit->device);
|
||||
|
||||
/* The handler address register becomes initialized. */
|
||||
snd_dice_transaction_reinit(dice);
|
||||
|
||||
mutex_lock(&dice->mutex);
|
||||
snd_dice_stream_update_duplex(dice);
|
||||
mutex_unlock(&dice->mutex);
|
||||
}
|
||||
|
||||
#define DICE_INTERFACE 0x000001
|
||||
|
||||
static const struct ieee1394_device_id dice_id_table[] = {
|
||||
{
|
||||
.match_flags = IEEE1394_MATCH_VERSION,
|
||||
.version = DICE_INTERFACE,
|
||||
},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(ieee1394, dice_id_table);
|
||||
|
||||
static struct fw_driver dice_driver = {
|
||||
.driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = KBUILD_MODNAME,
|
||||
.bus = &fw_bus_type,
|
||||
},
|
||||
.probe = dice_probe,
|
||||
.update = dice_bus_reset,
|
||||
.remove = dice_remove,
|
||||
.id_table = dice_id_table,
|
||||
};
|
||||
|
||||
static int __init alsa_dice_init(void)
|
||||
{
|
||||
return driver_register(&dice_driver.driver);
|
||||
}
|
||||
|
||||
static void __exit alsa_dice_exit(void)
|
||||
{
|
||||
driver_unregister(&dice_driver.driver);
|
||||
}
|
||||
|
||||
module_init(alsa_dice_init);
|
||||
module_exit(alsa_dice_exit);
|
|
@ -0,0 +1,189 @@
|
|||
/*
|
||||
* dice.h - a part of driver for Dice based devices
|
||||
*
|
||||
* Copyright (c) Clemens Ladisch
|
||||
* Copyright (c) 2014 Takashi Sakamoto
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#ifndef SOUND_DICE_H_INCLUDED
|
||||
#define SOUND_DICE_H_INCLUDED
|
||||
|
||||
#include <linux/compat.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/firewire.h>
|
||||
#include <linux/firewire-constants.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/wait.h>
|
||||
|
||||
#include <sound/control.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/firewire.h>
|
||||
#include <sound/hwdep.h>
|
||||
#include <sound/info.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/rawmidi.h>
|
||||
|
||||
#include "../amdtp.h"
|
||||
#include "../iso-resources.h"
|
||||
#include "../lib.h"
|
||||
#include "dice-interface.h"
|
||||
|
||||
struct snd_dice {
|
||||
struct snd_card *card;
|
||||
struct fw_unit *unit;
|
||||
spinlock_t lock;
|
||||
struct mutex mutex;
|
||||
|
||||
/* Offsets for sub-addresses */
|
||||
unsigned int global_offset;
|
||||
unsigned int rx_offset;
|
||||
unsigned int tx_offset;
|
||||
unsigned int sync_offset;
|
||||
unsigned int rsrv_offset;
|
||||
|
||||
unsigned int clock_caps;
|
||||
unsigned int tx_channels[3];
|
||||
unsigned int rx_channels[3];
|
||||
unsigned int tx_midi_ports[3];
|
||||
unsigned int rx_midi_ports[3];
|
||||
|
||||
struct fw_address_handler notification_handler;
|
||||
int owner_generation;
|
||||
u32 notification_bits;
|
||||
|
||||
/* For uapi */
|
||||
int dev_lock_count; /* > 0 driver, < 0 userspace */
|
||||
bool dev_lock_changed;
|
||||
wait_queue_head_t hwdep_wait;
|
||||
|
||||
/* For streaming */
|
||||
struct fw_iso_resources tx_resources;
|
||||
struct fw_iso_resources rx_resources;
|
||||
struct amdtp_stream tx_stream;
|
||||
struct amdtp_stream rx_stream;
|
||||
bool global_enabled;
|
||||
struct completion clock_accepted;
|
||||
unsigned int substreams_counter;
|
||||
};
|
||||
|
||||
enum snd_dice_addr_type {
|
||||
SND_DICE_ADDR_TYPE_PRIVATE,
|
||||
SND_DICE_ADDR_TYPE_GLOBAL,
|
||||
SND_DICE_ADDR_TYPE_TX,
|
||||
SND_DICE_ADDR_TYPE_RX,
|
||||
SND_DICE_ADDR_TYPE_SYNC,
|
||||
SND_DICE_ADDR_TYPE_RSRV,
|
||||
};
|
||||
|
||||
int snd_dice_transaction_write(struct snd_dice *dice,
|
||||
enum snd_dice_addr_type type,
|
||||
unsigned int offset,
|
||||
void *buf, unsigned int len);
|
||||
int snd_dice_transaction_read(struct snd_dice *dice,
|
||||
enum snd_dice_addr_type type, unsigned int offset,
|
||||
void *buf, unsigned int len);
|
||||
|
||||
static inline int snd_dice_transaction_write_global(struct snd_dice *dice,
|
||||
unsigned int offset,
|
||||
void *buf, unsigned int len)
|
||||
{
|
||||
return snd_dice_transaction_write(dice,
|
||||
SND_DICE_ADDR_TYPE_GLOBAL, offset,
|
||||
buf, len);
|
||||
}
|
||||
static inline int snd_dice_transaction_read_global(struct snd_dice *dice,
|
||||
unsigned int offset,
|
||||
void *buf, unsigned int len)
|
||||
{
|
||||
return snd_dice_transaction_read(dice,
|
||||
SND_DICE_ADDR_TYPE_GLOBAL, offset,
|
||||
buf, len);
|
||||
}
|
||||
static inline int snd_dice_transaction_write_tx(struct snd_dice *dice,
|
||||
unsigned int offset,
|
||||
void *buf, unsigned int len)
|
||||
{
|
||||
return snd_dice_transaction_write(dice, SND_DICE_ADDR_TYPE_TX, offset,
|
||||
buf, len);
|
||||
}
|
||||
static inline int snd_dice_transaction_read_tx(struct snd_dice *dice,
|
||||
unsigned int offset,
|
||||
void *buf, unsigned int len)
|
||||
{
|
||||
return snd_dice_transaction_read(dice, SND_DICE_ADDR_TYPE_TX, offset,
|
||||
buf, len);
|
||||
}
|
||||
static inline int snd_dice_transaction_write_rx(struct snd_dice *dice,
|
||||
unsigned int offset,
|
||||
void *buf, unsigned int len)
|
||||
{
|
||||
return snd_dice_transaction_write(dice, SND_DICE_ADDR_TYPE_RX, offset,
|
||||
buf, len);
|
||||
}
|
||||
static inline int snd_dice_transaction_read_rx(struct snd_dice *dice,
|
||||
unsigned int offset,
|
||||
void *buf, unsigned int len)
|
||||
{
|
||||
return snd_dice_transaction_read(dice, SND_DICE_ADDR_TYPE_RX, offset,
|
||||
buf, len);
|
||||
}
|
||||
static inline int snd_dice_transaction_write_sync(struct snd_dice *dice,
|
||||
unsigned int offset,
|
||||
void *buf, unsigned int len)
|
||||
{
|
||||
return snd_dice_transaction_write(dice, SND_DICE_ADDR_TYPE_SYNC, offset,
|
||||
buf, len);
|
||||
}
|
||||
static inline int snd_dice_transaction_read_sync(struct snd_dice *dice,
|
||||
unsigned int offset,
|
||||
void *buf, unsigned int len)
|
||||
{
|
||||
return snd_dice_transaction_read(dice, SND_DICE_ADDR_TYPE_SYNC, offset,
|
||||
buf, len);
|
||||
}
|
||||
|
||||
int snd_dice_transaction_get_clock_source(struct snd_dice *dice,
|
||||
unsigned int *source);
|
||||
int snd_dice_transaction_set_rate(struct snd_dice *dice, unsigned int rate);
|
||||
int snd_dice_transaction_get_rate(struct snd_dice *dice, unsigned int *rate);
|
||||
int snd_dice_transaction_set_enable(struct snd_dice *dice);
|
||||
void snd_dice_transaction_clear_enable(struct snd_dice *dice);
|
||||
int snd_dice_transaction_init(struct snd_dice *dice);
|
||||
int snd_dice_transaction_reinit(struct snd_dice *dice);
|
||||
void snd_dice_transaction_destroy(struct snd_dice *dice);
|
||||
|
||||
#define SND_DICE_RATES_COUNT 7
|
||||
extern const unsigned int snd_dice_rates[SND_DICE_RATES_COUNT];
|
||||
|
||||
int snd_dice_stream_get_rate_mode(struct snd_dice *dice,
|
||||
unsigned int rate, unsigned int *mode);
|
||||
|
||||
int snd_dice_stream_start_duplex(struct snd_dice *dice, unsigned int rate);
|
||||
void snd_dice_stream_stop_duplex(struct snd_dice *dice);
|
||||
int snd_dice_stream_init_duplex(struct snd_dice *dice);
|
||||
void snd_dice_stream_destroy_duplex(struct snd_dice *dice);
|
||||
void snd_dice_stream_update_duplex(struct snd_dice *dice);
|
||||
|
||||
int snd_dice_stream_lock_try(struct snd_dice *dice);
|
||||
void snd_dice_stream_lock_release(struct snd_dice *dice);
|
||||
|
||||
int snd_dice_create_pcm(struct snd_dice *dice);
|
||||
|
||||
int snd_dice_create_hwdep(struct snd_dice *dice);
|
||||
|
||||
void snd_dice_create_proc(struct snd_dice *dice);
|
||||
|
||||
int snd_dice_create_midi(struct snd_dice *dice);
|
||||
|
||||
#endif
|
|
@ -131,14 +131,8 @@ static void isight_samples(struct isight *isight,
|
|||
|
||||
static void isight_pcm_abort(struct isight *isight)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
if (ACCESS_ONCE(isight->pcm_active)) {
|
||||
snd_pcm_stream_lock_irqsave(isight->pcm, flags);
|
||||
if (snd_pcm_running(isight->pcm))
|
||||
snd_pcm_stop(isight->pcm, SNDRV_PCM_STATE_XRUN);
|
||||
snd_pcm_stream_unlock_irqrestore(isight->pcm, flags);
|
||||
}
|
||||
if (ACCESS_ONCE(isight->pcm_active))
|
||||
snd_pcm_stop_xrun(isight->pcm);
|
||||
}
|
||||
|
||||
static void isight_dropped_samples(struct isight *isight, unsigned int total)
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
snd-oxfw-objs := oxfw-command.o oxfw-stream.o oxfw-control.o oxfw-pcm.o \
|
||||
oxfw-proc.o oxfw-midi.o oxfw-hwdep.o oxfw.o
|
||||
obj-m += snd-oxfw.o
|
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* oxfw_command.c - a part of driver for OXFW970/971 based devices
|
||||
*
|
||||
* Copyright (c) 2014 Takashi Sakamoto
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#include "oxfw.h"
|
||||
|
||||
int avc_stream_set_format(struct fw_unit *unit, enum avc_general_plug_dir dir,
|
||||
unsigned int pid, u8 *format, unsigned int len)
|
||||
{
|
||||
u8 *buf;
|
||||
int err;
|
||||
|
||||
buf = kmalloc(len + 10, GFP_KERNEL);
|
||||
if (buf == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
buf[0] = 0x00; /* CONTROL */
|
||||
buf[1] = 0xff; /* UNIT */
|
||||
buf[2] = 0xbf; /* EXTENDED STREAM FORMAT INFORMATION */
|
||||
buf[3] = 0xc0; /* SINGLE subfunction */
|
||||
buf[4] = dir; /* Plug Direction */
|
||||
buf[5] = 0x00; /* UNIT */
|
||||
buf[6] = 0x00; /* PCR (Isochronous Plug) */
|
||||
buf[7] = 0xff & pid; /* Plug ID */
|
||||
buf[8] = 0xff; /* Padding */
|
||||
buf[9] = 0xff; /* Support status in response */
|
||||
memcpy(buf + 10, format, len);
|
||||
|
||||
/* do transaction and check buf[1-8] are the same against command */
|
||||
err = fcp_avc_transaction(unit, buf, len + 10, buf, len + 10,
|
||||
BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
|
||||
BIT(6) | BIT(7) | BIT(8));
|
||||
if ((err > 0) && (err < len + 10))
|
||||
err = -EIO;
|
||||
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
|
||||
err = -ENOSYS;
|
||||
else if (buf[0] == 0x0a) /* REJECTED */
|
||||
err = -EINVAL;
|
||||
else
|
||||
err = 0;
|
||||
|
||||
kfree(buf);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int avc_stream_get_format(struct fw_unit *unit,
|
||||
enum avc_general_plug_dir dir, unsigned int pid,
|
||||
u8 *buf, unsigned int *len, unsigned int eid)
|
||||
{
|
||||
unsigned int subfunc;
|
||||
int err;
|
||||
|
||||
if (eid == 0xff)
|
||||
subfunc = 0xc0; /* SINGLE */
|
||||
else
|
||||
subfunc = 0xc1; /* LIST */
|
||||
|
||||
buf[0] = 0x01; /* STATUS */
|
||||
buf[1] = 0xff; /* UNIT */
|
||||
buf[2] = 0xbf; /* EXTENDED STREAM FORMAT INFORMATION */
|
||||
buf[3] = subfunc; /* SINGLE or LIST */
|
||||
buf[4] = dir; /* Plug Direction */
|
||||
buf[5] = 0x00; /* Unit */
|
||||
buf[6] = 0x00; /* PCR (Isochronous Plug) */
|
||||
buf[7] = 0xff & pid; /* Plug ID */
|
||||
buf[8] = 0xff; /* Padding */
|
||||
buf[9] = 0xff; /* support status in response */
|
||||
buf[10] = 0xff & eid; /* entry ID for LIST subfunction */
|
||||
buf[11] = 0xff; /* padding */
|
||||
|
||||
/* do transaction and check buf[1-7] are the same against command */
|
||||
err = fcp_avc_transaction(unit, buf, 12, buf, *len,
|
||||
BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
|
||||
BIT(6) | BIT(7));
|
||||
if ((err > 0) && (err < 10))
|
||||
err = -EIO;
|
||||
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
|
||||
err = -ENOSYS;
|
||||
else if (buf[0] == 0x0a) /* REJECTED */
|
||||
err = -EINVAL;
|
||||
else if (buf[0] == 0x0b) /* IN TRANSITION */
|
||||
err = -EAGAIN;
|
||||
/* LIST subfunction has entry ID */
|
||||
else if ((subfunc == 0xc1) && (buf[10] != eid))
|
||||
err = -EIO;
|
||||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
/* keep just stream format information */
|
||||
if (subfunc == 0xc0) {
|
||||
memmove(buf, buf + 10, err - 10);
|
||||
*len = err - 10;
|
||||
} else {
|
||||
memmove(buf, buf + 11, err - 11);
|
||||
*len = err - 11;
|
||||
}
|
||||
|
||||
err = 0;
|
||||
end:
|
||||
return err;
|
||||
}
|
||||
|
||||
int avc_general_inquiry_sig_fmt(struct fw_unit *unit, unsigned int rate,
|
||||
enum avc_general_plug_dir dir,
|
||||
unsigned short pid)
|
||||
{
|
||||
unsigned int sfc;
|
||||
u8 *buf;
|
||||
int err;
|
||||
|
||||
for (sfc = 0; sfc < CIP_SFC_COUNT; sfc++) {
|
||||
if (amdtp_rate_table[sfc] == rate)
|
||||
break;
|
||||
}
|
||||
if (sfc == CIP_SFC_COUNT)
|
||||
return -EINVAL;
|
||||
|
||||
buf = kzalloc(8, GFP_KERNEL);
|
||||
if (buf == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
buf[0] = 0x02; /* SPECIFIC INQUIRY */
|
||||
buf[1] = 0xff; /* UNIT */
|
||||
if (dir == AVC_GENERAL_PLUG_DIR_IN)
|
||||
buf[2] = 0x19; /* INPUT PLUG SIGNAL FORMAT */
|
||||
else
|
||||
buf[2] = 0x18; /* OUTPUT PLUG SIGNAL FORMAT */
|
||||
buf[3] = 0xff & pid; /* plug id */
|
||||
buf[4] = 0x90; /* EOH_1, Form_1, FMT. AM824 */
|
||||
buf[5] = 0x07 & sfc; /* FDF-hi. AM824, frequency */
|
||||
buf[6] = 0xff; /* FDF-mid. AM824, SYT hi (not used) */
|
||||
buf[7] = 0xff; /* FDF-low. AM824, SYT lo (not used) */
|
||||
|
||||
/* do transaction and check buf[1-5] are the same against command */
|
||||
err = fcp_avc_transaction(unit, buf, 8, buf, 8,
|
||||
BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5));
|
||||
if ((err > 0) && (err < 8))
|
||||
err = -EIO;
|
||||
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
|
||||
err = -ENOSYS;
|
||||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
err = 0;
|
||||
end:
|
||||
kfree(buf);
|
||||
return err;
|
||||
}
|
|
@ -0,0 +1,283 @@
|
|||
/*
|
||||
* oxfw_stream.c - a part of driver for OXFW970/971 based devices
|
||||
*
|
||||
* Copyright (c) Clemens Ladisch <clemens@ladisch.de>
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#include "oxfw.h"
|
||||
|
||||
enum control_action { CTL_READ, CTL_WRITE };
|
||||
enum control_attribute {
|
||||
CTL_MIN = 0x02,
|
||||
CTL_MAX = 0x03,
|
||||
CTL_CURRENT = 0x10,
|
||||
};
|
||||
|
||||
static int oxfw_mute_command(struct snd_oxfw *oxfw, bool *value,
|
||||
enum control_action action)
|
||||
{
|
||||
u8 *buf;
|
||||
u8 response_ok;
|
||||
int err;
|
||||
|
||||
buf = kmalloc(11, GFP_KERNEL);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
if (action == CTL_READ) {
|
||||
buf[0] = 0x01; /* AV/C, STATUS */
|
||||
response_ok = 0x0c; /* STABLE */
|
||||
} else {
|
||||
buf[0] = 0x00; /* AV/C, CONTROL */
|
||||
response_ok = 0x09; /* ACCEPTED */
|
||||
}
|
||||
buf[1] = 0x08; /* audio unit 0 */
|
||||
buf[2] = 0xb8; /* FUNCTION BLOCK */
|
||||
buf[3] = 0x81; /* function block type: feature */
|
||||
buf[4] = oxfw->device_info->mute_fb_id; /* function block ID */
|
||||
buf[5] = 0x10; /* control attribute: current */
|
||||
buf[6] = 0x02; /* selector length */
|
||||
buf[7] = 0x00; /* audio channel number */
|
||||
buf[8] = 0x01; /* control selector: mute */
|
||||
buf[9] = 0x01; /* control data length */
|
||||
if (action == CTL_READ)
|
||||
buf[10] = 0xff;
|
||||
else
|
||||
buf[10] = *value ? 0x70 : 0x60;
|
||||
|
||||
err = fcp_avc_transaction(oxfw->unit, buf, 11, buf, 11, 0x3fe);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
if (err < 11) {
|
||||
dev_err(&oxfw->unit->device, "short FCP response\n");
|
||||
err = -EIO;
|
||||
goto error;
|
||||
}
|
||||
if (buf[0] != response_ok) {
|
||||
dev_err(&oxfw->unit->device, "mute command failed\n");
|
||||
err = -EIO;
|
||||
goto error;
|
||||
}
|
||||
if (action == CTL_READ)
|
||||
*value = buf[10] == 0x70;
|
||||
|
||||
err = 0;
|
||||
|
||||
error:
|
||||
kfree(buf);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int oxfw_volume_command(struct snd_oxfw *oxfw, s16 *value,
|
||||
unsigned int channel,
|
||||
enum control_attribute attribute,
|
||||
enum control_action action)
|
||||
{
|
||||
u8 *buf;
|
||||
u8 response_ok;
|
||||
int err;
|
||||
|
||||
buf = kmalloc(12, GFP_KERNEL);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
if (action == CTL_READ) {
|
||||
buf[0] = 0x01; /* AV/C, STATUS */
|
||||
response_ok = 0x0c; /* STABLE */
|
||||
} else {
|
||||
buf[0] = 0x00; /* AV/C, CONTROL */
|
||||
response_ok = 0x09; /* ACCEPTED */
|
||||
}
|
||||
buf[1] = 0x08; /* audio unit 0 */
|
||||
buf[2] = 0xb8; /* FUNCTION BLOCK */
|
||||
buf[3] = 0x81; /* function block type: feature */
|
||||
buf[4] = oxfw->device_info->volume_fb_id; /* function block ID */
|
||||
buf[5] = attribute; /* control attribute */
|
||||
buf[6] = 0x02; /* selector length */
|
||||
buf[7] = channel; /* audio channel number */
|
||||
buf[8] = 0x02; /* control selector: volume */
|
||||
buf[9] = 0x02; /* control data length */
|
||||
if (action == CTL_READ) {
|
||||
buf[10] = 0xff;
|
||||
buf[11] = 0xff;
|
||||
} else {
|
||||
buf[10] = *value >> 8;
|
||||
buf[11] = *value;
|
||||
}
|
||||
|
||||
err = fcp_avc_transaction(oxfw->unit, buf, 12, buf, 12, 0x3fe);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
if (err < 12) {
|
||||
dev_err(&oxfw->unit->device, "short FCP response\n");
|
||||
err = -EIO;
|
||||
goto error;
|
||||
}
|
||||
if (buf[0] != response_ok) {
|
||||
dev_err(&oxfw->unit->device, "volume command failed\n");
|
||||
err = -EIO;
|
||||
goto error;
|
||||
}
|
||||
if (action == CTL_READ)
|
||||
*value = (buf[10] << 8) | buf[11];
|
||||
|
||||
err = 0;
|
||||
|
||||
error:
|
||||
kfree(buf);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int oxfw_mute_get(struct snd_kcontrol *control,
|
||||
struct snd_ctl_elem_value *value)
|
||||
{
|
||||
struct snd_oxfw *oxfw = control->private_data;
|
||||
|
||||
value->value.integer.value[0] = !oxfw->mute;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int oxfw_mute_put(struct snd_kcontrol *control,
|
||||
struct snd_ctl_elem_value *value)
|
||||
{
|
||||
struct snd_oxfw *oxfw = control->private_data;
|
||||
bool mute;
|
||||
int err;
|
||||
|
||||
mute = !value->value.integer.value[0];
|
||||
|
||||
if (mute == oxfw->mute)
|
||||
return 0;
|
||||
|
||||
err = oxfw_mute_command(oxfw, &mute, CTL_WRITE);
|
||||
if (err < 0)
|
||||
return err;
|
||||
oxfw->mute = mute;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int oxfw_volume_info(struct snd_kcontrol *control,
|
||||
struct snd_ctl_elem_info *info)
|
||||
{
|
||||
struct snd_oxfw *oxfw = control->private_data;
|
||||
|
||||
info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
||||
info->count = oxfw->device_info->mixer_channels;
|
||||
info->value.integer.min = oxfw->volume_min;
|
||||
info->value.integer.max = oxfw->volume_max;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const u8 channel_map[6] = { 0, 1, 4, 5, 2, 3 };
|
||||
|
||||
static int oxfw_volume_get(struct snd_kcontrol *control,
|
||||
struct snd_ctl_elem_value *value)
|
||||
{
|
||||
struct snd_oxfw *oxfw = control->private_data;
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < oxfw->device_info->mixer_channels; ++i)
|
||||
value->value.integer.value[channel_map[i]] = oxfw->volume[i];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int oxfw_volume_put(struct snd_kcontrol *control,
|
||||
struct snd_ctl_elem_value *value)
|
||||
{
|
||||
struct snd_oxfw *oxfw = control->private_data;
|
||||
unsigned int i, changed_channels;
|
||||
bool equal_values = true;
|
||||
s16 volume;
|
||||
int err;
|
||||
|
||||
for (i = 0; i < oxfw->device_info->mixer_channels; ++i) {
|
||||
if (value->value.integer.value[i] < oxfw->volume_min ||
|
||||
value->value.integer.value[i] > oxfw->volume_max)
|
||||
return -EINVAL;
|
||||
if (value->value.integer.value[i] !=
|
||||
value->value.integer.value[0])
|
||||
equal_values = false;
|
||||
}
|
||||
|
||||
changed_channels = 0;
|
||||
for (i = 0; i < oxfw->device_info->mixer_channels; ++i)
|
||||
if (value->value.integer.value[channel_map[i]] !=
|
||||
oxfw->volume[i])
|
||||
changed_channels |= 1 << (i + 1);
|
||||
|
||||
if (equal_values && changed_channels != 0)
|
||||
changed_channels = 1 << 0;
|
||||
|
||||
for (i = 0; i <= oxfw->device_info->mixer_channels; ++i) {
|
||||
volume = value->value.integer.value[channel_map[i ? i - 1 : 0]];
|
||||
if (changed_channels & (1 << i)) {
|
||||
err = oxfw_volume_command(oxfw, &volume, i,
|
||||
CTL_CURRENT, CTL_WRITE);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
if (i > 0)
|
||||
oxfw->volume[i - 1] = volume;
|
||||
}
|
||||
|
||||
return changed_channels != 0;
|
||||
}
|
||||
|
||||
int snd_oxfw_create_mixer(struct snd_oxfw *oxfw)
|
||||
{
|
||||
static const struct snd_kcontrol_new controls[] = {
|
||||
{
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "PCM Playback Switch",
|
||||
.info = snd_ctl_boolean_mono_info,
|
||||
.get = oxfw_mute_get,
|
||||
.put = oxfw_mute_put,
|
||||
},
|
||||
{
|
||||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||
.name = "PCM Playback Volume",
|
||||
.info = oxfw_volume_info,
|
||||
.get = oxfw_volume_get,
|
||||
.put = oxfw_volume_put,
|
||||
},
|
||||
};
|
||||
unsigned int i, first_ch;
|
||||
int err;
|
||||
|
||||
err = oxfw_volume_command(oxfw, &oxfw->volume_min,
|
||||
0, CTL_MIN, CTL_READ);
|
||||
if (err < 0)
|
||||
return err;
|
||||
err = oxfw_volume_command(oxfw, &oxfw->volume_max,
|
||||
0, CTL_MAX, CTL_READ);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = oxfw_mute_command(oxfw, &oxfw->mute, CTL_READ);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
first_ch = oxfw->device_info->mixer_channels == 1 ? 0 : 1;
|
||||
for (i = 0; i < oxfw->device_info->mixer_channels; ++i) {
|
||||
err = oxfw_volume_command(oxfw, &oxfw->volume[i],
|
||||
first_ch + i, CTL_CURRENT, CTL_READ);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(controls); ++i) {
|
||||
err = snd_ctl_add(oxfw->card,
|
||||
snd_ctl_new1(&controls[i], oxfw));
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,190 @@
|
|||
/*
|
||||
* oxfw_hwdep.c - a part of driver for OXFW970/971 based devices
|
||||
*
|
||||
* Copyright (c) 2014 Takashi Sakamoto
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This codes give three functionality.
|
||||
*
|
||||
* 1.get firewire node information
|
||||
* 2.get notification about starting/stopping stream
|
||||
* 3.lock/unlock stream
|
||||
*/
|
||||
|
||||
#include "oxfw.h"
|
||||
|
||||
static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
|
||||
loff_t *offset)
|
||||
{
|
||||
struct snd_oxfw *oxfw = hwdep->private_data;
|
||||
DEFINE_WAIT(wait);
|
||||
union snd_firewire_event event;
|
||||
|
||||
spin_lock_irq(&oxfw->lock);
|
||||
|
||||
while (!oxfw->dev_lock_changed) {
|
||||
prepare_to_wait(&oxfw->hwdep_wait, &wait, TASK_INTERRUPTIBLE);
|
||||
spin_unlock_irq(&oxfw->lock);
|
||||
schedule();
|
||||
finish_wait(&oxfw->hwdep_wait, &wait);
|
||||
if (signal_pending(current))
|
||||
return -ERESTARTSYS;
|
||||
spin_lock_irq(&oxfw->lock);
|
||||
}
|
||||
|
||||
memset(&event, 0, sizeof(event));
|
||||
if (oxfw->dev_lock_changed) {
|
||||
event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS;
|
||||
event.lock_status.status = (oxfw->dev_lock_count > 0);
|
||||
oxfw->dev_lock_changed = false;
|
||||
|
||||
count = min_t(long, count, sizeof(event.lock_status));
|
||||
}
|
||||
|
||||
spin_unlock_irq(&oxfw->lock);
|
||||
|
||||
if (copy_to_user(buf, &event, count))
|
||||
return -EFAULT;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static unsigned int hwdep_poll(struct snd_hwdep *hwdep, struct file *file,
|
||||
poll_table *wait)
|
||||
{
|
||||
struct snd_oxfw *oxfw = hwdep->private_data;
|
||||
unsigned int events;
|
||||
|
||||
poll_wait(file, &oxfw->hwdep_wait, wait);
|
||||
|
||||
spin_lock_irq(&oxfw->lock);
|
||||
if (oxfw->dev_lock_changed)
|
||||
events = POLLIN | POLLRDNORM;
|
||||
else
|
||||
events = 0;
|
||||
spin_unlock_irq(&oxfw->lock);
|
||||
|
||||
return events;
|
||||
}
|
||||
|
||||
static int hwdep_get_info(struct snd_oxfw *oxfw, void __user *arg)
|
||||
{
|
||||
struct fw_device *dev = fw_parent_device(oxfw->unit);
|
||||
struct snd_firewire_get_info info;
|
||||
|
||||
memset(&info, 0, sizeof(info));
|
||||
info.type = SNDRV_FIREWIRE_TYPE_OXFW;
|
||||
info.card = dev->card->index;
|
||||
*(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]);
|
||||
*(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]);
|
||||
strlcpy(info.device_name, dev_name(&dev->device),
|
||||
sizeof(info.device_name));
|
||||
|
||||
if (copy_to_user(arg, &info, sizeof(info)))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hwdep_lock(struct snd_oxfw *oxfw)
|
||||
{
|
||||
int err;
|
||||
|
||||
spin_lock_irq(&oxfw->lock);
|
||||
|
||||
if (oxfw->dev_lock_count == 0) {
|
||||
oxfw->dev_lock_count = -1;
|
||||
err = 0;
|
||||
} else {
|
||||
err = -EBUSY;
|
||||
}
|
||||
|
||||
spin_unlock_irq(&oxfw->lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int hwdep_unlock(struct snd_oxfw *oxfw)
|
||||
{
|
||||
int err;
|
||||
|
||||
spin_lock_irq(&oxfw->lock);
|
||||
|
||||
if (oxfw->dev_lock_count == -1) {
|
||||
oxfw->dev_lock_count = 0;
|
||||
err = 0;
|
||||
} else {
|
||||
err = -EBADFD;
|
||||
}
|
||||
|
||||
spin_unlock_irq(&oxfw->lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int hwdep_release(struct snd_hwdep *hwdep, struct file *file)
|
||||
{
|
||||
struct snd_oxfw *oxfw = hwdep->private_data;
|
||||
|
||||
spin_lock_irq(&oxfw->lock);
|
||||
if (oxfw->dev_lock_count == -1)
|
||||
oxfw->dev_lock_count = 0;
|
||||
spin_unlock_irq(&oxfw->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct snd_oxfw *oxfw = hwdep->private_data;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_FIREWIRE_IOCTL_GET_INFO:
|
||||
return hwdep_get_info(oxfw, (void __user *)arg);
|
||||
case SNDRV_FIREWIRE_IOCTL_LOCK:
|
||||
return hwdep_lock(oxfw);
|
||||
case SNDRV_FIREWIRE_IOCTL_UNLOCK:
|
||||
return hwdep_unlock(oxfw);
|
||||
default:
|
||||
return -ENOIOCTLCMD;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
static int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
return hwdep_ioctl(hwdep, file, cmd,
|
||||
(unsigned long)compat_ptr(arg));
|
||||
}
|
||||
#else
|
||||
#define hwdep_compat_ioctl NULL
|
||||
#endif
|
||||
|
||||
int snd_oxfw_create_hwdep(struct snd_oxfw *oxfw)
|
||||
{
|
||||
static const struct snd_hwdep_ops hwdep_ops = {
|
||||
.read = hwdep_read,
|
||||
.release = hwdep_release,
|
||||
.poll = hwdep_poll,
|
||||
.ioctl = hwdep_ioctl,
|
||||
.ioctl_compat = hwdep_compat_ioctl,
|
||||
};
|
||||
struct snd_hwdep *hwdep;
|
||||
int err;
|
||||
|
||||
err = snd_hwdep_new(oxfw->card, oxfw->card->driver, 0, &hwdep);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
strcpy(hwdep->name, oxfw->card->driver);
|
||||
hwdep->iface = SNDRV_HWDEP_IFACE_FW_OXFW;
|
||||
hwdep->ops = hwdep_ops;
|
||||
hwdep->private_data = oxfw;
|
||||
hwdep->exclusive = true;
|
||||
end:
|
||||
return err;
|
||||
}
|
|
@ -0,0 +1,207 @@
|
|||
/*
|
||||
* oxfw_midi.c - a part of driver for OXFW970/971 based devices
|
||||
*
|
||||
* Copyright (c) 2014 Takashi Sakamoto
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#include "oxfw.h"
|
||||
|
||||
static int midi_capture_open(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
struct snd_oxfw *oxfw = substream->rmidi->private_data;
|
||||
int err;
|
||||
|
||||
err = snd_oxfw_stream_lock_try(oxfw);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
mutex_lock(&oxfw->mutex);
|
||||
|
||||
oxfw->capture_substreams++;
|
||||
err = snd_oxfw_stream_start_simplex(oxfw, &oxfw->tx_stream, 0, 0);
|
||||
|
||||
mutex_unlock(&oxfw->mutex);
|
||||
|
||||
if (err < 0)
|
||||
snd_oxfw_stream_lock_release(oxfw);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int midi_playback_open(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
struct snd_oxfw *oxfw = substream->rmidi->private_data;
|
||||
int err;
|
||||
|
||||
err = snd_oxfw_stream_lock_try(oxfw);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
mutex_lock(&oxfw->mutex);
|
||||
|
||||
oxfw->playback_substreams++;
|
||||
err = snd_oxfw_stream_start_simplex(oxfw, &oxfw->rx_stream, 0, 0);
|
||||
|
||||
mutex_unlock(&oxfw->mutex);
|
||||
|
||||
if (err < 0)
|
||||
snd_oxfw_stream_lock_release(oxfw);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int midi_capture_close(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
struct snd_oxfw *oxfw = substream->rmidi->private_data;
|
||||
|
||||
mutex_lock(&oxfw->mutex);
|
||||
|
||||
oxfw->capture_substreams--;
|
||||
snd_oxfw_stream_stop_simplex(oxfw, &oxfw->tx_stream);
|
||||
|
||||
mutex_unlock(&oxfw->mutex);
|
||||
|
||||
snd_oxfw_stream_lock_release(oxfw);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int midi_playback_close(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
struct snd_oxfw *oxfw = substream->rmidi->private_data;
|
||||
|
||||
mutex_lock(&oxfw->mutex);
|
||||
|
||||
oxfw->playback_substreams--;
|
||||
snd_oxfw_stream_stop_simplex(oxfw, &oxfw->rx_stream);
|
||||
|
||||
mutex_unlock(&oxfw->mutex);
|
||||
|
||||
snd_oxfw_stream_lock_release(oxfw);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up)
|
||||
{
|
||||
struct snd_oxfw *oxfw = substrm->rmidi->private_data;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&oxfw->lock, flags);
|
||||
|
||||
if (up)
|
||||
amdtp_stream_midi_trigger(&oxfw->tx_stream,
|
||||
substrm->number, substrm);
|
||||
else
|
||||
amdtp_stream_midi_trigger(&oxfw->tx_stream,
|
||||
substrm->number, NULL);
|
||||
|
||||
spin_unlock_irqrestore(&oxfw->lock, flags);
|
||||
}
|
||||
|
||||
static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up)
|
||||
{
|
||||
struct snd_oxfw *oxfw = substrm->rmidi->private_data;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&oxfw->lock, flags);
|
||||
|
||||
if (up)
|
||||
amdtp_stream_midi_trigger(&oxfw->rx_stream,
|
||||
substrm->number, substrm);
|
||||
else
|
||||
amdtp_stream_midi_trigger(&oxfw->rx_stream,
|
||||
substrm->number, NULL);
|
||||
|
||||
spin_unlock_irqrestore(&oxfw->lock, flags);
|
||||
}
|
||||
|
||||
static struct snd_rawmidi_ops midi_capture_ops = {
|
||||
.open = midi_capture_open,
|
||||
.close = midi_capture_close,
|
||||
.trigger = midi_capture_trigger,
|
||||
};
|
||||
|
||||
static struct snd_rawmidi_ops midi_playback_ops = {
|
||||
.open = midi_playback_open,
|
||||
.close = midi_playback_close,
|
||||
.trigger = midi_playback_trigger,
|
||||
};
|
||||
|
||||
static void set_midi_substream_names(struct snd_oxfw *oxfw,
|
||||
struct snd_rawmidi_str *str)
|
||||
{
|
||||
struct snd_rawmidi_substream *subs;
|
||||
|
||||
list_for_each_entry(subs, &str->substreams, list) {
|
||||
snprintf(subs->name, sizeof(subs->name),
|
||||
"%s MIDI %d",
|
||||
oxfw->card->shortname, subs->number + 1);
|
||||
}
|
||||
}
|
||||
|
||||
int snd_oxfw_create_midi(struct snd_oxfw *oxfw)
|
||||
{
|
||||
struct snd_oxfw_stream_formation formation;
|
||||
struct snd_rawmidi *rmidi;
|
||||
struct snd_rawmidi_str *str;
|
||||
u8 *format;
|
||||
int i, err;
|
||||
|
||||
/* If its stream has MIDI conformant data channel, add one MIDI port */
|
||||
for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) {
|
||||
format = oxfw->tx_stream_formats[i];
|
||||
if (format != NULL) {
|
||||
err = snd_oxfw_stream_parse_format(format, &formation);
|
||||
if (err >= 0 && formation.midi > 0)
|
||||
oxfw->midi_input_ports = 1;
|
||||
}
|
||||
|
||||
format = oxfw->rx_stream_formats[i];
|
||||
if (format != NULL) {
|
||||
err = snd_oxfw_stream_parse_format(format, &formation);
|
||||
if (err >= 0 && formation.midi > 0)
|
||||
oxfw->midi_output_ports = 1;
|
||||
}
|
||||
}
|
||||
if ((oxfw->midi_input_ports == 0) && (oxfw->midi_output_ports == 0))
|
||||
return 0;
|
||||
|
||||
/* create midi ports */
|
||||
err = snd_rawmidi_new(oxfw->card, oxfw->card->driver, 0,
|
||||
oxfw->midi_output_ports, oxfw->midi_input_ports,
|
||||
&rmidi);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
snprintf(rmidi->name, sizeof(rmidi->name),
|
||||
"%s MIDI", oxfw->card->shortname);
|
||||
rmidi->private_data = oxfw;
|
||||
|
||||
if (oxfw->midi_input_ports > 0) {
|
||||
rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
|
||||
|
||||
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
|
||||
&midi_capture_ops);
|
||||
|
||||
str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT];
|
||||
|
||||
set_midi_substream_names(oxfw, str);
|
||||
}
|
||||
|
||||
if (oxfw->midi_output_ports > 0) {
|
||||
rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;
|
||||
|
||||
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
|
||||
&midi_playback_ops);
|
||||
|
||||
str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT];
|
||||
|
||||
set_midi_substream_names(oxfw, str);
|
||||
}
|
||||
|
||||
if ((oxfw->midi_output_ports > 0) && (oxfw->midi_input_ports > 0))
|
||||
rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX;
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,424 @@
|
|||
/*
|
||||
* oxfw_pcm.c - a part of driver for OXFW970/971 based devices
|
||||
*
|
||||
* Copyright (c) Clemens Ladisch <clemens@ladisch.de>
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#include "oxfw.h"
|
||||
|
||||
static int hw_rule_rate(struct snd_pcm_hw_params *params,
|
||||
struct snd_pcm_hw_rule *rule)
|
||||
{
|
||||
u8 **formats = rule->private;
|
||||
struct snd_interval *r =
|
||||
hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
|
||||
const struct snd_interval *c =
|
||||
hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS);
|
||||
struct snd_interval t = {
|
||||
.min = UINT_MAX, .max = 0, .integer = 1
|
||||
};
|
||||
struct snd_oxfw_stream_formation formation;
|
||||
unsigned int i, err;
|
||||
|
||||
for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) {
|
||||
if (formats[i] == NULL)
|
||||
continue;
|
||||
|
||||
err = snd_oxfw_stream_parse_format(formats[i], &formation);
|
||||
if (err < 0)
|
||||
continue;
|
||||
if (!snd_interval_test(c, formation.pcm))
|
||||
continue;
|
||||
|
||||
t.min = min(t.min, formation.rate);
|
||||
t.max = max(t.max, formation.rate);
|
||||
|
||||
}
|
||||
return snd_interval_refine(r, &t);
|
||||
}
|
||||
|
||||
static int hw_rule_channels(struct snd_pcm_hw_params *params,
|
||||
struct snd_pcm_hw_rule *rule)
|
||||
{
|
||||
u8 **formats = rule->private;
|
||||
struct snd_interval *c =
|
||||
hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
|
||||
const struct snd_interval *r =
|
||||
hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE);
|
||||
struct snd_oxfw_stream_formation formation;
|
||||
unsigned int i, j, err;
|
||||
unsigned int count, list[SND_OXFW_STREAM_FORMAT_ENTRIES] = {0};
|
||||
|
||||
count = 0;
|
||||
for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) {
|
||||
if (formats[i] == NULL)
|
||||
break;
|
||||
|
||||
err = snd_oxfw_stream_parse_format(formats[i], &formation);
|
||||
if (err < 0)
|
||||
continue;
|
||||
if (!snd_interval_test(r, formation.rate))
|
||||
continue;
|
||||
if (list[count] == formation.pcm)
|
||||
continue;
|
||||
|
||||
for (j = 0; j < ARRAY_SIZE(list); j++) {
|
||||
if (list[j] == formation.pcm)
|
||||
break;
|
||||
}
|
||||
if (j == ARRAY_SIZE(list)) {
|
||||
list[count] = formation.pcm;
|
||||
if (++count == ARRAY_SIZE(list))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return snd_interval_list(c, count, list, 0);
|
||||
}
|
||||
|
||||
static void limit_channels_and_rates(struct snd_pcm_hardware *hw, u8 **formats)
|
||||
{
|
||||
struct snd_oxfw_stream_formation formation;
|
||||
unsigned int i, err;
|
||||
|
||||
hw->channels_min = UINT_MAX;
|
||||
hw->channels_max = 0;
|
||||
|
||||
hw->rate_min = UINT_MAX;
|
||||
hw->rate_max = 0;
|
||||
hw->rates = 0;
|
||||
|
||||
for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) {
|
||||
if (formats[i] == NULL)
|
||||
break;
|
||||
|
||||
err = snd_oxfw_stream_parse_format(formats[i], &formation);
|
||||
if (err < 0)
|
||||
continue;
|
||||
|
||||
hw->channels_min = min(hw->channels_min, formation.pcm);
|
||||
hw->channels_max = max(hw->channels_max, formation.pcm);
|
||||
|
||||
hw->rate_min = min(hw->rate_min, formation.rate);
|
||||
hw->rate_max = max(hw->rate_max, formation.rate);
|
||||
hw->rates |= snd_pcm_rate_to_rate_bit(formation.rate);
|
||||
}
|
||||
}
|
||||
|
||||
static void limit_period_and_buffer(struct snd_pcm_hardware *hw)
|
||||
{
|
||||
hw->periods_min = 2; /* SNDRV_PCM_INFO_BATCH */
|
||||
hw->periods_max = UINT_MAX;
|
||||
|
||||
hw->period_bytes_min = 4 * hw->channels_max; /* bytes for a frame */
|
||||
|
||||
/* Just to prevent from allocating much pages. */
|
||||
hw->period_bytes_max = hw->period_bytes_min * 2048;
|
||||
hw->buffer_bytes_max = hw->period_bytes_max * hw->periods_min;
|
||||
}
|
||||
|
||||
static int init_hw_params(struct snd_oxfw *oxfw,
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
u8 **formats;
|
||||
struct amdtp_stream *stream;
|
||||
int err;
|
||||
|
||||
runtime->hw.info = SNDRV_PCM_INFO_BATCH |
|
||||
SNDRV_PCM_INFO_BLOCK_TRANSFER |
|
||||
SNDRV_PCM_INFO_INTERLEAVED |
|
||||
SNDRV_PCM_INFO_JOINT_DUPLEX |
|
||||
SNDRV_PCM_INFO_MMAP |
|
||||
SNDRV_PCM_INFO_MMAP_VALID;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
|
||||
runtime->hw.formats = AMDTP_IN_PCM_FORMAT_BITS;
|
||||
stream = &oxfw->tx_stream;
|
||||
formats = oxfw->tx_stream_formats;
|
||||
} else {
|
||||
runtime->hw.formats = AMDTP_OUT_PCM_FORMAT_BITS;
|
||||
stream = &oxfw->rx_stream;
|
||||
formats = oxfw->rx_stream_formats;
|
||||
}
|
||||
|
||||
limit_channels_and_rates(&runtime->hw, formats);
|
||||
limit_period_and_buffer(&runtime->hw);
|
||||
|
||||
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
|
||||
hw_rule_channels, formats,
|
||||
SNDRV_PCM_HW_PARAM_RATE, -1);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
|
||||
hw_rule_rate, formats,
|
||||
SNDRV_PCM_HW_PARAM_CHANNELS, -1);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
err = amdtp_stream_add_pcm_hw_constraints(stream, runtime);
|
||||
end:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int limit_to_current_params(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_oxfw *oxfw = substream->private_data;
|
||||
struct snd_oxfw_stream_formation formation;
|
||||
enum avc_general_plug_dir dir;
|
||||
int err;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
|
||||
dir = AVC_GENERAL_PLUG_DIR_OUT;
|
||||
else
|
||||
dir = AVC_GENERAL_PLUG_DIR_IN;
|
||||
|
||||
err = snd_oxfw_stream_get_current_formation(oxfw, dir, &formation);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
substream->runtime->hw.channels_min = formation.pcm;
|
||||
substream->runtime->hw.channels_max = formation.pcm;
|
||||
substream->runtime->hw.rate_min = formation.rate;
|
||||
substream->runtime->hw.rate_max = formation.rate;
|
||||
end:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int pcm_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_oxfw *oxfw = substream->private_data;
|
||||
int err;
|
||||
|
||||
err = snd_oxfw_stream_lock_try(oxfw);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
err = init_hw_params(oxfw, substream);
|
||||
if (err < 0)
|
||||
goto err_locked;
|
||||
|
||||
/*
|
||||
* When any PCM streams are already running, the available sampling
|
||||
* rate is limited at current value.
|
||||
*/
|
||||
if (amdtp_stream_pcm_running(&oxfw->tx_stream) ||
|
||||
amdtp_stream_pcm_running(&oxfw->rx_stream)) {
|
||||
err = limit_to_current_params(substream);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
}
|
||||
|
||||
snd_pcm_set_sync(substream);
|
||||
end:
|
||||
return err;
|
||||
err_locked:
|
||||
snd_oxfw_stream_lock_release(oxfw);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int pcm_close(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_oxfw *oxfw = substream->private_data;
|
||||
|
||||
snd_oxfw_stream_lock_release(oxfw);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pcm_capture_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *hw_params)
|
||||
{
|
||||
struct snd_oxfw *oxfw = substream->private_data;
|
||||
|
||||
|
||||
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
|
||||
mutex_lock(&oxfw->mutex);
|
||||
oxfw->capture_substreams++;
|
||||
mutex_unlock(&oxfw->mutex);
|
||||
}
|
||||
|
||||
amdtp_stream_set_pcm_format(&oxfw->tx_stream, params_format(hw_params));
|
||||
|
||||
return snd_pcm_lib_alloc_vmalloc_buffer(substream,
|
||||
params_buffer_bytes(hw_params));
|
||||
}
|
||||
static int pcm_playback_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *hw_params)
|
||||
{
|
||||
struct snd_oxfw *oxfw = substream->private_data;
|
||||
|
||||
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
|
||||
mutex_lock(&oxfw->mutex);
|
||||
oxfw->playback_substreams++;
|
||||
mutex_unlock(&oxfw->mutex);
|
||||
}
|
||||
|
||||
amdtp_stream_set_pcm_format(&oxfw->rx_stream, params_format(hw_params));
|
||||
|
||||
return snd_pcm_lib_alloc_vmalloc_buffer(substream,
|
||||
params_buffer_bytes(hw_params));
|
||||
}
|
||||
|
||||
static int pcm_capture_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_oxfw *oxfw = substream->private_data;
|
||||
|
||||
mutex_lock(&oxfw->mutex);
|
||||
|
||||
if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
|
||||
oxfw->capture_substreams--;
|
||||
|
||||
snd_oxfw_stream_stop_simplex(oxfw, &oxfw->tx_stream);
|
||||
|
||||
mutex_unlock(&oxfw->mutex);
|
||||
|
||||
return snd_pcm_lib_free_vmalloc_buffer(substream);
|
||||
}
|
||||
static int pcm_playback_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_oxfw *oxfw = substream->private_data;
|
||||
|
||||
mutex_lock(&oxfw->mutex);
|
||||
|
||||
if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
|
||||
oxfw->playback_substreams--;
|
||||
|
||||
snd_oxfw_stream_stop_simplex(oxfw, &oxfw->rx_stream);
|
||||
|
||||
mutex_unlock(&oxfw->mutex);
|
||||
|
||||
return snd_pcm_lib_free_vmalloc_buffer(substream);
|
||||
}
|
||||
|
||||
static int pcm_capture_prepare(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_oxfw *oxfw = substream->private_data;
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
int err;
|
||||
|
||||
mutex_lock(&oxfw->mutex);
|
||||
err = snd_oxfw_stream_start_simplex(oxfw, &oxfw->tx_stream,
|
||||
runtime->rate, runtime->channels);
|
||||
mutex_unlock(&oxfw->mutex);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
amdtp_stream_pcm_prepare(&oxfw->tx_stream);
|
||||
end:
|
||||
return err;
|
||||
}
|
||||
static int pcm_playback_prepare(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_oxfw *oxfw = substream->private_data;
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
int err;
|
||||
|
||||
mutex_lock(&oxfw->mutex);
|
||||
err = snd_oxfw_stream_start_simplex(oxfw, &oxfw->rx_stream,
|
||||
runtime->rate, runtime->channels);
|
||||
mutex_unlock(&oxfw->mutex);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
amdtp_stream_pcm_prepare(&oxfw->rx_stream);
|
||||
end:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd)
|
||||
{
|
||||
struct snd_oxfw *oxfw = substream->private_data;
|
||||
struct snd_pcm_substream *pcm;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
pcm = substream;
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
pcm = NULL;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
amdtp_stream_pcm_trigger(&oxfw->tx_stream, pcm);
|
||||
return 0;
|
||||
}
|
||||
static int pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd)
|
||||
{
|
||||
struct snd_oxfw *oxfw = substream->private_data;
|
||||
struct snd_pcm_substream *pcm;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
pcm = substream;
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
pcm = NULL;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
amdtp_stream_pcm_trigger(&oxfw->rx_stream, pcm);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstm)
|
||||
{
|
||||
struct snd_oxfw *oxfw = sbstm->private_data;
|
||||
|
||||
return amdtp_stream_pcm_pointer(&oxfw->tx_stream);
|
||||
}
|
||||
static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstm)
|
||||
{
|
||||
struct snd_oxfw *oxfw = sbstm->private_data;
|
||||
|
||||
return amdtp_stream_pcm_pointer(&oxfw->rx_stream);
|
||||
}
|
||||
|
||||
int snd_oxfw_create_pcm(struct snd_oxfw *oxfw)
|
||||
{
|
||||
static struct snd_pcm_ops capture_ops = {
|
||||
.open = pcm_open,
|
||||
.close = pcm_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = pcm_capture_hw_params,
|
||||
.hw_free = pcm_capture_hw_free,
|
||||
.prepare = pcm_capture_prepare,
|
||||
.trigger = pcm_capture_trigger,
|
||||
.pointer = pcm_capture_pointer,
|
||||
.page = snd_pcm_lib_get_vmalloc_page,
|
||||
.mmap = snd_pcm_lib_mmap_vmalloc,
|
||||
};
|
||||
static struct snd_pcm_ops playback_ops = {
|
||||
.open = pcm_open,
|
||||
.close = pcm_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = pcm_playback_hw_params,
|
||||
.hw_free = pcm_playback_hw_free,
|
||||
.prepare = pcm_playback_prepare,
|
||||
.trigger = pcm_playback_trigger,
|
||||
.pointer = pcm_playback_pointer,
|
||||
.page = snd_pcm_lib_get_vmalloc_page,
|
||||
.mmap = snd_pcm_lib_mmap_vmalloc,
|
||||
};
|
||||
struct snd_pcm *pcm;
|
||||
unsigned int cap = 0;
|
||||
int err;
|
||||
|
||||
if (oxfw->has_output)
|
||||
cap = 1;
|
||||
|
||||
err = snd_pcm_new(oxfw->card, oxfw->card->driver, 0, 1, cap, &pcm);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
pcm->private_data = oxfw;
|
||||
strcpy(pcm->name, oxfw->card->shortname);
|
||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops);
|
||||
if (cap > 0)
|
||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_ops);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* oxfw_proc.c - a part of driver for OXFW970/971 based devices
|
||||
*
|
||||
* Copyright (c) 2014 Takashi Sakamoto
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#include "./oxfw.h"
|
||||
|
||||
static void proc_read_formation(struct snd_info_entry *entry,
|
||||
struct snd_info_buffer *buffer)
|
||||
{
|
||||
struct snd_oxfw *oxfw = entry->private_data;
|
||||
struct snd_oxfw_stream_formation formation, curr;
|
||||
u8 *format;
|
||||
char flag;
|
||||
unsigned int i, err;
|
||||
|
||||
/* Show input. */
|
||||
err = snd_oxfw_stream_get_current_formation(oxfw,
|
||||
AVC_GENERAL_PLUG_DIR_IN,
|
||||
&curr);
|
||||
if (err < 0)
|
||||
return;
|
||||
|
||||
snd_iprintf(buffer, "Input Stream to device:\n");
|
||||
snd_iprintf(buffer, "\tRate\tPCM\tMIDI\n");
|
||||
for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) {
|
||||
format = oxfw->rx_stream_formats[i];
|
||||
if (format == NULL)
|
||||
continue;
|
||||
|
||||
err = snd_oxfw_stream_parse_format(format, &formation);
|
||||
if (err < 0)
|
||||
continue;
|
||||
|
||||
if (memcmp(&formation, &curr, sizeof(curr)) == 0)
|
||||
flag = '*';
|
||||
else
|
||||
flag = ' ';
|
||||
|
||||
snd_iprintf(buffer, "%c\t%d\t%d\t%d\n", flag,
|
||||
formation.rate, formation.pcm, formation.midi);
|
||||
}
|
||||
|
||||
if (!oxfw->has_output)
|
||||
return;
|
||||
|
||||
/* Show output. */
|
||||
err = snd_oxfw_stream_get_current_formation(oxfw,
|
||||
AVC_GENERAL_PLUG_DIR_OUT,
|
||||
&curr);
|
||||
if (err < 0)
|
||||
return;
|
||||
|
||||
snd_iprintf(buffer, "Output Stream from device:\n");
|
||||
snd_iprintf(buffer, "\tRate\tPCM\tMIDI\n");
|
||||
for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) {
|
||||
format = oxfw->tx_stream_formats[i];
|
||||
if (format == NULL)
|
||||
continue;
|
||||
|
||||
err = snd_oxfw_stream_parse_format(format, &formation);
|
||||
if (err < 0)
|
||||
continue;
|
||||
|
||||
if (memcmp(&formation, &curr, sizeof(curr)) == 0)
|
||||
flag = '*';
|
||||
else
|
||||
flag = ' ';
|
||||
|
||||
snd_iprintf(buffer, "%c\t%d\t%d\t%d\n", flag,
|
||||
formation.rate, formation.pcm, formation.midi);
|
||||
}
|
||||
}
|
||||
|
||||
static void add_node(struct snd_oxfw *oxfw, struct snd_info_entry *root,
|
||||
const char *name,
|
||||
void (*op)(struct snd_info_entry *e,
|
||||
struct snd_info_buffer *b))
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
void snd_oxfw_proc_init(struct snd_oxfw *oxfw)
|
||||
{
|
||||
struct snd_info_entry *root;
|
||||
|
||||
/*
|
||||
* All nodes are automatically removed at snd_card_disconnect(),
|
||||
* by following to link list.
|
||||
*/
|
||||
root = snd_info_create_card_entry(oxfw->card, "firewire",
|
||||
oxfw->card->proc_root);
|
||||
if (root == NULL)
|
||||
return;
|
||||
root->mode = S_IFDIR | S_IRUGO | S_IXUGO;
|
||||
if (snd_info_register(root) < 0) {
|
||||
snd_info_free_entry(root);
|
||||
return;
|
||||
}
|
||||
|
||||
add_node(oxfw, root, "formation", proc_read_formation);
|
||||
}
|
|
@ -0,0 +1,685 @@
|
|||
/*
|
||||
* oxfw_stream.c - a part of driver for OXFW970/971 based devices
|
||||
*
|
||||
* Copyright (c) 2014 Takashi Sakamoto
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#include "oxfw.h"
|
||||
#include <linux/delay.h>
|
||||
|
||||
#define AVC_GENERIC_FRAME_MAXIMUM_BYTES 512
|
||||
#define CALLBACK_TIMEOUT 200
|
||||
|
||||
/*
|
||||
* According to datasheet of Oxford Semiconductor:
|
||||
* OXFW970: 32.0/44.1/48.0/96.0 Khz, 8 audio channels I/O
|
||||
* OXFW971: 32.0/44.1/48.0/88.2/96.0/192.0 kHz, 16 audio channels I/O, MIDI I/O
|
||||
*/
|
||||
static const unsigned int oxfw_rate_table[] = {
|
||||
[0] = 32000,
|
||||
[1] = 44100,
|
||||
[2] = 48000,
|
||||
[3] = 88200,
|
||||
[4] = 96000,
|
||||
[5] = 192000,
|
||||
};
|
||||
|
||||
/*
|
||||
* See Table 5.7 – Sampling frequency for Multi-bit Audio
|
||||
* in AV/C Stream Format Information Specification 1.1 (Apr 2005, 1394TA)
|
||||
*/
|
||||
static const unsigned int avc_stream_rate_table[] = {
|
||||
[0] = 0x02,
|
||||
[1] = 0x03,
|
||||
[2] = 0x04,
|
||||
[3] = 0x0a,
|
||||
[4] = 0x05,
|
||||
[5] = 0x07,
|
||||
};
|
||||
|
||||
static int set_rate(struct snd_oxfw *oxfw, unsigned int rate)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = avc_general_set_sig_fmt(oxfw->unit, rate,
|
||||
AVC_GENERAL_PLUG_DIR_IN, 0);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
if (oxfw->has_output)
|
||||
err = avc_general_set_sig_fmt(oxfw->unit, rate,
|
||||
AVC_GENERAL_PLUG_DIR_OUT, 0);
|
||||
end:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int set_stream_format(struct snd_oxfw *oxfw, struct amdtp_stream *s,
|
||||
unsigned int rate, unsigned int pcm_channels)
|
||||
{
|
||||
u8 **formats;
|
||||
struct snd_oxfw_stream_formation formation;
|
||||
enum avc_general_plug_dir dir;
|
||||
unsigned int i, err, len;
|
||||
|
||||
if (s == &oxfw->tx_stream) {
|
||||
formats = oxfw->tx_stream_formats;
|
||||
dir = AVC_GENERAL_PLUG_DIR_OUT;
|
||||
} else {
|
||||
formats = oxfw->rx_stream_formats;
|
||||
dir = AVC_GENERAL_PLUG_DIR_IN;
|
||||
}
|
||||
|
||||
/* Seek stream format for requirements. */
|
||||
for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) {
|
||||
err = snd_oxfw_stream_parse_format(formats[i], &formation);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if ((formation.rate == rate) && (formation.pcm == pcm_channels))
|
||||
break;
|
||||
}
|
||||
if (i == SND_OXFW_STREAM_FORMAT_ENTRIES)
|
||||
return -EINVAL;
|
||||
|
||||
/* If assumed, just change rate. */
|
||||
if (oxfw->assumed)
|
||||
return set_rate(oxfw, rate);
|
||||
|
||||
/* Calculate format length. */
|
||||
len = 5 + formats[i][4] * 2;
|
||||
|
||||
err = avc_stream_set_format(oxfw->unit, dir, 0, formats[i], len);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* Some requests just after changing format causes freezing. */
|
||||
msleep(100);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void stop_stream(struct snd_oxfw *oxfw, struct amdtp_stream *stream)
|
||||
{
|
||||
amdtp_stream_pcm_abort(stream);
|
||||
amdtp_stream_stop(stream);
|
||||
|
||||
if (stream == &oxfw->tx_stream)
|
||||
cmp_connection_break(&oxfw->out_conn);
|
||||
else
|
||||
cmp_connection_break(&oxfw->in_conn);
|
||||
}
|
||||
|
||||
static int start_stream(struct snd_oxfw *oxfw, struct amdtp_stream *stream,
|
||||
unsigned int rate, unsigned int pcm_channels)
|
||||
{
|
||||
u8 **formats;
|
||||
struct cmp_connection *conn;
|
||||
struct snd_oxfw_stream_formation formation;
|
||||
unsigned int i, midi_ports;
|
||||
int err;
|
||||
|
||||
if (stream == &oxfw->rx_stream) {
|
||||
formats = oxfw->rx_stream_formats;
|
||||
conn = &oxfw->in_conn;
|
||||
} else {
|
||||
formats = oxfw->tx_stream_formats;
|
||||
conn = &oxfw->out_conn;
|
||||
}
|
||||
|
||||
/* Get stream format */
|
||||
for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) {
|
||||
if (formats[i] == NULL)
|
||||
break;
|
||||
|
||||
err = snd_oxfw_stream_parse_format(formats[i], &formation);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
if (rate != formation.rate)
|
||||
continue;
|
||||
if (pcm_channels == 0 || pcm_channels == formation.pcm)
|
||||
break;
|
||||
}
|
||||
if (i == SND_OXFW_STREAM_FORMAT_ENTRIES) {
|
||||
err = -EINVAL;
|
||||
goto end;
|
||||
}
|
||||
|
||||
pcm_channels = formation.pcm;
|
||||
midi_ports = DIV_ROUND_UP(formation.midi, 8);
|
||||
|
||||
/* The stream should have one pcm channels at least */
|
||||
if (pcm_channels == 0) {
|
||||
err = -EINVAL;
|
||||
goto end;
|
||||
}
|
||||
amdtp_stream_set_parameters(stream, rate, pcm_channels, midi_ports);
|
||||
|
||||
err = cmp_connection_establish(conn,
|
||||
amdtp_stream_get_max_payload(stream));
|
||||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
err = amdtp_stream_start(stream,
|
||||
conn->resources.channel,
|
||||
conn->speed);
|
||||
if (err < 0) {
|
||||
cmp_connection_break(conn);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Wait first packet */
|
||||
err = amdtp_stream_wait_callback(stream, CALLBACK_TIMEOUT);
|
||||
if (err < 0)
|
||||
stop_stream(oxfw, stream);
|
||||
end:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int check_connection_used_by_others(struct snd_oxfw *oxfw,
|
||||
struct amdtp_stream *stream)
|
||||
{
|
||||
struct cmp_connection *conn;
|
||||
bool used;
|
||||
int err;
|
||||
|
||||
if (stream == &oxfw->tx_stream)
|
||||
conn = &oxfw->out_conn;
|
||||
else
|
||||
conn = &oxfw->in_conn;
|
||||
|
||||
err = cmp_connection_check_used(conn, &used);
|
||||
if ((err >= 0) && used && !amdtp_stream_running(stream)) {
|
||||
dev_err(&oxfw->unit->device,
|
||||
"Connection established by others: %cPCR[%d]\n",
|
||||
(conn->direction == CMP_OUTPUT) ? 'o' : 'i',
|
||||
conn->pcr_index);
|
||||
err = -EBUSY;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int snd_oxfw_stream_init_simplex(struct snd_oxfw *oxfw,
|
||||
struct amdtp_stream *stream)
|
||||
{
|
||||
struct cmp_connection *conn;
|
||||
enum cmp_direction c_dir;
|
||||
enum amdtp_stream_direction s_dir;
|
||||
int err;
|
||||
|
||||
if (stream == &oxfw->tx_stream) {
|
||||
conn = &oxfw->out_conn;
|
||||
c_dir = CMP_OUTPUT;
|
||||
s_dir = AMDTP_IN_STREAM;
|
||||
} else {
|
||||
conn = &oxfw->in_conn;
|
||||
c_dir = CMP_INPUT;
|
||||
s_dir = AMDTP_OUT_STREAM;
|
||||
}
|
||||
|
||||
err = cmp_connection_init(conn, oxfw->unit, c_dir, 0);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
err = amdtp_stream_init(stream, oxfw->unit, s_dir, CIP_NONBLOCKING);
|
||||
if (err < 0) {
|
||||
amdtp_stream_destroy(stream);
|
||||
cmp_connection_destroy(conn);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* OXFW starts to transmit packets with non-zero dbc. */
|
||||
if (stream == &oxfw->tx_stream)
|
||||
oxfw->tx_stream.flags |= CIP_SKIP_INIT_DBC_CHECK;
|
||||
end:
|
||||
return err;
|
||||
}
|
||||
|
||||
int snd_oxfw_stream_start_simplex(struct snd_oxfw *oxfw,
|
||||
struct amdtp_stream *stream,
|
||||
unsigned int rate, unsigned int pcm_channels)
|
||||
{
|
||||
struct amdtp_stream *opposite;
|
||||
struct snd_oxfw_stream_formation formation;
|
||||
enum avc_general_plug_dir dir;
|
||||
unsigned int substreams, opposite_substreams;
|
||||
int err = 0;
|
||||
|
||||
if (stream == &oxfw->tx_stream) {
|
||||
substreams = oxfw->capture_substreams;
|
||||
opposite = &oxfw->rx_stream;
|
||||
opposite_substreams = oxfw->playback_substreams;
|
||||
dir = AVC_GENERAL_PLUG_DIR_OUT;
|
||||
} else {
|
||||
substreams = oxfw->playback_substreams;
|
||||
opposite_substreams = oxfw->capture_substreams;
|
||||
|
||||
if (oxfw->has_output)
|
||||
opposite = &oxfw->rx_stream;
|
||||
else
|
||||
opposite = NULL;
|
||||
|
||||
dir = AVC_GENERAL_PLUG_DIR_IN;
|
||||
}
|
||||
|
||||
if (substreams == 0)
|
||||
goto end;
|
||||
|
||||
/*
|
||||
* Considering JACK/FFADO streaming:
|
||||
* TODO: This can be removed hwdep functionality becomes popular.
|
||||
*/
|
||||
err = check_connection_used_by_others(oxfw, stream);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
/* packet queueing error */
|
||||
if (amdtp_streaming_error(stream))
|
||||
stop_stream(oxfw, stream);
|
||||
|
||||
err = snd_oxfw_stream_get_current_formation(oxfw, dir, &formation);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
if (rate == 0)
|
||||
rate = formation.rate;
|
||||
if (pcm_channels == 0)
|
||||
pcm_channels = formation.pcm;
|
||||
|
||||
if ((formation.rate != rate) || (formation.pcm != pcm_channels)) {
|
||||
if (opposite != NULL) {
|
||||
err = check_connection_used_by_others(oxfw, opposite);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
stop_stream(oxfw, opposite);
|
||||
}
|
||||
stop_stream(oxfw, stream);
|
||||
|
||||
err = set_stream_format(oxfw, stream, rate, pcm_channels);
|
||||
if (err < 0) {
|
||||
dev_err(&oxfw->unit->device,
|
||||
"fail to set stream format: %d\n", err);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Start opposite stream if needed. */
|
||||
if (opposite && !amdtp_stream_running(opposite) &&
|
||||
(opposite_substreams > 0)) {
|
||||
err = start_stream(oxfw, opposite, rate, 0);
|
||||
if (err < 0) {
|
||||
dev_err(&oxfw->unit->device,
|
||||
"fail to restart stream: %d\n", err);
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Start requested stream. */
|
||||
if (!amdtp_stream_running(stream)) {
|
||||
err = start_stream(oxfw, stream, rate, pcm_channels);
|
||||
if (err < 0)
|
||||
dev_err(&oxfw->unit->device,
|
||||
"fail to start stream: %d\n", err);
|
||||
}
|
||||
end:
|
||||
return err;
|
||||
}
|
||||
|
||||
void snd_oxfw_stream_stop_simplex(struct snd_oxfw *oxfw,
|
||||
struct amdtp_stream *stream)
|
||||
{
|
||||
if (((stream == &oxfw->tx_stream) && (oxfw->capture_substreams > 0)) ||
|
||||
((stream == &oxfw->rx_stream) && (oxfw->playback_substreams > 0)))
|
||||
return;
|
||||
|
||||
stop_stream(oxfw, stream);
|
||||
}
|
||||
|
||||
void snd_oxfw_stream_destroy_simplex(struct snd_oxfw *oxfw,
|
||||
struct amdtp_stream *stream)
|
||||
{
|
||||
struct cmp_connection *conn;
|
||||
|
||||
if (stream == &oxfw->tx_stream)
|
||||
conn = &oxfw->out_conn;
|
||||
else
|
||||
conn = &oxfw->in_conn;
|
||||
|
||||
stop_stream(oxfw, stream);
|
||||
|
||||
amdtp_stream_destroy(stream);
|
||||
cmp_connection_destroy(conn);
|
||||
}
|
||||
|
||||
void snd_oxfw_stream_update_simplex(struct snd_oxfw *oxfw,
|
||||
struct amdtp_stream *stream)
|
||||
{
|
||||
struct cmp_connection *conn;
|
||||
|
||||
if (stream == &oxfw->tx_stream)
|
||||
conn = &oxfw->out_conn;
|
||||
else
|
||||
conn = &oxfw->in_conn;
|
||||
|
||||
if (cmp_connection_update(conn) < 0)
|
||||
stop_stream(oxfw, stream);
|
||||
else
|
||||
amdtp_stream_update(stream);
|
||||
}
|
||||
|
||||
int snd_oxfw_stream_get_current_formation(struct snd_oxfw *oxfw,
|
||||
enum avc_general_plug_dir dir,
|
||||
struct snd_oxfw_stream_formation *formation)
|
||||
{
|
||||
u8 *format;
|
||||
unsigned int len;
|
||||
int err;
|
||||
|
||||
len = AVC_GENERIC_FRAME_MAXIMUM_BYTES;
|
||||
format = kmalloc(len, GFP_KERNEL);
|
||||
if (format == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
err = avc_stream_get_format_single(oxfw->unit, dir, 0, format, &len);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
if (len < 3) {
|
||||
err = -EIO;
|
||||
goto end;
|
||||
}
|
||||
|
||||
err = snd_oxfw_stream_parse_format(format, formation);
|
||||
end:
|
||||
kfree(format);
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* See Table 6.16 - AM824 Stream Format
|
||||
* Figure 6.19 - format_information field for AM824 Compound
|
||||
* in AV/C Stream Format Information Specification 1.1 (Apr 2005, 1394TA)
|
||||
* Also 'Clause 12 AM824 sequence adaption layers' in IEC 61883-6:2005
|
||||
*/
|
||||
int snd_oxfw_stream_parse_format(u8 *format,
|
||||
struct snd_oxfw_stream_formation *formation)
|
||||
{
|
||||
unsigned int i, e, channels, type;
|
||||
|
||||
memset(formation, 0, sizeof(struct snd_oxfw_stream_formation));
|
||||
|
||||
/*
|
||||
* this module can support a hierarchy combination that:
|
||||
* Root: Audio and Music (0x90)
|
||||
* Level 1: AM824 Compound (0x40)
|
||||
*/
|
||||
if ((format[0] != 0x90) || (format[1] != 0x40))
|
||||
return -ENOSYS;
|
||||
|
||||
/* check the sampling rate */
|
||||
for (i = 0; i < ARRAY_SIZE(avc_stream_rate_table); i++) {
|
||||
if (format[2] == avc_stream_rate_table[i])
|
||||
break;
|
||||
}
|
||||
if (i == ARRAY_SIZE(avc_stream_rate_table))
|
||||
return -ENOSYS;
|
||||
|
||||
formation->rate = oxfw_rate_table[i];
|
||||
|
||||
for (e = 0; e < format[4]; e++) {
|
||||
channels = format[5 + e * 2];
|
||||
type = format[6 + e * 2];
|
||||
|
||||
switch (type) {
|
||||
/* IEC 60958 Conformant, currently handled as MBLA */
|
||||
case 0x00:
|
||||
/* Multi Bit Linear Audio (Raw) */
|
||||
case 0x06:
|
||||
formation->pcm += channels;
|
||||
break;
|
||||
/* MIDI Conformant */
|
||||
case 0x0d:
|
||||
formation->midi = channels;
|
||||
break;
|
||||
/* IEC 61937-3 to 7 */
|
||||
case 0x01:
|
||||
case 0x02:
|
||||
case 0x03:
|
||||
case 0x04:
|
||||
case 0x05:
|
||||
/* Multi Bit Linear Audio */
|
||||
case 0x07: /* DVD-Audio */
|
||||
case 0x0c: /* High Precision */
|
||||
/* One Bit Audio */
|
||||
case 0x08: /* (Plain) Raw */
|
||||
case 0x09: /* (Plain) SACD */
|
||||
case 0x0a: /* (Encoded) Raw */
|
||||
case 0x0b: /* (Encoded) SACD */
|
||||
/* SMPTE Time-Code conformant */
|
||||
case 0x0e:
|
||||
/* Sample Count */
|
||||
case 0x0f:
|
||||
/* Anciliary Data */
|
||||
case 0x10:
|
||||
/* Synchronization Stream (Stereo Raw audio) */
|
||||
case 0x40:
|
||||
/* Don't care */
|
||||
case 0xff:
|
||||
default:
|
||||
return -ENOSYS; /* not supported */
|
||||
}
|
||||
}
|
||||
|
||||
if (formation->pcm > AMDTP_MAX_CHANNELS_FOR_PCM ||
|
||||
formation->midi > AMDTP_MAX_CHANNELS_FOR_MIDI)
|
||||
return -ENOSYS;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
assume_stream_formats(struct snd_oxfw *oxfw, enum avc_general_plug_dir dir,
|
||||
unsigned int pid, u8 *buf, unsigned int *len,
|
||||
u8 **formats)
|
||||
{
|
||||
struct snd_oxfw_stream_formation formation;
|
||||
unsigned int i, eid;
|
||||
int err;
|
||||
|
||||
/* get format at current sampling rate */
|
||||
err = avc_stream_get_format_single(oxfw->unit, dir, pid, buf, len);
|
||||
if (err < 0) {
|
||||
dev_err(&oxfw->unit->device,
|
||||
"fail to get current stream format for isoc %s plug %d:%d\n",
|
||||
(dir == AVC_GENERAL_PLUG_DIR_IN) ? "in" : "out",
|
||||
pid, err);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* parse and set stream format */
|
||||
eid = 0;
|
||||
err = snd_oxfw_stream_parse_format(buf, &formation);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
formats[eid] = kmalloc(*len, GFP_KERNEL);
|
||||
if (formats[eid] == NULL) {
|
||||
err = -ENOMEM;
|
||||
goto end;
|
||||
}
|
||||
memcpy(formats[eid], buf, *len);
|
||||
|
||||
/* apply the format for each available sampling rate */
|
||||
for (i = 0; i < ARRAY_SIZE(oxfw_rate_table); i++) {
|
||||
if (formation.rate == oxfw_rate_table[i])
|
||||
continue;
|
||||
|
||||
err = avc_general_inquiry_sig_fmt(oxfw->unit,
|
||||
oxfw_rate_table[i],
|
||||
dir, pid);
|
||||
if (err < 0)
|
||||
continue;
|
||||
|
||||
eid++;
|
||||
formats[eid] = kmalloc(*len, GFP_KERNEL);
|
||||
if (formats[eid] == NULL) {
|
||||
err = -ENOMEM;
|
||||
goto end;
|
||||
}
|
||||
memcpy(formats[eid], buf, *len);
|
||||
formats[eid][2] = avc_stream_rate_table[i];
|
||||
}
|
||||
|
||||
err = 0;
|
||||
oxfw->assumed = true;
|
||||
end:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int fill_stream_formats(struct snd_oxfw *oxfw,
|
||||
enum avc_general_plug_dir dir,
|
||||
unsigned short pid)
|
||||
{
|
||||
u8 *buf, **formats;
|
||||
unsigned int len, eid = 0;
|
||||
struct snd_oxfw_stream_formation dummy;
|
||||
int err;
|
||||
|
||||
buf = kmalloc(AVC_GENERIC_FRAME_MAXIMUM_BYTES, GFP_KERNEL);
|
||||
if (buf == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
if (dir == AVC_GENERAL_PLUG_DIR_OUT)
|
||||
formats = oxfw->tx_stream_formats;
|
||||
else
|
||||
formats = oxfw->rx_stream_formats;
|
||||
|
||||
/* get first entry */
|
||||
len = AVC_GENERIC_FRAME_MAXIMUM_BYTES;
|
||||
err = avc_stream_get_format_list(oxfw->unit, dir, 0, buf, &len, 0);
|
||||
if (err == -ENOSYS) {
|
||||
/* LIST subfunction is not implemented */
|
||||
len = AVC_GENERIC_FRAME_MAXIMUM_BYTES;
|
||||
err = assume_stream_formats(oxfw, dir, pid, buf, &len,
|
||||
formats);
|
||||
goto end;
|
||||
} else if (err < 0) {
|
||||
dev_err(&oxfw->unit->device,
|
||||
"fail to get stream format %d for isoc %s plug %d:%d\n",
|
||||
eid, (dir == AVC_GENERAL_PLUG_DIR_IN) ? "in" : "out",
|
||||
pid, err);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* LIST subfunction is implemented */
|
||||
while (eid < SND_OXFW_STREAM_FORMAT_ENTRIES) {
|
||||
/* The format is too short. */
|
||||
if (len < 3) {
|
||||
err = -EIO;
|
||||
break;
|
||||
}
|
||||
|
||||
/* parse and set stream format */
|
||||
err = snd_oxfw_stream_parse_format(buf, &dummy);
|
||||
if (err < 0)
|
||||
break;
|
||||
|
||||
formats[eid] = kmalloc(len, GFP_KERNEL);
|
||||
if (formats[eid] == NULL) {
|
||||
err = -ENOMEM;
|
||||
break;
|
||||
}
|
||||
memcpy(formats[eid], buf, len);
|
||||
|
||||
/* get next entry */
|
||||
len = AVC_GENERIC_FRAME_MAXIMUM_BYTES;
|
||||
err = avc_stream_get_format_list(oxfw->unit, dir, 0,
|
||||
buf, &len, ++eid);
|
||||
/* No entries remained. */
|
||||
if (err == -EINVAL) {
|
||||
err = 0;
|
||||
break;
|
||||
} else if (err < 0) {
|
||||
dev_err(&oxfw->unit->device,
|
||||
"fail to get stream format %d for isoc %s plug %d:%d\n",
|
||||
eid, (dir == AVC_GENERAL_PLUG_DIR_IN) ? "in" :
|
||||
"out",
|
||||
pid, err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
end:
|
||||
kfree(buf);
|
||||
return err;
|
||||
}
|
||||
|
||||
int snd_oxfw_stream_discover(struct snd_oxfw *oxfw)
|
||||
{
|
||||
u8 plugs[AVC_PLUG_INFO_BUF_BYTES];
|
||||
int err;
|
||||
|
||||
/* the number of plugs for isoc in/out, ext in/out */
|
||||
err = avc_general_get_plug_info(oxfw->unit, 0x1f, 0x07, 0x00, plugs);
|
||||
if (err < 0) {
|
||||
dev_err(&oxfw->unit->device,
|
||||
"fail to get info for isoc/external in/out plugs: %d\n",
|
||||
err);
|
||||
goto end;
|
||||
} else if ((plugs[0] == 0) && (plugs[1] == 0)) {
|
||||
err = -ENOSYS;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* use oPCR[0] if exists */
|
||||
if (plugs[1] > 0) {
|
||||
err = fill_stream_formats(oxfw, AVC_GENERAL_PLUG_DIR_OUT, 0);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
oxfw->has_output = true;
|
||||
}
|
||||
|
||||
/* use iPCR[0] if exists */
|
||||
if (plugs[0] > 0)
|
||||
err = fill_stream_formats(oxfw, AVC_GENERAL_PLUG_DIR_IN, 0);
|
||||
end:
|
||||
return err;
|
||||
}
|
||||
|
||||
void snd_oxfw_stream_lock_changed(struct snd_oxfw *oxfw)
|
||||
{
|
||||
oxfw->dev_lock_changed = true;
|
||||
wake_up(&oxfw->hwdep_wait);
|
||||
}
|
||||
|
||||
int snd_oxfw_stream_lock_try(struct snd_oxfw *oxfw)
|
||||
{
|
||||
int err;
|
||||
|
||||
spin_lock_irq(&oxfw->lock);
|
||||
|
||||
/* user land lock this */
|
||||
if (oxfw->dev_lock_count < 0) {
|
||||
err = -EBUSY;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* this is the first time */
|
||||
if (oxfw->dev_lock_count++ == 0)
|
||||
snd_oxfw_stream_lock_changed(oxfw);
|
||||
err = 0;
|
||||
end:
|
||||
spin_unlock_irq(&oxfw->lock);
|
||||
return err;
|
||||
}
|
||||
|
||||
void snd_oxfw_stream_lock_release(struct snd_oxfw *oxfw)
|
||||
{
|
||||
spin_lock_irq(&oxfw->lock);
|
||||
|
||||
if (WARN_ON(oxfw->dev_lock_count <= 0))
|
||||
goto end;
|
||||
if (--oxfw->dev_lock_count == 0)
|
||||
snd_oxfw_stream_lock_changed(oxfw);
|
||||
end:
|
||||
spin_unlock_irq(&oxfw->lock);
|
||||
}
|
|
@ -0,0 +1,317 @@
|
|||
/*
|
||||
* oxfw.c - a part of driver for OXFW970/971 based devices
|
||||
*
|
||||
* Copyright (c) Clemens Ladisch <clemens@ladisch.de>
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#include "oxfw.h"
|
||||
|
||||
#define OXFORD_FIRMWARE_ID_ADDRESS (CSR_REGISTER_BASE + 0x50000)
|
||||
/* 0x970?vvvv or 0x971?vvvv, where vvvv = firmware version */
|
||||
|
||||
#define OXFORD_HARDWARE_ID_ADDRESS (CSR_REGISTER_BASE + 0x90020)
|
||||
#define OXFORD_HARDWARE_ID_OXFW970 0x39443841
|
||||
#define OXFORD_HARDWARE_ID_OXFW971 0x39373100
|
||||
|
||||
#define VENDOR_LOUD 0x000ff2
|
||||
#define VENDOR_GRIFFIN 0x001292
|
||||
#define VENDOR_BEHRINGER 0x001564
|
||||
#define VENDOR_LACIE 0x00d04b
|
||||
|
||||
#define SPECIFIER_1394TA 0x00a02d
|
||||
#define VERSION_AVC 0x010001
|
||||
|
||||
MODULE_DESCRIPTION("Oxford Semiconductor FW970/971 driver");
|
||||
MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("snd-firewire-speakers");
|
||||
|
||||
static bool detect_loud_models(struct fw_unit *unit)
|
||||
{
|
||||
const char *const models[] = {
|
||||
"Onyxi",
|
||||
"Onyx-i",
|
||||
"d.Pro",
|
||||
"Mackie Onyx Satellite",
|
||||
"Tapco LINK.firewire 4x6",
|
||||
"U.420"};
|
||||
char model[32];
|
||||
unsigned int i;
|
||||
int err;
|
||||
|
||||
err = fw_csr_string(unit->directory, CSR_MODEL,
|
||||
model, sizeof(model));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(models); i++) {
|
||||
if (strcmp(models[i], model) == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
return (i < ARRAY_SIZE(models));
|
||||
}
|
||||
|
||||
static int name_card(struct snd_oxfw *oxfw)
|
||||
{
|
||||
struct fw_device *fw_dev = fw_parent_device(oxfw->unit);
|
||||
char vendor[24];
|
||||
char model[32];
|
||||
const char *d, *v, *m;
|
||||
u32 firmware;
|
||||
int err;
|
||||
|
||||
/* get vendor name from root directory */
|
||||
err = fw_csr_string(fw_dev->config_rom + 5, CSR_VENDOR,
|
||||
vendor, sizeof(vendor));
|
||||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
/* get model name from unit directory */
|
||||
err = fw_csr_string(oxfw->unit->directory, CSR_MODEL,
|
||||
model, sizeof(model));
|
||||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
err = snd_fw_transaction(oxfw->unit, TCODE_READ_QUADLET_REQUEST,
|
||||
OXFORD_FIRMWARE_ID_ADDRESS, &firmware, 4, 0);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
be32_to_cpus(&firmware);
|
||||
|
||||
/* to apply card definitions */
|
||||
if (oxfw->device_info) {
|
||||
d = oxfw->device_info->driver_name;
|
||||
v = oxfw->device_info->vendor_name;
|
||||
m = oxfw->device_info->model_name;
|
||||
} else {
|
||||
d = "OXFW";
|
||||
v = vendor;
|
||||
m = model;
|
||||
}
|
||||
|
||||
strcpy(oxfw->card->driver, d);
|
||||
strcpy(oxfw->card->mixername, m);
|
||||
strcpy(oxfw->card->shortname, m);
|
||||
|
||||
snprintf(oxfw->card->longname, sizeof(oxfw->card->longname),
|
||||
"%s %s (OXFW%x %04x), GUID %08x%08x at %s, S%d",
|
||||
v, m, firmware >> 20, firmware & 0xffff,
|
||||
fw_dev->config_rom[3], fw_dev->config_rom[4],
|
||||
dev_name(&oxfw->unit->device), 100 << fw_dev->max_speed);
|
||||
end:
|
||||
return err;
|
||||
}
|
||||
|
||||
static void oxfw_card_free(struct snd_card *card)
|
||||
{
|
||||
struct snd_oxfw *oxfw = card->private_data;
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) {
|
||||
kfree(oxfw->tx_stream_formats[i]);
|
||||
kfree(oxfw->rx_stream_formats[i]);
|
||||
}
|
||||
|
||||
mutex_destroy(&oxfw->mutex);
|
||||
}
|
||||
|
||||
static int oxfw_probe(struct fw_unit *unit,
|
||||
const struct ieee1394_device_id *id)
|
||||
{
|
||||
struct snd_card *card;
|
||||
struct snd_oxfw *oxfw;
|
||||
int err;
|
||||
|
||||
if ((id->vendor_id == VENDOR_LOUD) && !detect_loud_models(unit))
|
||||
return -ENODEV;
|
||||
|
||||
err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE,
|
||||
sizeof(*oxfw), &card);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
card->private_free = oxfw_card_free;
|
||||
oxfw = card->private_data;
|
||||
oxfw->card = card;
|
||||
mutex_init(&oxfw->mutex);
|
||||
oxfw->unit = unit;
|
||||
oxfw->device_info = (const struct device_info *)id->driver_data;
|
||||
spin_lock_init(&oxfw->lock);
|
||||
init_waitqueue_head(&oxfw->hwdep_wait);
|
||||
|
||||
err = snd_oxfw_stream_discover(oxfw);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
err = name_card(oxfw);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
err = snd_oxfw_create_pcm(oxfw);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
if (oxfw->device_info) {
|
||||
err = snd_oxfw_create_mixer(oxfw);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
}
|
||||
|
||||
snd_oxfw_proc_init(oxfw);
|
||||
|
||||
err = snd_oxfw_create_midi(oxfw);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
err = snd_oxfw_create_hwdep(oxfw);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
err = snd_oxfw_stream_init_simplex(oxfw, &oxfw->rx_stream);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
if (oxfw->has_output) {
|
||||
err = snd_oxfw_stream_init_simplex(oxfw, &oxfw->tx_stream);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
}
|
||||
|
||||
err = snd_card_register(card);
|
||||
if (err < 0) {
|
||||
snd_oxfw_stream_destroy_simplex(oxfw, &oxfw->rx_stream);
|
||||
if (oxfw->has_output)
|
||||
snd_oxfw_stream_destroy_simplex(oxfw, &oxfw->tx_stream);
|
||||
goto error;
|
||||
}
|
||||
dev_set_drvdata(&unit->device, oxfw);
|
||||
|
||||
return 0;
|
||||
error:
|
||||
snd_card_free(card);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void oxfw_bus_reset(struct fw_unit *unit)
|
||||
{
|
||||
struct snd_oxfw *oxfw = dev_get_drvdata(&unit->device);
|
||||
|
||||
fcp_bus_reset(oxfw->unit);
|
||||
|
||||
mutex_lock(&oxfw->mutex);
|
||||
|
||||
snd_oxfw_stream_update_simplex(oxfw, &oxfw->rx_stream);
|
||||
if (oxfw->has_output)
|
||||
snd_oxfw_stream_update_simplex(oxfw, &oxfw->tx_stream);
|
||||
|
||||
mutex_unlock(&oxfw->mutex);
|
||||
}
|
||||
|
||||
static void oxfw_remove(struct fw_unit *unit)
|
||||
{
|
||||
struct snd_oxfw *oxfw = dev_get_drvdata(&unit->device);
|
||||
|
||||
snd_card_disconnect(oxfw->card);
|
||||
|
||||
snd_oxfw_stream_destroy_simplex(oxfw, &oxfw->rx_stream);
|
||||
if (oxfw->has_output)
|
||||
snd_oxfw_stream_destroy_simplex(oxfw, &oxfw->tx_stream);
|
||||
|
||||
snd_card_free_when_closed(oxfw->card);
|
||||
}
|
||||
|
||||
static const struct device_info griffin_firewave = {
|
||||
.driver_name = "FireWave",
|
||||
.vendor_name = "Griffin",
|
||||
.model_name = "FireWave",
|
||||
.mixer_channels = 6,
|
||||
.mute_fb_id = 0x01,
|
||||
.volume_fb_id = 0x02,
|
||||
};
|
||||
|
||||
static const struct device_info lacie_speakers = {
|
||||
.driver_name = "FWSpeakers",
|
||||
.vendor_name = "LaCie",
|
||||
.model_name = "FireWire Speakers",
|
||||
.mixer_channels = 1,
|
||||
.mute_fb_id = 0x01,
|
||||
.volume_fb_id = 0x01,
|
||||
};
|
||||
|
||||
static const struct ieee1394_device_id oxfw_id_table[] = {
|
||||
{
|
||||
.match_flags = IEEE1394_MATCH_VENDOR_ID |
|
||||
IEEE1394_MATCH_MODEL_ID |
|
||||
IEEE1394_MATCH_SPECIFIER_ID |
|
||||
IEEE1394_MATCH_VERSION,
|
||||
.vendor_id = VENDOR_GRIFFIN,
|
||||
.model_id = 0x00f970,
|
||||
.specifier_id = SPECIFIER_1394TA,
|
||||
.version = VERSION_AVC,
|
||||
.driver_data = (kernel_ulong_t)&griffin_firewave,
|
||||
},
|
||||
{
|
||||
.match_flags = IEEE1394_MATCH_VENDOR_ID |
|
||||
IEEE1394_MATCH_MODEL_ID |
|
||||
IEEE1394_MATCH_SPECIFIER_ID |
|
||||
IEEE1394_MATCH_VERSION,
|
||||
.vendor_id = VENDOR_LACIE,
|
||||
.model_id = 0x00f970,
|
||||
.specifier_id = SPECIFIER_1394TA,
|
||||
.version = VERSION_AVC,
|
||||
.driver_data = (kernel_ulong_t)&lacie_speakers,
|
||||
},
|
||||
/* Behringer,F-Control Audio 202 */
|
||||
{
|
||||
.match_flags = IEEE1394_MATCH_VENDOR_ID |
|
||||
IEEE1394_MATCH_MODEL_ID,
|
||||
.vendor_id = VENDOR_BEHRINGER,
|
||||
.model_id = 0x00fc22,
|
||||
},
|
||||
/*
|
||||
* Any Mackie(Loud) models (name string/model id):
|
||||
* Onyx-i series (former models): 0x081216
|
||||
* Mackie Onyx Satellite: 0x00200f
|
||||
* Tapco LINK.firewire 4x6: 0x000460
|
||||
* d.2 pro: Unknown
|
||||
* d.4 pro: Unknown
|
||||
* U.420: Unknown
|
||||
* U.420d: Unknown
|
||||
*/
|
||||
{
|
||||
.match_flags = IEEE1394_MATCH_VENDOR_ID |
|
||||
IEEE1394_MATCH_SPECIFIER_ID |
|
||||
IEEE1394_MATCH_VERSION,
|
||||
.vendor_id = VENDOR_LOUD,
|
||||
.specifier_id = SPECIFIER_1394TA,
|
||||
.version = VERSION_AVC,
|
||||
},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(ieee1394, oxfw_id_table);
|
||||
|
||||
static struct fw_driver oxfw_driver = {
|
||||
.driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = KBUILD_MODNAME,
|
||||
.bus = &fw_bus_type,
|
||||
},
|
||||
.probe = oxfw_probe,
|
||||
.update = oxfw_bus_reset,
|
||||
.remove = oxfw_remove,
|
||||
.id_table = oxfw_id_table,
|
||||
};
|
||||
|
||||
static int __init snd_oxfw_init(void)
|
||||
{
|
||||
return driver_register(&oxfw_driver.driver);
|
||||
}
|
||||
|
||||
static void __exit snd_oxfw_exit(void)
|
||||
{
|
||||
driver_unregister(&oxfw_driver.driver);
|
||||
}
|
||||
|
||||
module_init(snd_oxfw_init);
|
||||
module_exit(snd_oxfw_exit);
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче