Merge branch 'for-next' into for-linus
This commit is contained in:
commit
bc88c9e923
|
@ -112,6 +112,8 @@
|
|||
!Esound/soc/soc-devres.c
|
||||
!Esound/soc/soc-io.c
|
||||
!Esound/soc/soc-pcm.c
|
||||
!Esound/soc/soc-ops.c
|
||||
!Esound/soc/soc-compress.c
|
||||
</sect1>
|
||||
<sect1><title>ASoC DAPM API</title>
|
||||
!Esound/soc/soc-dapm.c
|
||||
|
|
|
@ -2181,10 +2181,6 @@ struct _snd_pcm_runtime {
|
|||
struct snd_pcm_hardware hw;
|
||||
struct snd_pcm_hw_constraints hw_constraints;
|
||||
|
||||
/* -- interrupt callbacks -- */
|
||||
void (*transfer_ack_begin)(struct snd_pcm_substream *substream);
|
||||
void (*transfer_ack_end)(struct snd_pcm_substream *substream);
|
||||
|
||||
/* -- timer -- */
|
||||
unsigned int timer_resolution; /* timer resolution */
|
||||
|
||||
|
@ -2209,9 +2205,8 @@ struct _snd_pcm_runtime {
|
|||
For the operators (callbacks) of each sound driver, most of
|
||||
these records are supposed to be read-only. Only the PCM
|
||||
middle-layer changes / updates them. The exceptions are
|
||||
the hardware description (hw), interrupt callbacks
|
||||
(transfer_ack_xxx), DMA buffer information, and the private
|
||||
data. Besides, if you use the standard buffer allocation
|
||||
the hardware description (hw) DMA buffer information and the
|
||||
private data. Besides, if you use the standard buffer allocation
|
||||
method via <function>snd_pcm_lib_malloc_pages()</function>,
|
||||
you don't need to set the DMA buffer information by yourself.
|
||||
</para>
|
||||
|
@ -2538,16 +2533,6 @@ struct _snd_pcm_runtime {
|
|||
</para>
|
||||
</section>
|
||||
|
||||
<section id="pcm-interface-runtime-intr">
|
||||
<title>Interrupt Callbacks</title>
|
||||
<para>
|
||||
The field <structfield>transfer_ack_begin</structfield> and
|
||||
<structfield>transfer_ack_end</structfield> are called at
|
||||
the beginning and at the end of
|
||||
<function>snd_pcm_period_elapsed()</function>, respectively.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
</section>
|
||||
|
||||
<section id="pcm-interface-operators">
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
AK4613 I2C transmitter
|
||||
|
||||
This device supports I2C mode only.
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible : "asahi-kasei,ak4613"
|
||||
- reg : The chip select number on the I2C bus
|
||||
|
||||
Example:
|
||||
|
||||
&i2c {
|
||||
ak4613: ak4613@0x10 {
|
||||
compatible = "asahi-kasei,ak4613";
|
||||
reg = <0x10>;
|
||||
};
|
||||
};
|
|
@ -7,7 +7,14 @@ Required properties:
|
|||
- compatible : "asahi-kasei,ak4642" or "asahi-kasei,ak4643" or "asahi-kasei,ak4648"
|
||||
- reg : The chip select number on the I2C bus
|
||||
|
||||
Example:
|
||||
Optional properties:
|
||||
|
||||
- #clock-cells : common clock binding; shall be set to 0
|
||||
- clocks : common clock binding; MCKI clock
|
||||
- clock-frequency : common clock binding; frequency of MCKO
|
||||
- clock-output-names : common clock binding; MCKO clock name
|
||||
|
||||
Example 1:
|
||||
|
||||
&i2c {
|
||||
ak4648: ak4648@0x12 {
|
||||
|
@ -15,3 +22,16 @@ Example:
|
|||
reg = <0x12>;
|
||||
};
|
||||
};
|
||||
|
||||
Example 2:
|
||||
|
||||
&i2c {
|
||||
ak4643: codec@12 {
|
||||
compatible = "asahi-kasei,ak4643";
|
||||
reg = <0x12>;
|
||||
#clock-cells = <0>;
|
||||
clocks = <&audio_clock>;
|
||||
clock-frequency = <12288000>;
|
||||
clock-output-names = "ak4643_mcko";
|
||||
};
|
||||
};
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
* Atmel ClassD driver under ALSA SoC architecture
|
||||
|
||||
Required properties:
|
||||
- compatible
|
||||
Should be "atmel,sama5d2-classd".
|
||||
- reg
|
||||
Should contain ClassD registers location and length.
|
||||
- interrupts
|
||||
Should contain the IRQ line for the ClassD.
|
||||
- dmas
|
||||
One DMA specifiers as described in atmel-dma.txt and dma.txt files.
|
||||
- dma-names
|
||||
Must be "tx".
|
||||
- clock-names
|
||||
Tuple listing input clock names.
|
||||
Required elements: "pclk", "gclk" and "aclk".
|
||||
- clocks
|
||||
Please refer to clock-bindings.txt.
|
||||
|
||||
Optional properties:
|
||||
- pinctrl-names, pinctrl-0
|
||||
Please refer to pinctrl-bindings.txt.
|
||||
- atmel,model
|
||||
The user-visible name of this sound complex.
|
||||
The default value is "CLASSD".
|
||||
- atmel,pwm-type
|
||||
PWM modulation type, "single" or "diff".
|
||||
The default value is "single".
|
||||
- atmel,non-overlap-time
|
||||
Set non-overlapping time, the unit is nanosecond(ns).
|
||||
There are four values,
|
||||
<5>, <10>, <15>, <20>, the default value is <10>.
|
||||
Non-overlapping will be disabled if not specified.
|
||||
|
||||
Example:
|
||||
classd: classd@fc048000 {
|
||||
compatible = "atmel,sama5d2-classd";
|
||||
reg = <0xfc048000 0x100>;
|
||||
interrupts = <59 IRQ_TYPE_LEVEL_HIGH 7>;
|
||||
dmas = <&dma0
|
||||
(AT91_XDMAC_DT_MEM_IF(0) | AT91_XDMAC_DT_PER_IF(1)
|
||||
| AT91_XDMAC_DT_PERID(47))>;
|
||||
dma-names = "tx";
|
||||
clocks = <&classd_clk>, <&classd_gclk>, <&audio_pll_pmc>;
|
||||
clock-names = "pclk", "gclk", "aclk";
|
||||
|
||||
pinctrl-names = "default";
|
||||
pinctrl-0 = <&pinctrl_classd_default>;
|
||||
atmel,model = "classd @ SAMA5D2-Xplained";
|
||||
atmel,pwm-type = "diff";
|
||||
atmel,non-overlap-time = <10>;
|
||||
};
|
|
@ -0,0 +1,41 @@
|
|||
Dialog Semiconductor DA7213 Audio Codec bindings
|
||||
|
||||
======
|
||||
|
||||
Required properties:
|
||||
- compatible : Should be "dlg,da7213"
|
||||
- reg: Specifies the I2C slave address
|
||||
|
||||
Optional properties:
|
||||
- clocks : phandle and clock specifier for codec MCLK.
|
||||
- clock-names : Clock name string for 'clocks' attribute, should be "mclk".
|
||||
|
||||
- dlg,micbias1-lvl : Voltage (mV) for Mic Bias 1
|
||||
[<1600>, <2200>, <2500>, <3000>]
|
||||
- dlg,micbias2-lvl : Voltage (mV) for Mic Bias 2
|
||||
[<1600>, <2200>, <2500>, <3000>]
|
||||
- dlg,dmic-data-sel : DMIC channel select based on clock edge.
|
||||
["lrise_rfall", "lfall_rrise"]
|
||||
- dlg,dmic-samplephase : When to sample audio from DMIC.
|
||||
["on_clkedge", "between_clkedge"]
|
||||
- dlg,dmic-clkrate : DMIC clock frequency (Hz).
|
||||
[<1500000>, <3000000>]
|
||||
|
||||
======
|
||||
|
||||
Example:
|
||||
|
||||
codec_i2c: da7213@1a {
|
||||
compatible = "dlg,da7213";
|
||||
reg = <0x1a>;
|
||||
|
||||
clocks = <&clks 201>;
|
||||
clock-names = "mclk";
|
||||
|
||||
dlg,micbias1-lvl = <2500>;
|
||||
dlg,micbias2-lvl = <2500>;
|
||||
|
||||
dlg,dmic-data-sel = "lrise_rfall";
|
||||
dlg,dmic-samplephase = "between_clkedge";
|
||||
dlg,dmic-clkrate = <3000000>;
|
||||
};
|
|
@ -0,0 +1,106 @@
|
|||
Dialog Semiconductor DA7219 Audio Codec bindings
|
||||
|
||||
DA7219 is an audio codec with advanced accessory detect features.
|
||||
|
||||
======
|
||||
|
||||
Required properties:
|
||||
- compatible : Should be "dlg,da7219"
|
||||
- reg: Specifies the I2C slave address
|
||||
|
||||
- interrupt-parent : Specifies the phandle of the interrupt controller to which
|
||||
the IRQs from DA7219 are delivered to.
|
||||
- interrupts : IRQ line info for DA7219.
|
||||
(See Documentation/devicetree/bindings/interrupt-controller/interrupts.txt for
|
||||
further information relating to interrupt properties)
|
||||
|
||||
- VDD-supply: VDD power supply for the device
|
||||
- VDDMIC-supply: VDDMIC power supply for the device
|
||||
- VDDIO-supply: VDDIO power supply for the device
|
||||
(See Documentation/devicetree/bindings/regulator/regulator.txt for further
|
||||
information relating to regulators)
|
||||
|
||||
Optional properties:
|
||||
- interrupt-names : Name associated with interrupt line. Should be "wakeup" if
|
||||
interrupt is to be used to wake system, otherwise "irq" should be used.
|
||||
- wakeup-source: Flag to indicate this device can wake system (suspend/resume).
|
||||
|
||||
- clocks : phandle and clock specifier for codec MCLK.
|
||||
- clock-names : Clock name string for 'clocks' attribute, should be "mclk".
|
||||
|
||||
- dlg,ldo-lvl : Required internal LDO voltage (mV) level for digital engine
|
||||
[<1050>, <1100>, <1200>, <1400>]
|
||||
- dlg,micbias-lvl : Voltage (mV) for Mic Bias
|
||||
[<1800>, <2000>, <2200>, <2400>, <2600>]
|
||||
- dlg,mic-amp-in-sel : Mic input source type
|
||||
["diff", "se_p", "se_n"]
|
||||
|
||||
======
|
||||
|
||||
Child node - 'da7219_aad':
|
||||
|
||||
Optional properties:
|
||||
- dlg,micbias-pulse-lvl : Mic bias higher voltage pulse level (mV).
|
||||
[<2800>, <2900>]
|
||||
- dlg,micbias-pulse-time : Mic bias higher voltage pulse duration (ms)
|
||||
- dlg,btn-cfg : Periodic button press measurements for 4-pole jack (ms)
|
||||
[<2>, <5>, <10>, <50>, <100>, <200>, <500>]
|
||||
- dlg,mic-det-thr : Impedance threshold for mic detection measurement (Ohms)
|
||||
[<200>, <500>, <750>, <1000>]
|
||||
- dlg,jack-ins-deb : Debounce time for jack insertion (ms)
|
||||
[<5>, <10>, <20>, <50>, <100>, <200>, <500>, <1000>]
|
||||
- dlg,jack-det-rate: Jack type detection latency (3/4 pole)
|
||||
["32ms_64ms", "64ms_128ms", "128ms_256ms", "256ms_512ms"]
|
||||
- dlg,jack-rem-deb : Debounce time for jack removal (ms)
|
||||
[<1>, <5>, <10>, <20>]
|
||||
- dlg,a-d-btn-thr : Impedance threshold between buttons A and D
|
||||
[0x0 - 0xFF]
|
||||
- dlg,d-b-btn-thr : Impedance threshold between buttons D and B
|
||||
[0x0 - 0xFF]
|
||||
- dlg,b-c-btn-thr : Impedance threshold between buttons B and C
|
||||
[0x0 - 0xFF]
|
||||
- dlg,c-mic-btn-thr : Impedance threshold between button C and Mic
|
||||
[0x0 - 0xFF]
|
||||
- dlg,btn-avg : Number of 8-bit readings for averaged button measurement
|
||||
[<1>, <2>, <4>, <8>]
|
||||
- dlg,adc-1bit-rpt : Repeat count for 1-bit button measurement
|
||||
[<1>, <2>, <4>, <8>]
|
||||
|
||||
======
|
||||
|
||||
Example:
|
||||
|
||||
codec: da7219@1a {
|
||||
compatible = "dlg,da7219";
|
||||
reg = <0x1a>;
|
||||
|
||||
interrupt-parent = <&gpio6>;
|
||||
interrupts = <11 IRQ_TYPE_LEVEL_HIGH>;
|
||||
|
||||
VDD-supply = <®_audio>;
|
||||
VDDMIC-supply = <®_audio>;
|
||||
VDDIO-supply = <®_audio>;
|
||||
|
||||
clocks = <&clks 201>;
|
||||
clock-names = "mclk";
|
||||
|
||||
dlg,ldo-lvl = <1200>;
|
||||
dlg,micbias-lvl = <2600>;
|
||||
dlg,mic-amp-in-sel = "diff";
|
||||
|
||||
da7219_aad {
|
||||
dlg,btn-cfg = <50>;
|
||||
dlg,mic-det-thr = <500>;
|
||||
dlg,jack-ins-deb = <20>;
|
||||
dlg,jack-det-rate = "32ms_64ms";
|
||||
dlg,jack-rem-deb = <1>;
|
||||
|
||||
dlg,a-d-btn-thr = <0xa>;
|
||||
dlg,d-b-btn-thr = <0x16>;
|
||||
dlg,b-c-btn-thr = <0x21>;
|
||||
dlg,c-mic-btn-thr = <0x3E>;
|
||||
|
||||
dlg,btn-avg = <4>;
|
||||
dlg,adc-1bit-rpt = <1>;
|
||||
};
|
||||
};
|
|
@ -13,13 +13,15 @@ So having this generic sound card allows all Freescale SoC users to benefit
|
|||
from the simplification of a new card support and the capability of the wide
|
||||
sample rates support through ASRC.
|
||||
|
||||
Note: The card is initially designed for those sound cards who use I2S and
|
||||
PCM DAI formats. However, it'll be also possible to support those non
|
||||
I2S/PCM type sound cards, such as S/PDIF audio and HDMI audio, as long
|
||||
as the driver has been properly upgraded.
|
||||
Note: The card is initially designed for those sound cards who use AC'97, I2S
|
||||
and PCM DAI formats. However, it'll be also possible to support those non
|
||||
AC'97/I2S/PCM type sound cards, such as S/PDIF audio and HDMI audio, as
|
||||
long as the driver has been properly upgraded.
|
||||
|
||||
|
||||
The compatible list for this generic sound card currently:
|
||||
"fsl,imx-audio-ac97"
|
||||
|
||||
"fsl,imx-audio-cs42888"
|
||||
|
||||
"fsl,imx-audio-wm8962"
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
Nuvoton NAU8825 audio codec
|
||||
|
||||
This device supports I2C only.
|
||||
|
||||
Required properties:
|
||||
- compatible : Must be "nuvoton,nau8825"
|
||||
|
||||
- reg : the I2C address of the device. This is either 0x1a (CSB=0) or 0x1b (CSB=1).
|
||||
|
||||
Optional properties:
|
||||
- nuvoton,jkdet-enable: Enable jack detection via JKDET pin.
|
||||
- nuvoton,jkdet-pull-enable: Enable JKDET pin pull. If set - pin pull enabled,
|
||||
otherwise pin in high impedance state.
|
||||
- nuvoton,jkdet-pull-up: Pull-up JKDET pin. If set then JKDET pin is pull up, otherwise pull down.
|
||||
- nuvoton,jkdet-polarity: JKDET pin polarity. 0 - active high, 1 - active low.
|
||||
|
||||
- nuvoton,vref-impedance: VREF Impedance selection
|
||||
0 - Open
|
||||
1 - 25 kOhm
|
||||
2 - 125 kOhm
|
||||
3 - 2.5 kOhm
|
||||
|
||||
- nuvoton,micbias-voltage: Micbias voltage level.
|
||||
0 - VDDA
|
||||
1 - VDDA
|
||||
2 - VDDA * 1.1
|
||||
3 - VDDA * 1.2
|
||||
4 - VDDA * 1.3
|
||||
5 - VDDA * 1.4
|
||||
6 - VDDA * 1.53
|
||||
7 - VDDA * 1.53
|
||||
|
||||
- nuvoton,sar-threshold-num: Number of buttons supported
|
||||
- nuvoton,sar-threshold: Impedance threshold for each button. Array that contains up to 8 buttons configuration. SAR value is calculated as
|
||||
SAR = 255 * MICBIAS / SAR_VOLTAGE * R / (2000 + R)
|
||||
where MICBIAS is configured by 'nuvoton,micbias-voltage', SAR_VOLTAGE is configured by 'nuvoton,sar-voltage', R - button impedance.
|
||||
Refer datasheet section 10.2 for more information about threshold calculation.
|
||||
|
||||
- nuvoton,sar-hysteresis: Button impedance measurement hysteresis.
|
||||
|
||||
- nuvoton,sar-voltage: Reference voltage for button impedance measurement.
|
||||
0 - VDDA
|
||||
1 - VDDA
|
||||
2 - VDDA * 1.1
|
||||
3 - VDDA * 1.2
|
||||
4 - VDDA * 1.3
|
||||
5 - VDDA * 1.4
|
||||
6 - VDDA * 1.53
|
||||
7 - VDDA * 1.53
|
||||
|
||||
- nuvoton,sar-compare-time: SAR compare time
|
||||
0 - 500 ns
|
||||
1 - 1 us
|
||||
2 - 2 us
|
||||
3 - 4 us
|
||||
|
||||
- nuvoton,sar-sampling-time: SAR sampling time
|
||||
0 - 2 us
|
||||
1 - 4 us
|
||||
2 - 8 us
|
||||
3 - 16 us
|
||||
|
||||
- nuvoton,short-key-debounce: Button short key press debounce time.
|
||||
0 - 30 ms
|
||||
1 - 50 ms
|
||||
2 - 100 ms
|
||||
3 - 30 ms
|
||||
|
||||
- nuvoton,jack-insert-debounce: number from 0 to 7 that sets debounce time to 2^(n+2) ms
|
||||
- nuvoton,jack-eject-debounce: number from 0 to 7 that sets debounce time to 2^(n+2) ms
|
||||
|
||||
- clocks: list of phandle and clock specifier pairs according to common clock bindings for the
|
||||
clocks described in clock-names
|
||||
- clock-names: should include "mclk" for the MCLK master clock
|
||||
|
||||
Example:
|
||||
|
||||
headset: nau8825@1a {
|
||||
compatible = "nuvoton,nau8825";
|
||||
reg = <0x1a>;
|
||||
interrupt-parent = <&gpio>;
|
||||
interrupts = <TEGRA_GPIO(E, 6) IRQ_TYPE_LEVEL_LOW>;
|
||||
nuvoton,jkdet-enable;
|
||||
nuvoton,jkdet-pull-enable;
|
||||
nuvoton,jkdet-pull-up;
|
||||
nuvoton,jkdet-polarity = <GPIO_ACTIVE_LOW>;
|
||||
nuvoton,vref-impedance = <2>;
|
||||
nuvoton,micbias-voltage = <6>;
|
||||
// Setup 4 buttons impedance according to Android specification
|
||||
nuvoton,sar-threshold-num = <4>;
|
||||
nuvoton,sar-threshold = <0xc 0x1e 0x38 0x60>;
|
||||
nuvoton,sar-hysteresis = <1>;
|
||||
nuvoton,sar-voltage = <0>;
|
||||
nuvoton,sar-compare-time = <0>;
|
||||
nuvoton,sar-sampling-time = <0>;
|
||||
nuvoton,short-key-debounce = <2>;
|
||||
nuvoton,jack-insert-debounce = <7>;
|
||||
nuvoton,jack-eject-debounce = <7>;
|
||||
|
||||
clock-names = "mclk";
|
||||
clocks = <&tegra_car TEGRA210_CLK_CLK_OUT_2>;
|
||||
};
|
|
@ -4,10 +4,12 @@ Required properties:
|
|||
- compatible : "renesas,rcar_sound-<soctype>", fallbacks
|
||||
"renesas,rcar_sound-gen1" if generation1, and
|
||||
"renesas,rcar_sound-gen2" if generation2
|
||||
"renesas,rcar_sound-gen3" if generation3
|
||||
Examples with soctypes are:
|
||||
- "renesas,rcar_sound-r8a7778" (R-Car M1A)
|
||||
- "renesas,rcar_sound-r8a7790" (R-Car H2)
|
||||
- "renesas,rcar_sound-r8a7791" (R-Car M2-W)
|
||||
- "renesas,rcar_sound-r8a7795" (R-Car H3)
|
||||
- reg : Should contain the register physical address.
|
||||
required register is
|
||||
SRU/ADG/SSI if generation1
|
||||
|
@ -30,6 +32,11 @@ Required properties:
|
|||
- rcar_sound,dai : DAI contents.
|
||||
The number of DAI subnode should be same as HW.
|
||||
see below for detail.
|
||||
- #sound-dai-cells : it must be 0 if your system is using single DAI
|
||||
it must be 1 if your system is using multi DAI
|
||||
- #clock-cells : it must be 0 if your system has audio_clkout
|
||||
it must be 1 if your system has audio_clkout0/1/2/3
|
||||
- clock-frequency : for all audio_clkout0/1/2/3
|
||||
|
||||
SSI subnode properties:
|
||||
- interrupts : Should contain SSI interrupt for PIO transfer
|
||||
|
|
|
@ -12,8 +12,6 @@ Required properties:
|
|||
- reg: physical base address of the controller and length of memory mapped
|
||||
region.
|
||||
- interrupts: should contain the I2S interrupt.
|
||||
- #address-cells: should be 1.
|
||||
- #size-cells: should be 0.
|
||||
- dmas: DMA specifiers for tx and rx dma. See the DMA client binding,
|
||||
Documentation/devicetree/bindings/dma/dma.txt
|
||||
- dma-names: should include "tx" and "rx".
|
||||
|
@ -21,6 +19,7 @@ Required properties:
|
|||
- clock-names: should contain followings:
|
||||
- "i2s_hclk": clock for I2S BUS
|
||||
- "i2s_clk" : clock for I2S controller
|
||||
- rockchip,capture-channels: max capture channels, if not set, 2 channels default.
|
||||
|
||||
Example for rk3288 I2S controller:
|
||||
|
||||
|
@ -28,10 +27,9 @@ i2s@ff890000 {
|
|||
compatible = "rockchip,rk3288-i2s", "rockchip,rk3066-i2s";
|
||||
reg = <0xff890000 0x10000>;
|
||||
interrupts = <GIC_SPI 85 IRQ_TYPE_LEVEL_HIGH>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
dmas = <&pdma1 0>, <&pdma1 1>;
|
||||
dma-names = "tx", "rx";
|
||||
clock-names = "i2s_hclk", "i2s_clk";
|
||||
clocks = <&cru HCLK_I2S0>, <&cru SCLK_I2S0>;
|
||||
rockchip,capture-channels = <2>;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
* Rockchip SPDIF transceiver
|
||||
|
||||
The S/PDIF audio block is a stereo transceiver that allows the
|
||||
processor to receive and transmit digital audio via an coaxial cable or
|
||||
a fibre cable.
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible: should be one of the following:
|
||||
- "rockchip,rk3288-spdif", "rockchip,rk3188-spdif" or
|
||||
"rockchip,rk3066-spdif"
|
||||
- reg: physical base address of the controller and length of memory mapped
|
||||
region.
|
||||
- interrupts: should contain the SPDIF interrupt.
|
||||
- dmas: DMA specifiers for tx dma. See the DMA client binding,
|
||||
Documentation/devicetree/bindings/dma/dma.txt
|
||||
- dma-names: should be "tx"
|
||||
- clocks: a list of phandle + clock-specifier pairs, one for each entry
|
||||
in clock-names.
|
||||
- clock-names: should contain following:
|
||||
- "hclk": clock for SPDIF controller
|
||||
- "mclk" : clock for SPDIF bus
|
||||
|
||||
Required properties on RK3288:
|
||||
- rockchip,grf: the phandle of the syscon node for the general register
|
||||
file (GRF)
|
||||
|
||||
Example for the rk3188 SPDIF controller:
|
||||
|
||||
spdif: spdif@0x1011e000 {
|
||||
compatible = "rockchip,rk3188-spdif", "rockchip,rk3066-spdif";
|
||||
reg = <0x1011e000 0x2000>;
|
||||
interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;
|
||||
dmas = <&dmac1_s 8>;
|
||||
dma-names = "tx";
|
||||
clock-names = "hclk", "mclk";
|
||||
clocks = <&cru HCLK_SPDIF>, <&cru SCLK_SPDIF>;
|
||||
status = "disabled";
|
||||
#sound-dai-cells = <0>;
|
||||
};
|
|
@ -14,7 +14,8 @@ Optional properties:
|
|||
|
||||
- realtek,in1-differential
|
||||
- realtek,in2-differential
|
||||
Boolean. Indicate MIC1/2 input are differential, rather than single-ended.
|
||||
- realtek,in3-differential
|
||||
Boolean. Indicate MIC1/2/3 input are differential, rather than single-ended.
|
||||
|
||||
- realtek,ldo1-en-gpios : The GPIO that controls the CODEC's LDO1_EN pin.
|
||||
|
||||
|
@ -24,9 +25,11 @@ Pins on the device (for linking into audio routes) for RT5639/RT5640:
|
|||
* DMIC2
|
||||
* MICBIAS1
|
||||
* IN1P
|
||||
* IN1R
|
||||
* IN1N
|
||||
* IN2P
|
||||
* IN2R
|
||||
* IN2N
|
||||
* IN3P
|
||||
* IN3N
|
||||
* HPOL
|
||||
* HPOR
|
||||
* LOUTL
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
* Allwinner A10 Codec
|
||||
|
||||
Required properties:
|
||||
- compatible: must be either "allwinner,sun4i-a10-codec" or
|
||||
"allwinner,sun7i-a20-codec"
|
||||
- reg: must contain the registers location and length
|
||||
- interrupts: must contain the codec interrupt
|
||||
- dmas: DMA channels for tx and rx dma. See the DMA client binding,
|
||||
Documentation/devicetree/bindings/dma/dma.txt
|
||||
- dma-names: should include "tx" and "rx".
|
||||
- clocks: a list of phandle + clock-specifer pairs, one for each entry
|
||||
in clock-names.
|
||||
- clock-names: should contain followings:
|
||||
- "apb": the parent APB clock for this controller
|
||||
- "codec": the parent module clock
|
||||
|
||||
Example:
|
||||
codec: codec@01c22c00 {
|
||||
#sound-dai-cells = <0>;
|
||||
compatible = "allwinner,sun7i-a20-codec";
|
||||
reg = <0x01c22c00 0x40>;
|
||||
interrupts = <0 30 4>;
|
||||
clocks = <&apb0_gates 0>, <&codec_clk>;
|
||||
clock-names = "apb", "codec";
|
||||
dmas = <&dma 0 19>, <&dma 0 19>;
|
||||
dma-names = "rx", "tx";
|
||||
};
|
|
@ -4,11 +4,15 @@ This specifies audio DAI's TDM slot.
|
|||
|
||||
TDM slot properties:
|
||||
dai-tdm-slot-num : Number of slots in use.
|
||||
dai-tdm-slot-width : Width in bits for each slot.
|
||||
dai-tdm-slot-width : Width in bits for each slot.
|
||||
dai-tdm-slot-tx-mask : Transmit direction slot mask, optional
|
||||
dai-tdm-slot-rx-mask : Receive direction slot mask, optional
|
||||
|
||||
For instance:
|
||||
dai-tdm-slot-num = <2>;
|
||||
dai-tdm-slot-width = <8>;
|
||||
dai-tdm-slot-tx-mask = <0 1>;
|
||||
dai-tdm-slot-rx-mask = <1 0>;
|
||||
|
||||
And for each spcified driver, there could be one .of_xlate_tdm_slot_mask()
|
||||
to specify a explicit mapping of the channels and the slots. If it's absent
|
||||
|
@ -18,3 +22,8 @@ tx and rx masks.
|
|||
For snd_soc_of_xlate_tdm_slot_mask(), the tx and rx masks will use a 1 bit
|
||||
for an active slot as default, and the default active bits are at the LSB of
|
||||
the masks.
|
||||
|
||||
The explicit masks are given as array of integers, where the first
|
||||
number presents bit-0 (LSB), second presents bit-1, etc. Any non zero
|
||||
number is considered 1 and 0 is 0. snd_soc_of_xlate_tdm_slot_mask()
|
||||
does not do anything, if either mask is set non zero value.
|
||||
|
|
|
@ -1,322 +0,0 @@
|
|||
Notes on Universal Interface for Intel High Definition Audio Codec
|
||||
------------------------------------------------------------------
|
||||
|
||||
Takashi Iwai <tiwai@suse.de>
|
||||
|
||||
|
||||
[Still a draft version]
|
||||
|
||||
|
||||
General
|
||||
=======
|
||||
|
||||
The snd-hda-codec module supports the generic access function for the
|
||||
High Definition (HD) audio codecs. It's designed to be independent
|
||||
from the controller code like ac97 codec module. The real accessors
|
||||
from/to the controller must be implemented in the lowlevel driver.
|
||||
|
||||
The structure of this module is similar with ac97_codec module.
|
||||
Each codec chip belongs to a bus class which communicates with the
|
||||
controller.
|
||||
|
||||
|
||||
Initialization of Bus Instance
|
||||
==============================
|
||||
|
||||
The card driver has to create struct hda_bus at first. The template
|
||||
struct should be filled and passed to the constructor:
|
||||
|
||||
struct hda_bus_template {
|
||||
void *private_data;
|
||||
struct pci_dev *pci;
|
||||
const char *modelname;
|
||||
struct hda_bus_ops ops;
|
||||
};
|
||||
|
||||
The card driver can set and use the private_data field to retrieve its
|
||||
own data in callback functions. The pci field is used when the patch
|
||||
needs to check the PCI subsystem IDs, so on. For non-PCI system, it
|
||||
doesn't have to be set, of course.
|
||||
The modelname field specifies the board's specific configuration. The
|
||||
string is passed to the codec parser, and it depends on the parser how
|
||||
the string is used.
|
||||
These fields, private_data, pci and modelname are all optional.
|
||||
|
||||
The ops field contains the callback functions as the following:
|
||||
|
||||
struct hda_bus_ops {
|
||||
int (*command)(struct hda_codec *codec, hda_nid_t nid, int direct,
|
||||
unsigned int verb, unsigned int parm);
|
||||
unsigned int (*get_response)(struct hda_codec *codec);
|
||||
void (*private_free)(struct hda_bus *);
|
||||
#ifdef CONFIG_SND_HDA_POWER_SAVE
|
||||
void (*pm_notify)(struct hda_codec *codec);
|
||||
#endif
|
||||
};
|
||||
|
||||
The command callback is called when the codec module needs to send a
|
||||
VERB to the controller. It's always a single command.
|
||||
The get_response callback is called when the codec requires the answer
|
||||
for the last command. These two callbacks are mandatory and have to
|
||||
be given.
|
||||
The third, private_free callback, is optional. It's called in the
|
||||
destructor to release any necessary data in the lowlevel driver.
|
||||
|
||||
The pm_notify callback is available only with
|
||||
CONFIG_SND_HDA_POWER_SAVE kconfig. It's called when the codec needs
|
||||
to power up or may power down. The controller should check the all
|
||||
belonging codecs on the bus whether they are actually powered off
|
||||
(check codec->power_on), and optionally the driver may power down the
|
||||
controller side, too.
|
||||
|
||||
The bus instance is created via snd_hda_bus_new(). You need to pass
|
||||
the card instance, the template, and the pointer to store the
|
||||
resultant bus instance.
|
||||
|
||||
int snd_hda_bus_new(struct snd_card *card, const struct hda_bus_template *temp,
|
||||
struct hda_bus **busp);
|
||||
|
||||
It returns zero if successful. A negative return value means any
|
||||
error during creation.
|
||||
|
||||
|
||||
Creation of Codec Instance
|
||||
==========================
|
||||
|
||||
Each codec chip on the board is then created on the BUS instance.
|
||||
To create a codec instance, call snd_hda_codec_new().
|
||||
|
||||
int snd_hda_codec_new(struct hda_bus *bus, unsigned int codec_addr,
|
||||
struct hda_codec **codecp);
|
||||
|
||||
The first argument is the BUS instance, the second argument is the
|
||||
address of the codec, and the last one is the pointer to store the
|
||||
resultant codec instance (can be NULL if not needed).
|
||||
|
||||
The codec is stored in a linked list of bus instance. You can follow
|
||||
the codec list like:
|
||||
|
||||
struct hda_codec *codec;
|
||||
list_for_each_entry(codec, &bus->codec_list, list) {
|
||||
...
|
||||
}
|
||||
|
||||
The codec isn't initialized at this stage properly. The
|
||||
initialization sequence is called when the controls are built later.
|
||||
|
||||
|
||||
Codec Access
|
||||
============
|
||||
|
||||
To access codec, use snd_hda_codec_read() and snd_hda_codec_write().
|
||||
snd_hda_param_read() is for reading parameters.
|
||||
For writing a sequence of verbs, use snd_hda_sequence_write().
|
||||
|
||||
There are variants of cached read/write, snd_hda_codec_write_cache(),
|
||||
snd_hda_sequence_write_cache(). These are used for recording the
|
||||
register states for the power-management resume. When no PM is needed,
|
||||
these are equivalent with non-cached version.
|
||||
|
||||
To retrieve the number of sub nodes connected to the given node, use
|
||||
snd_hda_get_sub_nodes(). The connection list can be obtained via
|
||||
snd_hda_get_connections() call.
|
||||
|
||||
When an unsolicited event happens, pass the event via
|
||||
snd_hda_queue_unsol_event() so that the codec routines will process it
|
||||
later.
|
||||
|
||||
|
||||
(Mixer) Controls
|
||||
================
|
||||
|
||||
To create mixer controls of all codecs, call
|
||||
snd_hda_build_controls(). It then builds the mixers and does
|
||||
initialization stuff on each codec.
|
||||
|
||||
|
||||
PCM Stuff
|
||||
=========
|
||||
|
||||
snd_hda_build_pcms() gives the necessary information to create PCM
|
||||
streams. When it's called, each codec belonging to the bus stores
|
||||
codec->num_pcms and codec->pcm_info fields. The num_pcms indicates
|
||||
the number of elements in pcm_info array. The card driver is supposed
|
||||
to traverse the codec linked list, read the pcm information in
|
||||
pcm_info array, and build pcm instances according to them.
|
||||
|
||||
The pcm_info array contains the following record:
|
||||
|
||||
/* PCM information for each substream */
|
||||
struct hda_pcm_stream {
|
||||
unsigned int substreams; /* number of substreams, 0 = not exist */
|
||||
unsigned int channels_min; /* min. number of channels */
|
||||
unsigned int channels_max; /* max. number of channels */
|
||||
hda_nid_t nid; /* default NID to query rates/formats/bps, or set up */
|
||||
u32 rates; /* supported rates */
|
||||
u64 formats; /* supported formats (SNDRV_PCM_FMTBIT_) */
|
||||
unsigned int maxbps; /* supported max. bit per sample */
|
||||
struct hda_pcm_ops ops;
|
||||
};
|
||||
|
||||
/* for PCM creation */
|
||||
struct hda_pcm {
|
||||
char *name;
|
||||
struct hda_pcm_stream stream[2];
|
||||
};
|
||||
|
||||
The name can be passed to snd_pcm_new(). The stream field contains
|
||||
the information for playback (SNDRV_PCM_STREAM_PLAYBACK = 0) and
|
||||
capture (SNDRV_PCM_STREAM_CAPTURE = 1) directions. The card driver
|
||||
should pass substreams to snd_pcm_new() for the number of substreams
|
||||
to create.
|
||||
|
||||
The channels_min, channels_max, rates and formats should be copied to
|
||||
runtime->hw record. They and maxbps fields are used also to compute
|
||||
the format value for the HDA codec and controller. Call
|
||||
snd_hda_calc_stream_format() to get the format value.
|
||||
|
||||
The ops field contains the following callback functions:
|
||||
|
||||
struct hda_pcm_ops {
|
||||
int (*open)(struct hda_pcm_stream *info, struct hda_codec *codec,
|
||||
struct snd_pcm_substream *substream);
|
||||
int (*close)(struct hda_pcm_stream *info, struct hda_codec *codec,
|
||||
struct snd_pcm_substream *substream);
|
||||
int (*prepare)(struct hda_pcm_stream *info, struct hda_codec *codec,
|
||||
unsigned int stream_tag, unsigned int format,
|
||||
struct snd_pcm_substream *substream);
|
||||
int (*cleanup)(struct hda_pcm_stream *info, struct hda_codec *codec,
|
||||
struct snd_pcm_substream *substream);
|
||||
};
|
||||
|
||||
All are non-NULL, so you can call them safely without NULL check.
|
||||
|
||||
The open callback should be called in PCM open after runtime->hw is
|
||||
set up. It may override some setting and constraints additionally.
|
||||
Similarly, the close callback should be called in the PCM close.
|
||||
|
||||
The prepare callback should be called in PCM prepare. This will set
|
||||
up the codec chip properly for the operation. The cleanup should be
|
||||
called in hw_free to clean up the configuration.
|
||||
|
||||
The caller should check the return value, at least for open and
|
||||
prepare callbacks. When a negative value is returned, some error
|
||||
occurred.
|
||||
|
||||
|
||||
Proc Files
|
||||
==========
|
||||
|
||||
Each codec dumps the widget node information in
|
||||
/proc/asound/card*/codec#* file. This information would be really
|
||||
helpful for debugging. Please provide its contents together with the
|
||||
bug report.
|
||||
|
||||
|
||||
Power Management
|
||||
================
|
||||
|
||||
It's simple:
|
||||
Call snd_hda_suspend() in the PM suspend callback.
|
||||
Call snd_hda_resume() in the PM resume callback.
|
||||
|
||||
|
||||
Codec Preset (Patch)
|
||||
====================
|
||||
|
||||
To set up and handle the codec functionality fully, each codec may
|
||||
have a codec preset (patch). It's defined in struct hda_codec_preset:
|
||||
|
||||
struct hda_codec_preset {
|
||||
unsigned int id;
|
||||
unsigned int mask;
|
||||
unsigned int subs;
|
||||
unsigned int subs_mask;
|
||||
unsigned int rev;
|
||||
const char *name;
|
||||
int (*patch)(struct hda_codec *codec);
|
||||
};
|
||||
|
||||
When the codec id and codec subsystem id match with the given id and
|
||||
subs fields bitwise (with bitmask mask and subs_mask), the callback
|
||||
patch is called. The patch callback should initialize the codec and
|
||||
set the codec->patch_ops field. This is defined as below:
|
||||
|
||||
struct hda_codec_ops {
|
||||
int (*build_controls)(struct hda_codec *codec);
|
||||
int (*build_pcms)(struct hda_codec *codec);
|
||||
int (*init)(struct hda_codec *codec);
|
||||
void (*free)(struct hda_codec *codec);
|
||||
void (*unsol_event)(struct hda_codec *codec, unsigned int res);
|
||||
#ifdef CONFIG_PM
|
||||
int (*suspend)(struct hda_codec *codec, pm_message_t state);
|
||||
int (*resume)(struct hda_codec *codec);
|
||||
#endif
|
||||
#ifdef CONFIG_SND_HDA_POWER_SAVE
|
||||
int (*check_power_status)(struct hda_codec *codec,
|
||||
hda_nid_t nid);
|
||||
#endif
|
||||
};
|
||||
|
||||
The build_controls callback is called from snd_hda_build_controls().
|
||||
Similarly, the build_pcms callback is called from
|
||||
snd_hda_build_pcms(). The init callback is called after
|
||||
build_controls to initialize the hardware.
|
||||
The free callback is called as a destructor.
|
||||
|
||||
The unsol_event callback is called when an unsolicited event is
|
||||
received.
|
||||
|
||||
The suspend and resume callbacks are for power management.
|
||||
They can be NULL if no special sequence is required. When the resume
|
||||
callback is NULL, the driver calls the init callback and resumes the
|
||||
registers from the cache. If other handling is needed, you'd need to
|
||||
write your own resume callback. There, the amp values can be resumed
|
||||
via
|
||||
void snd_hda_codec_resume_amp(struct hda_codec *codec);
|
||||
and the other codec registers via
|
||||
void snd_hda_codec_resume_cache(struct hda_codec *codec);
|
||||
|
||||
The check_power_status callback is called when the amp value of the
|
||||
given widget NID is changed. The codec code can turn on/off the power
|
||||
appropriately from this information.
|
||||
|
||||
Each entry can be NULL if not necessary to be called.
|
||||
|
||||
|
||||
Generic Parser
|
||||
==============
|
||||
|
||||
When the device doesn't match with any given presets, the widgets are
|
||||
parsed via th generic parser (hda_generic.c). Its support is
|
||||
limited: no multi-channel support, for example.
|
||||
|
||||
|
||||
Digital I/O
|
||||
===========
|
||||
|
||||
Call snd_hda_create_spdif_out_ctls() from the patch to create controls
|
||||
related with SPDIF out.
|
||||
|
||||
|
||||
Helper Functions
|
||||
================
|
||||
|
||||
snd_hda_get_codec_name() stores the codec name on the given string.
|
||||
|
||||
snd_hda_check_board_config() can be used to obtain the configuration
|
||||
information matching with the device. Define the model string table
|
||||
and the table with struct snd_pci_quirk entries (zero-terminated),
|
||||
and pass it to the function. The function checks the modelname given
|
||||
as a module parameter, and PCI subsystem IDs. If the matching entry
|
||||
is found, it returns the config field value.
|
||||
|
||||
snd_hda_add_new_ctls() can be used to create and add control entries.
|
||||
Pass the zero-terminated array of struct snd_kcontrol_new
|
||||
|
||||
Macros HDA_CODEC_VOLUME(), HDA_CODEC_MUTE() and their variables can be
|
||||
used for the entry of struct snd_kcontrol_new.
|
||||
|
||||
The input MUX helper callbacks for such a control are provided, too:
|
||||
snd_hda_input_mux_info() and snd_hda_input_mux_put(). See
|
||||
patch_realtek.c for example.
|
|
@ -3368,6 +3368,7 @@ M: Support Opensource <support.opensource@diasemi.com>
|
|||
W: http://www.dialog-semiconductor.com/products
|
||||
S: Supported
|
||||
F: Documentation/hwmon/da90??
|
||||
F: Documentation/devicetree/bindings/sound/da[79]*.txt
|
||||
F: drivers/gpio/gpio-da90??.c
|
||||
F: drivers/hwmon/da90??-hwmon.c
|
||||
F: drivers/iio/adc/da91??-*.c
|
||||
|
|
|
@ -832,6 +832,7 @@ int i915_driver_load(struct drm_device *dev, unsigned long flags)
|
|||
mutex_init(&dev_priv->sb_lock);
|
||||
mutex_init(&dev_priv->modeset_restore_lock);
|
||||
mutex_init(&dev_priv->csr_lock);
|
||||
mutex_init(&dev_priv->av_mutex);
|
||||
|
||||
intel_pm_setup(dev);
|
||||
|
||||
|
|
|
@ -1885,6 +1885,11 @@ struct drm_i915_private {
|
|||
/* hda/i915 audio component */
|
||||
struct i915_audio_component *audio_component;
|
||||
bool audio_component_registered;
|
||||
/**
|
||||
* av_mutex - mutex for audio/video sync
|
||||
*
|
||||
*/
|
||||
struct mutex av_mutex;
|
||||
|
||||
uint32_t hw_context_size;
|
||||
struct list_head context_list;
|
||||
|
|
|
@ -68,6 +68,31 @@ static const struct {
|
|||
{ 148500, AUD_CONFIG_PIXEL_CLOCK_HDMI_148500 },
|
||||
};
|
||||
|
||||
/* HDMI N/CTS table */
|
||||
#define TMDS_297M 297000
|
||||
#define TMDS_296M DIV_ROUND_UP(297000 * 1000, 1001)
|
||||
static const struct {
|
||||
int sample_rate;
|
||||
int clock;
|
||||
int n;
|
||||
int cts;
|
||||
} aud_ncts[] = {
|
||||
{ 44100, TMDS_296M, 4459, 234375 },
|
||||
{ 44100, TMDS_297M, 4704, 247500 },
|
||||
{ 48000, TMDS_296M, 5824, 281250 },
|
||||
{ 48000, TMDS_297M, 5120, 247500 },
|
||||
{ 32000, TMDS_296M, 5824, 421875 },
|
||||
{ 32000, TMDS_297M, 3072, 222750 },
|
||||
{ 88200, TMDS_296M, 8918, 234375 },
|
||||
{ 88200, TMDS_297M, 9408, 247500 },
|
||||
{ 96000, TMDS_296M, 11648, 281250 },
|
||||
{ 96000, TMDS_297M, 10240, 247500 },
|
||||
{ 176400, TMDS_296M, 17836, 234375 },
|
||||
{ 176400, TMDS_297M, 18816, 247500 },
|
||||
{ 192000, TMDS_296M, 23296, 281250 },
|
||||
{ 192000, TMDS_297M, 20480, 247500 },
|
||||
};
|
||||
|
||||
/* get AUD_CONFIG_PIXEL_CLOCK_HDMI_* value for mode */
|
||||
static u32 audio_config_hdmi_pixel_clock(struct drm_display_mode *mode)
|
||||
{
|
||||
|
@ -90,6 +115,45 @@ static u32 audio_config_hdmi_pixel_clock(struct drm_display_mode *mode)
|
|||
return hdmi_audio_clock[i].config;
|
||||
}
|
||||
|
||||
static int audio_config_get_n(const struct drm_display_mode *mode, int rate)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(aud_ncts); i++) {
|
||||
if ((rate == aud_ncts[i].sample_rate) &&
|
||||
(mode->clock == aud_ncts[i].clock)) {
|
||||
return aud_ncts[i].n;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint32_t audio_config_setup_n_reg(int n, uint32_t val)
|
||||
{
|
||||
int n_low, n_up;
|
||||
uint32_t tmp = val;
|
||||
|
||||
n_low = n & 0xfff;
|
||||
n_up = (n >> 12) & 0xff;
|
||||
tmp &= ~(AUD_CONFIG_UPPER_N_MASK | AUD_CONFIG_LOWER_N_MASK);
|
||||
tmp |= ((n_up << AUD_CONFIG_UPPER_N_SHIFT) |
|
||||
(n_low << AUD_CONFIG_LOWER_N_SHIFT) |
|
||||
AUD_CONFIG_N_PROG_ENABLE);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
/* check whether N/CTS/M need be set manually */
|
||||
static bool audio_rate_need_prog(struct intel_crtc *crtc,
|
||||
const struct drm_display_mode *mode)
|
||||
{
|
||||
if (((mode->clock == TMDS_297M) ||
|
||||
(mode->clock == TMDS_296M)) &&
|
||||
intel_pipe_has_type(crtc, INTEL_OUTPUT_HDMI))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool intel_eld_uptodate(struct drm_connector *connector,
|
||||
int reg_eldv, uint32_t bits_eldv,
|
||||
int reg_elda, uint32_t bits_elda,
|
||||
|
@ -184,6 +248,8 @@ static void hsw_audio_codec_disable(struct intel_encoder *encoder)
|
|||
|
||||
DRM_DEBUG_KMS("Disable audio codec on pipe %c\n", pipe_name(pipe));
|
||||
|
||||
mutex_lock(&dev_priv->av_mutex);
|
||||
|
||||
/* Disable timestamps */
|
||||
tmp = I915_READ(HSW_AUD_CFG(pipe));
|
||||
tmp &= ~AUD_CONFIG_N_VALUE_INDEX;
|
||||
|
@ -199,6 +265,8 @@ static void hsw_audio_codec_disable(struct intel_encoder *encoder)
|
|||
tmp &= ~AUDIO_ELD_VALID(pipe);
|
||||
tmp &= ~AUDIO_OUTPUT_ENABLE(pipe);
|
||||
I915_WRITE(HSW_AUD_PIN_ELD_CP_VLD, tmp);
|
||||
|
||||
mutex_unlock(&dev_priv->av_mutex);
|
||||
}
|
||||
|
||||
static void hsw_audio_codec_enable(struct drm_connector *connector,
|
||||
|
@ -208,13 +276,20 @@ static void hsw_audio_codec_enable(struct drm_connector *connector,
|
|||
struct drm_i915_private *dev_priv = connector->dev->dev_private;
|
||||
struct intel_crtc *intel_crtc = to_intel_crtc(encoder->base.crtc);
|
||||
enum pipe pipe = intel_crtc->pipe;
|
||||
struct i915_audio_component *acomp = dev_priv->audio_component;
|
||||
const uint8_t *eld = connector->eld;
|
||||
struct intel_digital_port *intel_dig_port =
|
||||
enc_to_dig_port(&encoder->base);
|
||||
enum port port = intel_dig_port->port;
|
||||
uint32_t tmp;
|
||||
int len, i;
|
||||
int n, rate;
|
||||
|
||||
DRM_DEBUG_KMS("Enable audio codec on pipe %c, %u bytes ELD\n",
|
||||
pipe_name(pipe), drm_eld_size(eld));
|
||||
|
||||
mutex_lock(&dev_priv->av_mutex);
|
||||
|
||||
/* Enable audio presence detect, invalidate ELD */
|
||||
tmp = I915_READ(HSW_AUD_PIN_ELD_CP_VLD);
|
||||
tmp |= AUDIO_OUTPUT_ENABLE(pipe);
|
||||
|
@ -246,13 +321,32 @@ static void hsw_audio_codec_enable(struct drm_connector *connector,
|
|||
/* Enable timestamps */
|
||||
tmp = I915_READ(HSW_AUD_CFG(pipe));
|
||||
tmp &= ~AUD_CONFIG_N_VALUE_INDEX;
|
||||
tmp &= ~AUD_CONFIG_N_PROG_ENABLE;
|
||||
tmp &= ~AUD_CONFIG_PIXEL_CLOCK_HDMI_MASK;
|
||||
if (intel_pipe_has_type(intel_crtc, INTEL_OUTPUT_DISPLAYPORT))
|
||||
tmp |= AUD_CONFIG_N_VALUE_INDEX;
|
||||
else
|
||||
tmp |= audio_config_hdmi_pixel_clock(mode);
|
||||
|
||||
tmp &= ~AUD_CONFIG_N_PROG_ENABLE;
|
||||
if (audio_rate_need_prog(intel_crtc, mode)) {
|
||||
if (!acomp)
|
||||
rate = 0;
|
||||
else if (port >= PORT_A && port <= PORT_E)
|
||||
rate = acomp->aud_sample_rate[port];
|
||||
else {
|
||||
DRM_ERROR("invalid port: %d\n", port);
|
||||
rate = 0;
|
||||
}
|
||||
n = audio_config_get_n(mode, rate);
|
||||
if (n != 0)
|
||||
tmp = audio_config_setup_n_reg(n, tmp);
|
||||
else
|
||||
DRM_DEBUG_KMS("no suitable N value is found\n");
|
||||
}
|
||||
|
||||
I915_WRITE(HSW_AUD_CFG(pipe), tmp);
|
||||
|
||||
mutex_unlock(&dev_priv->av_mutex);
|
||||
}
|
||||
|
||||
static void ilk_audio_codec_disable(struct intel_encoder *encoder)
|
||||
|
@ -527,12 +621,91 @@ static int i915_audio_component_get_cdclk_freq(struct device *dev)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int i915_audio_component_sync_audio_rate(struct device *dev,
|
||||
int port, int rate)
|
||||
{
|
||||
struct drm_i915_private *dev_priv = dev_to_i915(dev);
|
||||
struct drm_device *drm_dev = dev_priv->dev;
|
||||
struct intel_encoder *intel_encoder;
|
||||
struct intel_digital_port *intel_dig_port;
|
||||
struct intel_crtc *crtc;
|
||||
struct drm_display_mode *mode;
|
||||
struct i915_audio_component *acomp = dev_priv->audio_component;
|
||||
enum pipe pipe = -1;
|
||||
u32 tmp;
|
||||
int n;
|
||||
|
||||
/* HSW, BDW SKL need this fix */
|
||||
if (!IS_SKYLAKE(dev_priv) &&
|
||||
!IS_BROADWELL(dev_priv) &&
|
||||
!IS_HASWELL(dev_priv))
|
||||
return 0;
|
||||
|
||||
mutex_lock(&dev_priv->av_mutex);
|
||||
/* 1. get the pipe */
|
||||
for_each_intel_encoder(drm_dev, intel_encoder) {
|
||||
if (intel_encoder->type != INTEL_OUTPUT_HDMI)
|
||||
continue;
|
||||
intel_dig_port = enc_to_dig_port(&intel_encoder->base);
|
||||
if (port == intel_dig_port->port) {
|
||||
crtc = to_intel_crtc(intel_encoder->base.crtc);
|
||||
if (!crtc) {
|
||||
DRM_DEBUG_KMS("%s: crtc is NULL\n", __func__);
|
||||
continue;
|
||||
}
|
||||
pipe = crtc->pipe;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pipe == INVALID_PIPE) {
|
||||
DRM_DEBUG_KMS("no pipe for the port %c\n", port_name(port));
|
||||
mutex_unlock(&dev_priv->av_mutex);
|
||||
return -ENODEV;
|
||||
}
|
||||
DRM_DEBUG_KMS("pipe %c connects port %c\n",
|
||||
pipe_name(pipe), port_name(port));
|
||||
mode = &crtc->config->base.adjusted_mode;
|
||||
|
||||
/* port must be valid now, otherwise the pipe will be invalid */
|
||||
acomp->aud_sample_rate[port] = rate;
|
||||
|
||||
/* 2. check whether to set the N/CTS/M manually or not */
|
||||
if (!audio_rate_need_prog(crtc, mode)) {
|
||||
tmp = I915_READ(HSW_AUD_CFG(pipe));
|
||||
tmp &= ~AUD_CONFIG_N_PROG_ENABLE;
|
||||
I915_WRITE(HSW_AUD_CFG(pipe), tmp);
|
||||
mutex_unlock(&dev_priv->av_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
n = audio_config_get_n(mode, rate);
|
||||
if (n == 0) {
|
||||
DRM_DEBUG_KMS("Using automatic mode for N value on port %c\n",
|
||||
port_name(port));
|
||||
tmp = I915_READ(HSW_AUD_CFG(pipe));
|
||||
tmp &= ~AUD_CONFIG_N_PROG_ENABLE;
|
||||
I915_WRITE(HSW_AUD_CFG(pipe), tmp);
|
||||
mutex_unlock(&dev_priv->av_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 3. set the N/CTS/M */
|
||||
tmp = I915_READ(HSW_AUD_CFG(pipe));
|
||||
tmp = audio_config_setup_n_reg(n, tmp);
|
||||
I915_WRITE(HSW_AUD_CFG(pipe), tmp);
|
||||
|
||||
mutex_unlock(&dev_priv->av_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i915_audio_component_ops i915_audio_component_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.get_power = i915_audio_component_get_power,
|
||||
.put_power = i915_audio_component_put_power,
|
||||
.codec_wake_override = i915_audio_component_codec_wake_override,
|
||||
.get_cdclk_freq = i915_audio_component_get_cdclk_freq,
|
||||
.sync_audio_rate = i915_audio_component_sync_audio_rate,
|
||||
};
|
||||
|
||||
static int i915_audio_component_bind(struct device *i915_dev,
|
||||
|
@ -540,6 +713,7 @@ static int i915_audio_component_bind(struct device *i915_dev,
|
|||
{
|
||||
struct i915_audio_component *acomp = data;
|
||||
struct drm_i915_private *dev_priv = dev_to_i915(i915_dev);
|
||||
int i;
|
||||
|
||||
if (WARN_ON(acomp->ops || acomp->dev))
|
||||
return -EEXIST;
|
||||
|
@ -547,6 +721,9 @@ static int i915_audio_component_bind(struct device *i915_dev,
|
|||
drm_modeset_lock_all(dev_priv->dev);
|
||||
acomp->ops = &i915_audio_component_ops;
|
||||
acomp->dev = i915_dev;
|
||||
BUILD_BUG_ON(MAX_PORTS != I915_MAX_PORTS);
|
||||
for (i = 0; i < ARRAY_SIZE(acomp->aud_sample_rate); i++)
|
||||
acomp->aud_sample_rate[i] = 0;
|
||||
dev_priv->audio_component = acomp;
|
||||
drm_modeset_unlock_all(dev_priv->dev);
|
||||
|
||||
|
|
|
@ -871,14 +871,7 @@ static int atmel_spi_set_xfer_speed(struct atmel_spi *as,
|
|||
* Calculate the lowest divider that satisfies the
|
||||
* constraint, assuming div32/fdiv/mbz == 0.
|
||||
*/
|
||||
if (xfer->speed_hz)
|
||||
scbr = DIV_ROUND_UP(bus_hz, xfer->speed_hz);
|
||||
else
|
||||
/*
|
||||
* This can happend if max_speed is null.
|
||||
* In this case, we set the lowest possible speed
|
||||
*/
|
||||
scbr = 0xff;
|
||||
scbr = DIV_ROUND_UP(bus_hz, xfer->speed_hz);
|
||||
|
||||
/*
|
||||
* If the resulting divider doesn't fit into the
|
||||
|
@ -1300,14 +1293,12 @@ static int atmel_spi_one_transfer(struct spi_master *master,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (xfer->bits_per_word) {
|
||||
asd = spi->controller_state;
|
||||
bits = (asd->csr >> 4) & 0xf;
|
||||
if (bits != xfer->bits_per_word - 8) {
|
||||
dev_dbg(&spi->dev,
|
||||
asd = spi->controller_state;
|
||||
bits = (asd->csr >> 4) & 0xf;
|
||||
if (bits != xfer->bits_per_word - 8) {
|
||||
dev_dbg(&spi->dev,
|
||||
"you can't yet change bits_per_word in transfers\n");
|
||||
return -ENOPROTOOPT;
|
||||
}
|
||||
return -ENOPROTOOPT;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -24,8 +24,18 @@
|
|||
#ifndef _I915_COMPONENT_H_
|
||||
#define _I915_COMPONENT_H_
|
||||
|
||||
/* MAX_PORT is the number of port
|
||||
* It must be sync with I915_MAX_PORTS defined i915_drv.h
|
||||
* 5 should be enough as only HSW, BDW, SKL need such fix.
|
||||
*/
|
||||
#define MAX_PORTS 5
|
||||
|
||||
struct i915_audio_component {
|
||||
struct device *dev;
|
||||
/**
|
||||
* @aud_sample_rate: the array of audio sample rate per port
|
||||
*/
|
||||
int aud_sample_rate[MAX_PORTS];
|
||||
|
||||
const struct i915_audio_component_ops {
|
||||
struct module *owner;
|
||||
|
@ -33,6 +43,13 @@ struct i915_audio_component {
|
|||
void (*put_power)(struct device *);
|
||||
void (*codec_wake_override)(struct device *, bool enable);
|
||||
int (*get_cdclk_freq)(struct device *);
|
||||
/**
|
||||
* @sync_audio_rate: set n/cts based on the sample rate
|
||||
*
|
||||
* Called from audio driver. After audio driver sets the
|
||||
* sample rate, it will call this function to set n/cts
|
||||
*/
|
||||
int (*sync_audio_rate)(struct device *, int port, int rate);
|
||||
} *ops;
|
||||
|
||||
const struct i915_audio_component_audio_ops {
|
||||
|
|
|
@ -219,6 +219,14 @@ struct serio_device_id {
|
|||
__u8 proto;
|
||||
};
|
||||
|
||||
struct hda_device_id {
|
||||
__u32 vendor_id;
|
||||
__u32 rev_id;
|
||||
__u8 api_version;
|
||||
const char *name;
|
||||
unsigned long driver_data;
|
||||
};
|
||||
|
||||
/*
|
||||
* Struct used for matching a device
|
||||
*/
|
||||
|
|
|
@ -44,9 +44,6 @@ struct da7213_platform_data {
|
|||
enum da7213_dmic_data_sel dmic_data_sel;
|
||||
enum da7213_dmic_samplephase dmic_samplephase;
|
||||
enum da7213_dmic_clk_rate dmic_clk_rate;
|
||||
|
||||
/* MCLK squaring config */
|
||||
bool mclk_squaring;
|
||||
};
|
||||
|
||||
#endif /* _DA7213_PDATA_H */
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* da7219-aad.h - DA7322 ASoC Codec AAD Driver Platform Data
|
||||
*
|
||||
* Copyright (c) 2015 Dialog Semiconductor Ltd.
|
||||
*
|
||||
* Author: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*/
|
||||
|
||||
#ifndef __DA7219_AAD_PDATA_H
|
||||
#define __DA7219_AAD_PDATA_H
|
||||
|
||||
enum da7219_aad_micbias_pulse_lvl {
|
||||
DA7219_AAD_MICBIAS_PULSE_LVL_OFF = 0,
|
||||
DA7219_AAD_MICBIAS_PULSE_LVL_2_8V = 6,
|
||||
DA7219_AAD_MICBIAS_PULSE_LVL_2_9V,
|
||||
};
|
||||
|
||||
enum da7219_aad_btn_cfg {
|
||||
DA7219_AAD_BTN_CFG_2MS = 1,
|
||||
DA7219_AAD_BTN_CFG_5MS,
|
||||
DA7219_AAD_BTN_CFG_10MS,
|
||||
DA7219_AAD_BTN_CFG_50MS,
|
||||
DA7219_AAD_BTN_CFG_100MS,
|
||||
DA7219_AAD_BTN_CFG_200MS,
|
||||
DA7219_AAD_BTN_CFG_500MS,
|
||||
};
|
||||
|
||||
enum da7219_aad_mic_det_thr {
|
||||
DA7219_AAD_MIC_DET_THR_200_OHMS = 0,
|
||||
DA7219_AAD_MIC_DET_THR_500_OHMS,
|
||||
DA7219_AAD_MIC_DET_THR_750_OHMS,
|
||||
DA7219_AAD_MIC_DET_THR_1000_OHMS,
|
||||
};
|
||||
|
||||
enum da7219_aad_jack_ins_deb {
|
||||
DA7219_AAD_JACK_INS_DEB_5MS = 0,
|
||||
DA7219_AAD_JACK_INS_DEB_10MS,
|
||||
DA7219_AAD_JACK_INS_DEB_20MS,
|
||||
DA7219_AAD_JACK_INS_DEB_50MS,
|
||||
DA7219_AAD_JACK_INS_DEB_100MS,
|
||||
DA7219_AAD_JACK_INS_DEB_200MS,
|
||||
DA7219_AAD_JACK_INS_DEB_500MS,
|
||||
DA7219_AAD_JACK_INS_DEB_1S,
|
||||
};
|
||||
|
||||
enum da7219_aad_jack_det_rate {
|
||||
DA7219_AAD_JACK_DET_RATE_32_64MS = 0,
|
||||
DA7219_AAD_JACK_DET_RATE_64_128MS,
|
||||
DA7219_AAD_JACK_DET_RATE_128_256MS,
|
||||
DA7219_AAD_JACK_DET_RATE_256_512MS,
|
||||
};
|
||||
|
||||
enum da7219_aad_jack_rem_deb {
|
||||
DA7219_AAD_JACK_REM_DEB_1MS = 0,
|
||||
DA7219_AAD_JACK_REM_DEB_5MS,
|
||||
DA7219_AAD_JACK_REM_DEB_10MS,
|
||||
DA7219_AAD_JACK_REM_DEB_20MS,
|
||||
};
|
||||
|
||||
enum da7219_aad_btn_avg {
|
||||
DA7219_AAD_BTN_AVG_1 = 0,
|
||||
DA7219_AAD_BTN_AVG_2,
|
||||
DA7219_AAD_BTN_AVG_4,
|
||||
DA7219_AAD_BTN_AVG_8,
|
||||
};
|
||||
|
||||
enum da7219_aad_adc_1bit_rpt {
|
||||
DA7219_AAD_ADC_1BIT_RPT_1 = 0,
|
||||
DA7219_AAD_ADC_1BIT_RPT_2,
|
||||
DA7219_AAD_ADC_1BIT_RPT_4,
|
||||
DA7219_AAD_ADC_1BIT_RPT_8,
|
||||
};
|
||||
|
||||
struct da7219_aad_pdata {
|
||||
int irq;
|
||||
|
||||
enum da7219_aad_micbias_pulse_lvl micbias_pulse_lvl;
|
||||
u32 micbias_pulse_time;
|
||||
enum da7219_aad_btn_cfg btn_cfg;
|
||||
enum da7219_aad_mic_det_thr mic_det_thr;
|
||||
enum da7219_aad_jack_ins_deb jack_ins_deb;
|
||||
enum da7219_aad_jack_det_rate jack_det_rate;
|
||||
enum da7219_aad_jack_rem_deb jack_rem_deb;
|
||||
|
||||
u8 a_d_btn_thr;
|
||||
u8 d_b_btn_thr;
|
||||
u8 b_c_btn_thr;
|
||||
u8 c_mic_btn_thr;
|
||||
|
||||
enum da7219_aad_btn_avg btn_avg;
|
||||
enum da7219_aad_adc_1bit_rpt adc_1bit_rpt;
|
||||
};
|
||||
|
||||
#endif /* __DA7219_AAD_PDATA_H */
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* da7219.h - DA7219 ASoC Codec Driver Platform Data
|
||||
*
|
||||
* Copyright (c) 2015 Dialog Semiconductor
|
||||
*
|
||||
* Author: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*/
|
||||
|
||||
#ifndef __DA7219_PDATA_H
|
||||
#define __DA7219_PDATA_H
|
||||
|
||||
/* LDO */
|
||||
enum da7219_ldo_lvl_sel {
|
||||
DA7219_LDO_LVL_SEL_1_05V = 0,
|
||||
DA7219_LDO_LVL_SEL_1_10V,
|
||||
DA7219_LDO_LVL_SEL_1_20V,
|
||||
DA7219_LDO_LVL_SEL_1_40V,
|
||||
};
|
||||
|
||||
/* Mic Bias */
|
||||
enum da7219_micbias_voltage {
|
||||
DA7219_MICBIAS_1_8V = 1,
|
||||
DA7219_MICBIAS_2_0V,
|
||||
DA7219_MICBIAS_2_2V,
|
||||
DA7219_MICBIAS_2_4V,
|
||||
DA7219_MICBIAS_2_6V,
|
||||
};
|
||||
|
||||
/* Mic input type */
|
||||
enum da7219_mic_amp_in_sel {
|
||||
DA7219_MIC_AMP_IN_SEL_DIFF = 0,
|
||||
DA7219_MIC_AMP_IN_SEL_SE_P,
|
||||
DA7219_MIC_AMP_IN_SEL_SE_N,
|
||||
};
|
||||
|
||||
struct da7219_aad_pdata;
|
||||
|
||||
struct da7219_pdata {
|
||||
/* Internal LDO */
|
||||
enum da7219_ldo_lvl_sel ldo_lvl_sel;
|
||||
|
||||
/* Mic */
|
||||
enum da7219_micbias_voltage micbias_lvl;
|
||||
enum da7219_mic_amp_in_sel mic_amp_in_sel;
|
||||
|
||||
/* AAD */
|
||||
struct da7219_aad_pdata *aad_pdata;
|
||||
};
|
||||
|
||||
#endif /* __DA7219_PDATA_H */
|
|
@ -38,6 +38,8 @@ struct i2s_clk_config_data {
|
|||
struct i2s_platform_data {
|
||||
#define DWC_I2S_PLAY (1 << 0)
|
||||
#define DWC_I2S_RECORD (1 << 1)
|
||||
#define DW_I2S_SLAVE (1 << 2)
|
||||
#define DW_I2S_MASTER (1 << 3)
|
||||
unsigned int cap;
|
||||
int channel;
|
||||
u32 snd_fmts;
|
||||
|
|
|
@ -67,7 +67,7 @@ int snd_hdac_regmap_update_raw(struct hdac_device *codec, unsigned int reg,
|
|||
* @reg: verb to write
|
||||
* @val: value to write
|
||||
*
|
||||
* For writing an amp value, use snd_hda_regmap_amp_update().
|
||||
* For writing an amp value, use snd_hdac_regmap_update_amp().
|
||||
*/
|
||||
static inline int
|
||||
snd_hdac_regmap_write(struct hdac_device *codec, hda_nid_t nid,
|
||||
|
@ -85,7 +85,7 @@ snd_hdac_regmap_write(struct hdac_device *codec, hda_nid_t nid,
|
|||
* @mask: bit mask to update
|
||||
* @val: value to update
|
||||
*
|
||||
* For updating an amp value, use snd_hda_regmap_amp_update().
|
||||
* For updating an amp value, use snd_hdac_regmap_update_amp().
|
||||
*/
|
||||
static inline int
|
||||
snd_hdac_regmap_update(struct hdac_device *codec, hda_nid_t nid,
|
||||
|
|
|
@ -21,22 +21,13 @@ struct hdac_stream;
|
|||
struct hdac_device;
|
||||
struct hdac_driver;
|
||||
struct hdac_widget_tree;
|
||||
struct hda_device_id;
|
||||
|
||||
/*
|
||||
* exported bus type
|
||||
*/
|
||||
extern struct bus_type snd_hda_bus_type;
|
||||
|
||||
/*
|
||||
* HDA device table
|
||||
*/
|
||||
struct hda_device_id {
|
||||
__u32 vendor_id;
|
||||
__u32 rev_id;
|
||||
const char *name;
|
||||
unsigned long driver_data;
|
||||
};
|
||||
|
||||
/*
|
||||
* generic arrays
|
||||
*/
|
||||
|
@ -117,6 +108,8 @@ int snd_hdac_device_init(struct hdac_device *dev, struct hdac_bus *bus,
|
|||
void snd_hdac_device_exit(struct hdac_device *dev);
|
||||
int snd_hdac_device_register(struct hdac_device *codec);
|
||||
void snd_hdac_device_unregister(struct hdac_device *codec);
|
||||
int snd_hdac_device_set_chip_name(struct hdac_device *codec, const char *name);
|
||||
int snd_hdac_codec_modalias(struct hdac_device *hdac, char *buf, size_t size);
|
||||
|
||||
int snd_hdac_refresh_widgets(struct hdac_device *codec);
|
||||
int snd_hdac_refresh_widget_sysfs(struct hdac_device *codec);
|
||||
|
@ -147,6 +140,12 @@ int snd_hdac_query_supported_pcm(struct hdac_device *codec, hda_nid_t nid,
|
|||
bool snd_hdac_is_supported_format(struct hdac_device *codec, hda_nid_t nid,
|
||||
unsigned int format);
|
||||
|
||||
int snd_hdac_codec_read(struct hdac_device *hdac, hda_nid_t nid,
|
||||
int flags, unsigned int verb, unsigned int parm);
|
||||
int snd_hdac_codec_write(struct hdac_device *hdac, hda_nid_t nid,
|
||||
int flags, unsigned int verb, unsigned int parm);
|
||||
bool snd_hdac_check_power_state(struct hdac_device *hdac,
|
||||
hda_nid_t nid, unsigned int target_state);
|
||||
/**
|
||||
* snd_hdac_read_parm - read a codec parameter
|
||||
* @codec: the codec object
|
||||
|
|
|
@ -40,6 +40,13 @@ void snd_hdac_ext_bus_device_remove(struct hdac_ext_bus *ebus);
|
|||
#define hbus_to_ebus(_bus) \
|
||||
container_of(_bus, struct hdac_ext_bus, bus)
|
||||
|
||||
#define HDA_CODEC_REV_EXT_ENTRY(_vid, _rev, _name, drv_data) \
|
||||
{ .vendor_id = (_vid), .rev_id = (_rev), .name = (_name), \
|
||||
.api_version = HDA_DEV_ASOC, \
|
||||
.driver_data = (unsigned long)(drv_data) }
|
||||
#define HDA_CODEC_EXT_ENTRY(_vid, _revid, _name, _drv_data) \
|
||||
HDA_CODEC_REV_EXT_ENTRY(_vid, _revid, _name, _drv_data)
|
||||
|
||||
int snd_hdac_ext_bus_parse_capabilities(struct hdac_ext_bus *sbus);
|
||||
void snd_hdac_ext_bus_ppcap_enable(struct hdac_ext_bus *chip, bool enable);
|
||||
void snd_hdac_ext_bus_ppcap_int_enable(struct hdac_ext_bus *chip, bool enable);
|
||||
|
|
|
@ -265,12 +265,12 @@ struct snd_ratden {
|
|||
|
||||
struct snd_pcm_hw_constraint_ratnums {
|
||||
int nrats;
|
||||
struct snd_ratnum *rats;
|
||||
const struct snd_ratnum *rats;
|
||||
};
|
||||
|
||||
struct snd_pcm_hw_constraint_ratdens {
|
||||
int nrats;
|
||||
struct snd_ratden *rats;
|
||||
const struct snd_ratden *rats;
|
||||
};
|
||||
|
||||
struct snd_pcm_hw_constraint_list {
|
||||
|
@ -285,8 +285,6 @@ struct snd_pcm_hw_constraint_ranges {
|
|||
unsigned int mask;
|
||||
};
|
||||
|
||||
struct snd_pcm_hwptr_log;
|
||||
|
||||
/*
|
||||
* userspace-provided audio timestamp config to kernel,
|
||||
* structure is for internal use only and filled with dedicated unpack routine
|
||||
|
@ -404,10 +402,6 @@ struct snd_pcm_runtime {
|
|||
struct snd_pcm_hardware hw;
|
||||
struct snd_pcm_hw_constraints hw_constraints;
|
||||
|
||||
/* -- interrupt callbacks -- */
|
||||
void (*transfer_ack_begin)(struct snd_pcm_substream *substream);
|
||||
void (*transfer_ack_end)(struct snd_pcm_substream *substream);
|
||||
|
||||
/* -- timer -- */
|
||||
unsigned int timer_resolution; /* timer resolution */
|
||||
int tstamp_type; /* timestamp type */
|
||||
|
@ -428,10 +422,6 @@ struct snd_pcm_runtime {
|
|||
/* -- OSS things -- */
|
||||
struct snd_pcm_oss_runtime oss;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_SND_PCM_XRUN_DEBUG
|
||||
struct snd_pcm_hwptr_log *hwptr_log;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct snd_pcm_group { /* keep linked substreams */
|
||||
|
@ -980,7 +970,7 @@ int snd_interval_list(struct snd_interval *i, unsigned int count,
|
|||
int snd_interval_ranges(struct snd_interval *i, unsigned int count,
|
||||
const struct snd_interval *list, unsigned int mask);
|
||||
int snd_interval_ratnum(struct snd_interval *i,
|
||||
unsigned int rats_count, struct snd_ratnum *rats,
|
||||
unsigned int rats_count, const struct snd_ratnum *rats,
|
||||
unsigned int *nump, unsigned int *denp);
|
||||
|
||||
void _snd_pcm_hw_params_any(struct snd_pcm_hw_params *params);
|
||||
|
@ -1010,11 +1000,11 @@ int snd_pcm_hw_constraint_ranges(struct snd_pcm_runtime *runtime,
|
|||
int snd_pcm_hw_constraint_ratnums(struct snd_pcm_runtime *runtime,
|
||||
unsigned int cond,
|
||||
snd_pcm_hw_param_t var,
|
||||
struct snd_pcm_hw_constraint_ratnums *r);
|
||||
const struct snd_pcm_hw_constraint_ratnums *r);
|
||||
int snd_pcm_hw_constraint_ratdens(struct snd_pcm_runtime *runtime,
|
||||
unsigned int cond,
|
||||
snd_pcm_hw_param_t var,
|
||||
struct snd_pcm_hw_constraint_ratdens *r);
|
||||
const struct snd_pcm_hw_constraint_ratdens *r);
|
||||
int snd_pcm_hw_constraint_msbits(struct snd_pcm_runtime *runtime,
|
||||
unsigned int cond,
|
||||
unsigned int width,
|
||||
|
@ -1034,6 +1024,22 @@ int snd_pcm_hw_rule_add(struct snd_pcm_runtime *runtime,
|
|||
snd_pcm_hw_rule_func_t func, void *private,
|
||||
int dep, ...);
|
||||
|
||||
/**
|
||||
* snd_pcm_hw_constraint_single() - Constrain parameter to a single value
|
||||
* @runtime: PCM runtime instance
|
||||
* @var: The hw_params variable to constrain
|
||||
* @val: The value to constrain to
|
||||
*
|
||||
* Return: Positive if the value is changed, zero if it's not changed, or a
|
||||
* negative error code.
|
||||
*/
|
||||
static inline int snd_pcm_hw_constraint_single(
|
||||
struct snd_pcm_runtime *runtime, snd_pcm_hw_param_t var,
|
||||
unsigned int val)
|
||||
{
|
||||
return snd_pcm_hw_constraint_minmax(runtime, var, val, val);
|
||||
}
|
||||
|
||||
int snd_pcm_format_signed(snd_pcm_format_t format);
|
||||
int snd_pcm_format_unsigned(snd_pcm_format_t format);
|
||||
int snd_pcm_format_linear(snd_pcm_format_t format);
|
||||
|
@ -1117,10 +1123,16 @@ static inline void snd_pcm_set_runtime_buffer(struct snd_pcm_substream *substrea
|
|||
* Timer interface
|
||||
*/
|
||||
|
||||
#ifdef CONFIG_SND_PCM_TIMER
|
||||
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);
|
||||
|
||||
#else
|
||||
static inline void
|
||||
snd_pcm_timer_resolution_change(struct snd_pcm_substream *substream) {}
|
||||
static inline void snd_pcm_timer_init(struct snd_pcm_substream *substream) {}
|
||||
static inline void snd_pcm_timer_done(struct snd_pcm_substream *substream) {}
|
||||
#endif
|
||||
/**
|
||||
* snd_pcm_gettime - Fill the timespec depending on the timestamp mode
|
||||
* @runtime: PCM runtime instance
|
||||
|
|
|
@ -12,7 +12,6 @@ extern int __pxa2xx_pcm_hw_free(struct snd_pcm_substream *substream);
|
|||
extern int pxa2xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd);
|
||||
extern snd_pcm_uframes_t pxa2xx_pcm_pointer(struct snd_pcm_substream *substream);
|
||||
extern int __pxa2xx_pcm_prepare(struct snd_pcm_substream *substream);
|
||||
extern void pxa2xx_pcm_dma_irq(int dma_ch, void *dev_id);
|
||||
extern int __pxa2xx_pcm_open(struct snd_pcm_substream *substream);
|
||||
extern int __pxa2xx_pcm_close(struct snd_pcm_substream *substream);
|
||||
extern int pxa2xx_pcm_mmap(struct snd_pcm_substream *substream,
|
||||
|
|
|
@ -12,9 +12,10 @@
|
|||
#define __LINUX_SND_RT5640_H
|
||||
|
||||
struct rt5640_platform_data {
|
||||
/* IN1 & IN2 can optionally be differential */
|
||||
/* IN1 & IN2 & IN3 can optionally be differential */
|
||||
bool in1_diff;
|
||||
bool in2_diff;
|
||||
bool in3_diff;
|
||||
|
||||
bool dmic_en;
|
||||
bool dmic1_data_pin; /* 0 = IN1P; 1 = GPIO3 */
|
||||
|
|
|
@ -21,6 +21,8 @@ struct rt5645_platform_data {
|
|||
/* 0 = IN2P; 1 = GPIO6; 2 = GPIO10; 3 = GPIO12 */
|
||||
|
||||
unsigned int jd_mode;
|
||||
/* Invert JD when jack insert */
|
||||
bool jd_invert;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -19,6 +19,8 @@ struct asoc_simple_dai {
|
|||
unsigned int sysclk;
|
||||
int slots;
|
||||
int slot_width;
|
||||
unsigned int tx_slot_mask;
|
||||
unsigned int rx_slot_mask;
|
||||
struct clk *clk;
|
||||
};
|
||||
|
||||
|
|
|
@ -48,10 +48,25 @@ struct snd_compr_stream;
|
|||
#define SND_SOC_DAIFMT_GATED (0 << 4) /* clock is gated */
|
||||
|
||||
/*
|
||||
* DAI hardware signal inversions.
|
||||
* DAI hardware signal polarity.
|
||||
*
|
||||
* Specifies whether the DAI can also support inverted clocks for the specified
|
||||
* format.
|
||||
*
|
||||
* BCLK:
|
||||
* - "normal" polarity means signal is available at rising edge of BCLK
|
||||
* - "inverted" polarity means signal is available at falling edge of BCLK
|
||||
*
|
||||
* FSYNC "normal" polarity depends on the frame format:
|
||||
* - I2S: frame consists of left then right channel data. Left channel starts
|
||||
* with falling FSYNC edge, right channel starts with rising FSYNC edge.
|
||||
* - Left/Right Justified: frame consists of left then right channel data.
|
||||
* Left channel starts with rising FSYNC edge, right channel starts with
|
||||
* falling FSYNC edge.
|
||||
* - DSP A/B: Frame starts with rising FSYNC edge.
|
||||
* - AC97: Frame starts with rising FSYNC edge.
|
||||
*
|
||||
* "Negative" FSYNC polarity is the one opposite of "normal" polarity.
|
||||
*/
|
||||
#define SND_SOC_DAIFMT_NB_NF (0 << 8) /* normal bit clock + frame */
|
||||
#define SND_SOC_DAIFMT_NB_IF (2 << 8) /* normal BCLK + inv FRM */
|
||||
|
@ -214,7 +229,7 @@ struct snd_soc_dai_driver {
|
|||
int (*suspend)(struct snd_soc_dai *dai);
|
||||
int (*resume)(struct snd_soc_dai *dai);
|
||||
/* compress dai */
|
||||
bool compress_dai;
|
||||
int (*compress_new)(struct snd_soc_pcm_runtime *rtd, int num);
|
||||
/* DAI is also used for the control bus */
|
||||
bool bus_control;
|
||||
|
||||
|
|
|
@ -451,6 +451,9 @@ int snd_soc_dapm_dai_get_connected_widgets(struct snd_soc_dai *dai, int stream,
|
|||
struct snd_soc_dapm_context *snd_soc_dapm_kcontrol_dapm(
|
||||
struct snd_kcontrol *kcontrol);
|
||||
|
||||
struct snd_soc_dapm_widget *snd_soc_dapm_kcontrol_widget(
|
||||
struct snd_kcontrol *kcontrol);
|
||||
|
||||
int snd_soc_dapm_force_bias_level(struct snd_soc_dapm_context *dapm,
|
||||
enum snd_soc_bias_level level);
|
||||
|
||||
|
|
|
@ -217,6 +217,13 @@
|
|||
.get = xhandler_get, .put = xhandler_put, \
|
||||
.private_value = \
|
||||
SOC_DOUBLE_VALUE(reg, shift_left, shift_right, max, invert, 0) }
|
||||
#define SOC_DOUBLE_R_EXT(xname, reg_left, reg_right, xshift, xmax, xinvert,\
|
||||
xhandler_get, xhandler_put) \
|
||||
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
|
||||
.info = snd_soc_info_volsw, \
|
||||
.get = xhandler_get, .put = xhandler_put, \
|
||||
.private_value = SOC_DOUBLE_R_VALUE(reg_left, reg_right, xshift, \
|
||||
xmax, xinvert) }
|
||||
#define SOC_SINGLE_EXT_TLV(xname, xreg, xshift, xmax, xinvert,\
|
||||
xhandler_get, xhandler_put, tlv_array) \
|
||||
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
|
||||
|
@ -226,6 +233,18 @@
|
|||
.info = snd_soc_info_volsw, \
|
||||
.get = xhandler_get, .put = xhandler_put, \
|
||||
.private_value = SOC_SINGLE_VALUE(xreg, xshift, xmax, xinvert, 0) }
|
||||
#define SOC_SINGLE_RANGE_EXT_TLV(xname, xreg, xshift, xmin, xmax, xinvert, \
|
||||
xhandler_get, xhandler_put, tlv_array) \
|
||||
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\
|
||||
.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
|
||||
SNDRV_CTL_ELEM_ACCESS_READWRITE,\
|
||||
.tlv.p = (tlv_array), \
|
||||
.info = snd_soc_info_volsw_range, \
|
||||
.get = xhandler_get, .put = xhandler_put, \
|
||||
.private_value = (unsigned long)&(struct soc_mixer_control) \
|
||||
{.reg = xreg, .rreg = xreg, .shift = xshift, \
|
||||
.rshift = xshift, .min = xmin, .max = xmax, \
|
||||
.platform_max = xmax, .invert = xinvert} }
|
||||
#define SOC_DOUBLE_EXT_TLV(xname, xreg, shift_left, shift_right, xmax, xinvert,\
|
||||
xhandler_get, xhandler_put, tlv_array) \
|
||||
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
|
||||
|
@ -440,7 +459,9 @@ int snd_soc_platform_read(struct snd_soc_platform *platform,
|
|||
int snd_soc_platform_write(struct snd_soc_platform *platform,
|
||||
unsigned int reg, unsigned int val);
|
||||
int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num);
|
||||
int soc_new_compress(struct snd_soc_pcm_runtime *rtd, int num);
|
||||
#ifdef CONFIG_SND_SOC_COMPRESS
|
||||
int snd_soc_new_compress(struct snd_soc_pcm_runtime *rtd, int num);
|
||||
#endif
|
||||
|
||||
struct snd_pcm_substream *snd_soc_get_dai_substream(struct snd_soc_card *card,
|
||||
const char *dai_link, int stream);
|
||||
|
@ -593,7 +614,7 @@ int snd_soc_put_volsw_range(struct snd_kcontrol *kcontrol,
|
|||
struct snd_ctl_elem_value *ucontrol);
|
||||
int snd_soc_get_volsw_range(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol);
|
||||
int snd_soc_limit_volume(struct snd_soc_codec *codec,
|
||||
int snd_soc_limit_volume(struct snd_soc_card *card,
|
||||
const char *name, int max);
|
||||
int snd_soc_bytes_info(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_info *uinfo);
|
||||
|
@ -1603,6 +1624,8 @@ int snd_soc_of_parse_card_name(struct snd_soc_card *card,
|
|||
int snd_soc_of_parse_audio_simple_widgets(struct snd_soc_card *card,
|
||||
const char *propname);
|
||||
int snd_soc_of_parse_tdm_slot(struct device_node *np,
|
||||
unsigned int *tx_mask,
|
||||
unsigned int *rx_mask,
|
||||
unsigned int *slots,
|
||||
unsigned int *slot_width);
|
||||
void snd_soc_of_parse_audio_prefix(struct snd_soc_card *card,
|
||||
|
|
|
@ -83,7 +83,7 @@
|
|||
#define SND_SOC_TPLG_NUM_TEXTS 16
|
||||
|
||||
/* ABI version */
|
||||
#define SND_SOC_TPLG_ABI_VERSION 0x3
|
||||
#define SND_SOC_TPLG_ABI_VERSION 0x4
|
||||
|
||||
/* Max size of TLV data */
|
||||
#define SND_SOC_TPLG_TLV_SIZE 32
|
||||
|
@ -103,7 +103,8 @@
|
|||
#define SND_SOC_TPLG_TYPE_PCM 7
|
||||
#define SND_SOC_TPLG_TYPE_MANIFEST 8
|
||||
#define SND_SOC_TPLG_TYPE_CODEC_LINK 9
|
||||
#define SND_SOC_TPLG_TYPE_PDATA 10
|
||||
#define SND_SOC_TPLG_TYPE_BACKEND_LINK 10
|
||||
#define SND_SOC_TPLG_TYPE_PDATA 11
|
||||
#define SND_SOC_TPLG_TYPE_MAX SND_SOC_TPLG_TYPE_PDATA
|
||||
|
||||
/* vendor block IDs - please add new vendor types to end */
|
||||
|
@ -198,7 +199,7 @@ struct snd_soc_tplg_ctl_hdr {
|
|||
struct snd_soc_tplg_stream_caps {
|
||||
__le32 size; /* in bytes of this structure */
|
||||
char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
|
||||
__le64 formats[SND_SOC_TPLG_MAX_FORMATS]; /* supported formats SNDRV_PCM_FMTBIT_* */
|
||||
__le64 formats; /* supported formats SNDRV_PCM_FMTBIT_* */
|
||||
__le32 rates; /* supported rates SNDRV_PCM_RATE_* */
|
||||
__le32 rate_min; /* min rate */
|
||||
__le32 rate_max; /* max rate */
|
||||
|
@ -217,23 +218,12 @@ struct snd_soc_tplg_stream_caps {
|
|||
*/
|
||||
struct snd_soc_tplg_stream {
|
||||
__le32 size; /* in bytes of this structure */
|
||||
char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; /* Name of the stream */
|
||||
__le64 format; /* SNDRV_PCM_FMTBIT_* */
|
||||
__le32 rate; /* SNDRV_PCM_RATE_* */
|
||||
__le32 period_bytes; /* size of period in bytes */
|
||||
__le32 buffer_bytes; /* size of buffer in bytes */
|
||||
__le32 channels; /* channels */
|
||||
__le32 tdm_slot; /* optional BE bitmask of supported TDM slots */
|
||||
__le32 dai_fmt; /* SND_SOC_DAIFMT_ */
|
||||
} __attribute__((packed));
|
||||
|
||||
/*
|
||||
* Duplex stream configuration supported by SW/FW.
|
||||
*/
|
||||
struct snd_soc_tplg_stream_config {
|
||||
__le32 size; /* in bytes of this structure */
|
||||
char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
|
||||
struct snd_soc_tplg_stream playback;
|
||||
struct snd_soc_tplg_stream capture;
|
||||
} __attribute__((packed));
|
||||
|
||||
/*
|
||||
|
@ -366,11 +356,11 @@ struct snd_soc_tplg_dapm_widget {
|
|||
__le32 shift; /* bits to shift */
|
||||
__le32 mask; /* non-shifted mask */
|
||||
__le32 subseq; /* sort within widget type */
|
||||
__u32 invert; /* invert the power bit */
|
||||
__u32 ignore_suspend; /* kept enabled over suspend */
|
||||
__u16 event_flags;
|
||||
__u16 event_type;
|
||||
__u16 num_kcontrols;
|
||||
__le32 invert; /* invert the power bit */
|
||||
__le32 ignore_suspend; /* kept enabled over suspend */
|
||||
__le16 event_flags;
|
||||
__le16 event_type;
|
||||
__le32 num_kcontrols;
|
||||
struct snd_soc_tplg_private priv;
|
||||
/*
|
||||
* kcontrols that relate to this widget
|
||||
|
@ -378,30 +368,46 @@ struct snd_soc_tplg_dapm_widget {
|
|||
*/
|
||||
} __attribute__((packed));
|
||||
|
||||
struct snd_soc_tplg_pcm_cfg_caps {
|
||||
struct snd_soc_tplg_stream_caps caps;
|
||||
struct snd_soc_tplg_stream_config configs[SND_SOC_TPLG_STREAM_CONFIG_MAX];
|
||||
__le32 num_configs; /* number of configs */
|
||||
} __attribute__((packed));
|
||||
|
||||
/*
|
||||
* Describes SW/FW specific features of PCM or DAI link.
|
||||
* Describes SW/FW specific features of PCM (FE DAI & DAI link).
|
||||
*
|
||||
* File block representation for PCM/DAI-Link :-
|
||||
* File block representation for PCM :-
|
||||
* +-----------------------------------+-----+
|
||||
* | struct snd_soc_tplg_hdr | 1 |
|
||||
* +-----------------------------------+-----+
|
||||
* | struct snd_soc_tplg_dapm_pcm_dai | N |
|
||||
* | struct snd_soc_tplg_pcm | N |
|
||||
* +-----------------------------------+-----+
|
||||
*/
|
||||
struct snd_soc_tplg_pcm_dai {
|
||||
struct snd_soc_tplg_pcm {
|
||||
__le32 size; /* in bytes of this structure */
|
||||
char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
|
||||
__le32 id; /* unique ID - used to match */
|
||||
__le32 playback; /* supports playback mode */
|
||||
__le32 capture; /* supports capture mode */
|
||||
__le32 compress; /* 1 = compressed; 0 = PCM */
|
||||
struct snd_soc_tplg_pcm_cfg_caps capconf[2]; /* capabilities and configs */
|
||||
char pcm_name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
|
||||
char dai_name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
|
||||
__le32 pcm_id; /* unique ID - used to match */
|
||||
__le32 dai_id; /* unique ID - used to match */
|
||||
__le32 playback; /* supports playback mode */
|
||||
__le32 capture; /* supports capture mode */
|
||||
__le32 compress; /* 1 = compressed; 0 = PCM */
|
||||
struct snd_soc_tplg_stream stream[SND_SOC_TPLG_STREAM_CONFIG_MAX]; /* for DAI link */
|
||||
__le32 num_streams; /* number of streams */
|
||||
struct snd_soc_tplg_stream_caps caps[2]; /* playback and capture for DAI */
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
/*
|
||||
* Describes the BE or CC link runtime supported configs or params
|
||||
*
|
||||
* File block representation for BE/CC link config :-
|
||||
* +-----------------------------------+-----+
|
||||
* | struct snd_soc_tplg_hdr | 1 |
|
||||
* +-----------------------------------+-----+
|
||||
* | struct snd_soc_tplg_link_config | N |
|
||||
* +-----------------------------------+-----+
|
||||
*/
|
||||
struct snd_soc_tplg_link_config {
|
||||
__le32 size; /* in bytes of this structure */
|
||||
__le32 id; /* unique ID - used to match */
|
||||
struct snd_soc_tplg_stream stream[SND_SOC_TPLG_STREAM_CONFIG_MAX]; /* supported configs playback and captrure */
|
||||
__le32 num_streams; /* number of streams */
|
||||
} __attribute__((packed));
|
||||
#endif
|
||||
|
|
|
@ -100,9 +100,11 @@ enum {
|
|||
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 */
|
||||
SNDRV_HWDEP_IFACE_FW_DIGI00X, /* Digidesign Digi 002/003 family */
|
||||
SNDRV_HWDEP_IFACE_FW_TASCAM, /* TASCAM FireWire series */
|
||||
|
||||
/* Don't forget to change the following: */
|
||||
SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_OXFW
|
||||
SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_TASCAM
|
||||
};
|
||||
|
||||
struct snd_hwdep_info {
|
||||
|
|
|
@ -34,6 +34,14 @@
|
|||
|
||||
#define EMU10K1_FX8010_PCM_COUNT 8
|
||||
|
||||
/*
|
||||
* Following definition is copied from linux/types.h to support compiling
|
||||
* this header file in userspace since they are not generally available for
|
||||
* uapi headers.
|
||||
*/
|
||||
#define __EMU10K1_DECLARE_BITMAP(name,bits) \
|
||||
unsigned long name[(bits) / (sizeof(unsigned long) * 8)]
|
||||
|
||||
/* instruction set */
|
||||
#define iMAC0 0x00 /* R = A + (X * Y >> 31) ; saturation */
|
||||
#define iMAC1 0x01 /* R = A + (-X * Y >> 31) ; saturation */
|
||||
|
@ -300,7 +308,7 @@ struct snd_emu10k1_fx8010_control_old_gpr {
|
|||
struct snd_emu10k1_fx8010_code {
|
||||
char name[128];
|
||||
|
||||
DECLARE_BITMAP(gpr_valid, 0x200); /* bitmask of valid initializers */
|
||||
__EMU10K1_DECLARE_BITMAP(gpr_valid, 0x200); /* bitmask of valid initializers */
|
||||
__u32 __user *gpr_map; /* initializers */
|
||||
|
||||
unsigned int gpr_add_control_count; /* count of GPR controls to add/replace */
|
||||
|
@ -313,11 +321,11 @@ struct snd_emu10k1_fx8010_code {
|
|||
unsigned int gpr_list_control_total; /* total count of GPR controls */
|
||||
struct snd_emu10k1_fx8010_control_gpr __user *gpr_list_controls; /* listed GPR controls */
|
||||
|
||||
DECLARE_BITMAP(tram_valid, 0x100); /* bitmask of valid initializers */
|
||||
__EMU10K1_DECLARE_BITMAP(tram_valid, 0x100); /* bitmask of valid initializers */
|
||||
__u32 __user *tram_data_map; /* data initializers */
|
||||
__u32 __user *tram_addr_map; /* map initializers */
|
||||
|
||||
DECLARE_BITMAP(code_valid, 1024); /* bitmask of valid instructions */
|
||||
__EMU10K1_DECLARE_BITMAP(code_valid, 1024); /* bitmask of valid instructions */
|
||||
__u32 __user *code; /* one instruction - 64 bits */
|
||||
};
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#define SNDRV_FIREWIRE_EVENT_LOCK_STATUS 0x000010cc
|
||||
#define SNDRV_FIREWIRE_EVENT_DICE_NOTIFICATION 0xd1ce004e
|
||||
#define SNDRV_FIREWIRE_EVENT_EFW_RESPONSE 0x4e617475
|
||||
#define SNDRV_FIREWIRE_EVENT_DIGI00X_MESSAGE 0x746e736c
|
||||
|
||||
struct snd_firewire_event_common {
|
||||
unsigned int type; /* SNDRV_FIREWIRE_EVENT_xxx */
|
||||
|
@ -40,11 +41,17 @@ struct snd_firewire_event_efw_response {
|
|||
__be32 response[0]; /* some responses */
|
||||
};
|
||||
|
||||
struct snd_firewire_event_digi00x_message {
|
||||
unsigned int type;
|
||||
__u32 message; /* Digi00x-specific message */
|
||||
};
|
||||
|
||||
union snd_firewire_event {
|
||||
struct snd_firewire_event_common common;
|
||||
struct snd_firewire_event_lock_status lock_status;
|
||||
struct snd_firewire_event_dice_notification dice_notification;
|
||||
struct snd_firewire_event_efw_response efw_response;
|
||||
struct snd_firewire_event_digi00x_message digi00x_message;
|
||||
};
|
||||
|
||||
|
||||
|
@ -56,6 +63,8 @@ union snd_firewire_event {
|
|||
#define SNDRV_FIREWIRE_TYPE_FIREWORKS 2
|
||||
#define SNDRV_FIREWIRE_TYPE_BEBOB 3
|
||||
#define SNDRV_FIREWIRE_TYPE_OXFW 4
|
||||
#define SNDRV_FIREWIRE_TYPE_DIGI00X 5
|
||||
#define SNDRV_FIREWIRE_TYPE_TASCAM 6
|
||||
/* RME, MOTU, ... */
|
||||
|
||||
struct snd_firewire_get_info {
|
||||
|
|
|
@ -20,11 +20,7 @@
|
|||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#ifdef __KERNEL__
|
||||
#include <linux/types.h>
|
||||
#else
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
|
||||
/* Maximum channels is 64 even on 56Mode you have 64playbacks to matrix */
|
||||
#define HDSPM_MAX_CHANNELS 64
|
||||
|
@ -46,15 +42,15 @@ enum hdspm_speed {
|
|||
/* -------------------- IOCTL Peak/RMS Meters -------------------- */
|
||||
|
||||
struct hdspm_peak_rms {
|
||||
uint32_t input_peaks[64];
|
||||
uint32_t playback_peaks[64];
|
||||
uint32_t output_peaks[64];
|
||||
__u32 input_peaks[64];
|
||||
__u32 playback_peaks[64];
|
||||
__u32 output_peaks[64];
|
||||
|
||||
uint64_t input_rms[64];
|
||||
uint64_t playback_rms[64];
|
||||
uint64_t output_rms[64];
|
||||
__u64 input_rms[64];
|
||||
__u64 playback_rms[64];
|
||||
__u64 output_rms[64];
|
||||
|
||||
uint8_t speed; /* enum {ss, ds, qs} */
|
||||
__u8 speed; /* enum {ss, ds, qs} */
|
||||
int status2;
|
||||
};
|
||||
|
||||
|
@ -155,21 +151,21 @@ enum hdspm_syncsource {
|
|||
};
|
||||
|
||||
struct hdspm_status {
|
||||
uint8_t card_type; /* enum hdspm_io_type */
|
||||
__u8 card_type; /* enum hdspm_io_type */
|
||||
enum hdspm_syncsource autosync_source;
|
||||
|
||||
uint64_t card_clock;
|
||||
uint32_t master_period;
|
||||
__u64 card_clock;
|
||||
__u32 master_period;
|
||||
|
||||
union {
|
||||
struct {
|
||||
uint8_t sync_wc; /* enum hdspm_sync */
|
||||
uint8_t sync_madi; /* enum hdspm_sync */
|
||||
uint8_t sync_tco; /* enum hdspm_sync */
|
||||
uint8_t sync_in; /* enum hdspm_sync */
|
||||
uint8_t madi_input; /* enum hdspm_madi_input */
|
||||
uint8_t channel_format; /* enum hdspm_madi_channel_format */
|
||||
uint8_t frame_format; /* enum hdspm_madi_frame_format */
|
||||
__u8 sync_wc; /* enum hdspm_sync */
|
||||
__u8 sync_madi; /* enum hdspm_sync */
|
||||
__u8 sync_tco; /* enum hdspm_sync */
|
||||
__u8 sync_in; /* enum hdspm_sync */
|
||||
__u8 madi_input; /* enum hdspm_madi_input */
|
||||
__u8 channel_format; /* enum hdspm_madi_channel_format */
|
||||
__u8 frame_format; /* enum hdspm_madi_frame_format */
|
||||
} madi;
|
||||
} card_specific;
|
||||
};
|
||||
|
@ -184,7 +180,7 @@ struct hdspm_status {
|
|||
#define HDSPM_ADDON_TCO 1
|
||||
|
||||
struct hdspm_version {
|
||||
uint8_t card_type; /* enum hdspm_io_type */
|
||||
__u8 card_type; /* enum hdspm_io_type */
|
||||
char cardname[20];
|
||||
unsigned int serial;
|
||||
unsigned short firmware_rev;
|
||||
|
|
|
@ -196,5 +196,10 @@ int main(void)
|
|||
DEVID_FIELD(ulpi_device_id, vendor);
|
||||
DEVID_FIELD(ulpi_device_id, product);
|
||||
|
||||
DEVID(hda_device_id);
|
||||
DEVID_FIELD(hda_device_id, vendor_id);
|
||||
DEVID_FIELD(hda_device_id, rev_id);
|
||||
DEVID_FIELD(hda_device_id, api_version);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -1250,6 +1250,23 @@ static int do_ulpi_entry(const char *filename, void *symval,
|
|||
}
|
||||
ADD_TO_DEVTABLE("ulpi", ulpi_device_id, do_ulpi_entry);
|
||||
|
||||
/* Looks like: hdaudio:vNrNaN */
|
||||
static int do_hda_entry(const char *filename, void *symval, char *alias)
|
||||
{
|
||||
DEF_FIELD(symval, hda_device_id, vendor_id);
|
||||
DEF_FIELD(symval, hda_device_id, rev_id);
|
||||
DEF_FIELD(symval, hda_device_id, api_version);
|
||||
|
||||
strcpy(alias, "hdaudio:");
|
||||
ADD(alias, "v", vendor_id != 0, vendor_id);
|
||||
ADD(alias, "r", rev_id != 0, rev_id);
|
||||
ADD(alias, "a", api_version != 0, api_version);
|
||||
|
||||
add_wildcard(alias);
|
||||
return 1;
|
||||
}
|
||||
ADD_TO_DEVTABLE("hdaudio", hda_device_id, do_hda_entry);
|
||||
|
||||
/* Does namelen bytes of name exactly match the symbol? */
|
||||
static bool sym_is(const char *name, unsigned namelen, const char *symbol)
|
||||
{
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/dmaengine.h>
|
||||
#include <linux/dma/pxa-dma.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
|
@ -43,7 +44,11 @@ static struct snd_ac97_bus_ops pxa2xx_ac97_ops = {
|
|||
.reset = pxa2xx_ac97_reset,
|
||||
};
|
||||
|
||||
static unsigned long pxa2xx_ac97_pcm_out_req = 12;
|
||||
static struct pxad_param pxa2xx_ac97_pcm_out_req = {
|
||||
.prio = PXAD_PRIO_LOWEST,
|
||||
.drcmr = 12,
|
||||
};
|
||||
|
||||
static struct snd_dmaengine_dai_dma_data pxa2xx_ac97_pcm_out = {
|
||||
.addr = __PREG(PCDR),
|
||||
.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
|
||||
|
@ -51,7 +56,11 @@ static struct snd_dmaengine_dai_dma_data pxa2xx_ac97_pcm_out = {
|
|||
.filter_data = &pxa2xx_ac97_pcm_out_req,
|
||||
};
|
||||
|
||||
static unsigned long pxa2xx_ac97_pcm_in_req = 11;
|
||||
static struct pxad_param pxa2xx_ac97_pcm_in_req = {
|
||||
.prio = PXAD_PRIO_LOWEST,
|
||||
.drcmr = 11,
|
||||
};
|
||||
|
||||
static struct snd_dmaengine_dai_dma_data pxa2xx_ac97_pcm_in = {
|
||||
.addr = __PREG(PCDR),
|
||||
.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <linux/module.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/dmaengine.h>
|
||||
#include <linux/dma/pxa-dma.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
|
@ -15,8 +16,6 @@
|
|||
#include <sound/pxa2xx-lib.h>
|
||||
#include <sound/dmaengine_pcm.h>
|
||||
|
||||
#include <mach/dma.h>
|
||||
|
||||
#include "pxa2xx-pcm.h"
|
||||
|
||||
static const struct snd_pcm_hardware pxa2xx_pcm_hardware = {
|
||||
|
@ -31,7 +30,7 @@ static const struct snd_pcm_hardware pxa2xx_pcm_hardware = {
|
|||
.period_bytes_min = 32,
|
||||
.period_bytes_max = 8192 - 32,
|
||||
.periods_min = 1,
|
||||
.periods_max = PAGE_SIZE/sizeof(pxa_dma_desc),
|
||||
.periods_max = 256,
|
||||
.buffer_bytes_max = 128 * 1024,
|
||||
.fifo_size = 32,
|
||||
};
|
||||
|
@ -39,65 +38,29 @@ static const struct snd_pcm_hardware pxa2xx_pcm_hardware = {
|
|||
int __pxa2xx_pcm_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct pxa2xx_runtime_data *rtd = runtime->private_data;
|
||||
size_t totsize = params_buffer_bytes(params);
|
||||
size_t period = params_period_bytes(params);
|
||||
pxa_dma_desc *dma_desc;
|
||||
dma_addr_t dma_buff_phys, next_desc_phys;
|
||||
u32 dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG;
|
||||
struct dma_chan *chan = snd_dmaengine_pcm_get_chan(substream);
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_dmaengine_dai_dma_data *dma_params;
|
||||
struct dma_slave_config config;
|
||||
int ret;
|
||||
|
||||
/* temporary transition hack */
|
||||
switch (rtd->params->addr_width) {
|
||||
case DMA_SLAVE_BUSWIDTH_1_BYTE:
|
||||
dcmd |= DCMD_WIDTH1;
|
||||
break;
|
||||
case DMA_SLAVE_BUSWIDTH_2_BYTES:
|
||||
dcmd |= DCMD_WIDTH2;
|
||||
break;
|
||||
case DMA_SLAVE_BUSWIDTH_4_BYTES:
|
||||
dcmd |= DCMD_WIDTH4;
|
||||
break;
|
||||
default:
|
||||
/* can't happen */
|
||||
break;
|
||||
}
|
||||
dma_params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
|
||||
if (!dma_params)
|
||||
return 0;
|
||||
|
||||
switch (rtd->params->maxburst) {
|
||||
case 8:
|
||||
dcmd |= DCMD_BURST8;
|
||||
break;
|
||||
case 16:
|
||||
dcmd |= DCMD_BURST16;
|
||||
break;
|
||||
case 32:
|
||||
dcmd |= DCMD_BURST32;
|
||||
break;
|
||||
}
|
||||
ret = snd_hwparams_to_dma_slave_config(substream, params, &config);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
snd_dmaengine_pcm_set_config_from_dai_data(substream,
|
||||
snd_soc_dai_get_dma_data(rtd->cpu_dai, substream),
|
||||
&config);
|
||||
|
||||
ret = dmaengine_slave_config(chan, &config);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
|
||||
runtime->dma_bytes = totsize;
|
||||
|
||||
dma_desc = rtd->dma_desc_array;
|
||||
next_desc_phys = rtd->dma_desc_array_phys;
|
||||
dma_buff_phys = runtime->dma_addr;
|
||||
do {
|
||||
next_desc_phys += sizeof(pxa_dma_desc);
|
||||
dma_desc->ddadr = next_desc_phys;
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
dma_desc->dsadr = dma_buff_phys;
|
||||
dma_desc->dtadr = rtd->params->addr;
|
||||
} else {
|
||||
dma_desc->dsadr = rtd->params->addr;
|
||||
dma_desc->dtadr = dma_buff_phys;
|
||||
}
|
||||
if (period > totsize)
|
||||
period = totsize;
|
||||
dma_desc->dcmd = dcmd | period | DCMD_ENDIRQEN;
|
||||
dma_desc++;
|
||||
dma_buff_phys += period;
|
||||
} while (totsize -= period);
|
||||
dma_desc[-1].ddadr = rtd->dma_desc_array_phys;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -105,13 +68,6 @@ EXPORT_SYMBOL(__pxa2xx_pcm_hw_params);
|
|||
|
||||
int __pxa2xx_pcm_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct pxa2xx_runtime_data *rtd = substream->runtime->private_data;
|
||||
|
||||
if (rtd && rtd->params && rtd->params->filter_data) {
|
||||
unsigned long req = *(unsigned long *) rtd->params->filter_data;
|
||||
DRCMR(req) = 0;
|
||||
}
|
||||
|
||||
snd_pcm_set_runtime_buffer(substream, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
@ -119,100 +75,36 @@ EXPORT_SYMBOL(__pxa2xx_pcm_hw_free);
|
|||
|
||||
int pxa2xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
|
||||
{
|
||||
struct pxa2xx_runtime_data *prtd = substream->runtime->private_data;
|
||||
int ret = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
DDADR(prtd->dma_ch) = prtd->dma_desc_array_phys;
|
||||
DCSR(prtd->dma_ch) = DCSR_RUN;
|
||||
break;
|
||||
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
DCSR(prtd->dma_ch) &= ~DCSR_RUN;
|
||||
break;
|
||||
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
DCSR(prtd->dma_ch) |= DCSR_RUN;
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
DDADR(prtd->dma_ch) = prtd->dma_desc_array_phys;
|
||||
DCSR(prtd->dma_ch) |= DCSR_RUN;
|
||||
break;
|
||||
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
return snd_dmaengine_pcm_trigger(substream, cmd);
|
||||
}
|
||||
EXPORT_SYMBOL(pxa2xx_pcm_trigger);
|
||||
|
||||
snd_pcm_uframes_t
|
||||
pxa2xx_pcm_pointer(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct pxa2xx_runtime_data *prtd = runtime->private_data;
|
||||
|
||||
dma_addr_t ptr = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
|
||||
DSADR(prtd->dma_ch) : DTADR(prtd->dma_ch);
|
||||
snd_pcm_uframes_t x = bytes_to_frames(runtime, ptr - runtime->dma_addr);
|
||||
|
||||
if (x == runtime->buffer_size)
|
||||
x = 0;
|
||||
return x;
|
||||
return snd_dmaengine_pcm_pointer(substream);
|
||||
}
|
||||
EXPORT_SYMBOL(pxa2xx_pcm_pointer);
|
||||
|
||||
int __pxa2xx_pcm_prepare(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct pxa2xx_runtime_data *prtd = substream->runtime->private_data;
|
||||
unsigned long req;
|
||||
|
||||
if (!prtd || !prtd->params)
|
||||
return 0;
|
||||
|
||||
if (prtd->dma_ch == -1)
|
||||
return -EINVAL;
|
||||
|
||||
DCSR(prtd->dma_ch) &= ~DCSR_RUN;
|
||||
DCSR(prtd->dma_ch) = 0;
|
||||
DCMD(prtd->dma_ch) = 0;
|
||||
req = *(unsigned long *) prtd->params->filter_data;
|
||||
DRCMR(req) = prtd->dma_ch | DRCMR_MAPVLD;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(__pxa2xx_pcm_prepare);
|
||||
|
||||
void pxa2xx_pcm_dma_irq(int dma_ch, void *dev_id)
|
||||
{
|
||||
struct snd_pcm_substream *substream = dev_id;
|
||||
int dcsr;
|
||||
|
||||
dcsr = DCSR(dma_ch);
|
||||
DCSR(dma_ch) = dcsr & ~DCSR_STOPIRQEN;
|
||||
|
||||
if (dcsr & DCSR_ENDINTR) {
|
||||
snd_pcm_period_elapsed(substream);
|
||||
} else {
|
||||
printk(KERN_ERR "DMA error on channel %d (DCSR=%#x)\n",
|
||||
dma_ch, dcsr);
|
||||
snd_pcm_stop_xrun(substream);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(pxa2xx_pcm_dma_irq);
|
||||
|
||||
int __pxa2xx_pcm_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct pxa2xx_runtime_data *rtd;
|
||||
struct snd_dmaengine_dai_dma_data *dma_params;
|
||||
int ret;
|
||||
|
||||
runtime->hw = pxa2xx_pcm_hardware;
|
||||
|
||||
dma_params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
|
||||
if (!dma_params)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* For mysterious reasons (and despite what the manual says)
|
||||
* playback samples are lost if the DMA count is not a multiple
|
||||
|
@ -221,48 +113,27 @@ int __pxa2xx_pcm_open(struct snd_pcm_substream *substream)
|
|||
ret = snd_pcm_hw_constraint_step(runtime, 0,
|
||||
SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32);
|
||||
if (ret)
|
||||
goto out;
|
||||
return ret;
|
||||
|
||||
ret = snd_pcm_hw_constraint_step(runtime, 0,
|
||||
SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32);
|
||||
if (ret)
|
||||
goto out;
|
||||
return ret;
|
||||
|
||||
ret = snd_pcm_hw_constraint_integer(runtime,
|
||||
SNDRV_PCM_HW_PARAM_PERIODS);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
return ret;
|
||||
|
||||
ret = -ENOMEM;
|
||||
rtd = kzalloc(sizeof(*rtd), GFP_KERNEL);
|
||||
if (!rtd)
|
||||
goto out;
|
||||
rtd->dma_desc_array =
|
||||
dma_alloc_writecombine(substream->pcm->card->dev, PAGE_SIZE,
|
||||
&rtd->dma_desc_array_phys, GFP_KERNEL);
|
||||
if (!rtd->dma_desc_array)
|
||||
goto err1;
|
||||
|
||||
rtd->dma_ch = -1;
|
||||
runtime->private_data = rtd;
|
||||
return 0;
|
||||
|
||||
err1:
|
||||
kfree(rtd);
|
||||
out:
|
||||
return ret;
|
||||
return snd_dmaengine_pcm_open_request_chan(substream,
|
||||
pxad_filter_fn,
|
||||
dma_params->filter_data);
|
||||
}
|
||||
EXPORT_SYMBOL(__pxa2xx_pcm_open);
|
||||
|
||||
int __pxa2xx_pcm_close(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct pxa2xx_runtime_data *rtd = runtime->private_data;
|
||||
|
||||
dma_free_writecombine(substream->pcm->card->dev, PAGE_SIZE,
|
||||
rtd->dma_desc_array, rtd->dma_desc_array_phys);
|
||||
kfree(rtd);
|
||||
return 0;
|
||||
return snd_dmaengine_pcm_close_release_chan(substream);
|
||||
}
|
||||
EXPORT_SYMBOL(__pxa2xx_pcm_close);
|
||||
|
||||
|
|
|
@ -46,17 +46,13 @@ static int pxa2xx_pcm_open(struct snd_pcm_substream *substream)
|
|||
|
||||
rtd->params = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
|
||||
client->playback_params : client->capture_params;
|
||||
ret = pxa_request_dma("dma", DMA_PRIO_LOW,
|
||||
pxa2xx_pcm_dma_irq, substream);
|
||||
if (ret < 0)
|
||||
goto err2;
|
||||
rtd->dma_ch = ret;
|
||||
|
||||
ret = client->startup(substream);
|
||||
if (!ret)
|
||||
goto out;
|
||||
goto err2;
|
||||
|
||||
return 0;
|
||||
|
||||
pxa_free_dma(rtd->dma_ch);
|
||||
err2:
|
||||
__pxa2xx_pcm_close(substream);
|
||||
out:
|
||||
|
@ -66,9 +62,7 @@ static int pxa2xx_pcm_open(struct snd_pcm_substream *substream)
|
|||
static int pxa2xx_pcm_close(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct pxa2xx_pcm_client *client = substream->private_data;
|
||||
struct pxa2xx_runtime_data *rtd = substream->runtime->private_data;
|
||||
|
||||
pxa_free_dma(rtd->dma_ch);
|
||||
client->shutdown(substream);
|
||||
|
||||
return __pxa2xx_pcm_close(substream);
|
||||
|
|
|
@ -13,8 +13,6 @@
|
|||
struct pxa2xx_runtime_data {
|
||||
int dma_ch;
|
||||
struct snd_dmaengine_dai_dma_data *params;
|
||||
struct pxa_dma_desc *dma_desc_array;
|
||||
dma_addr_t dma_desc_array_phys;
|
||||
};
|
||||
|
||||
struct pxa2xx_pcm_client {
|
||||
|
|
|
@ -4,7 +4,7 @@ config SND_TIMER
|
|||
|
||||
config SND_PCM
|
||||
tristate
|
||||
select SND_TIMER
|
||||
select SND_TIMER if SND_PCM_TIMER
|
||||
|
||||
config SND_PCM_ELD
|
||||
bool
|
||||
|
@ -93,6 +93,17 @@ config SND_PCM_OSS_PLUGINS
|
|||
support conversion of channels, formats and rates. It will
|
||||
behave like most of new OSS/Free drivers in 2.4/2.6 kernels.
|
||||
|
||||
config SND_PCM_TIMER
|
||||
bool "PCM timer interface" if EXPERT
|
||||
default y
|
||||
help
|
||||
If you disable this option, pcm timer will be inavailable, so
|
||||
those stubs used pcm timer (e.g. dmix, dsnoop & co) may work
|
||||
incorrectlly.
|
||||
|
||||
For some embedded device, we may disable it to reduce memory
|
||||
footprint, about 20KB on x86_64 platform.
|
||||
|
||||
config SND_SEQUENCER_OSS
|
||||
bool "OSS Sequencer API"
|
||||
depends on SND_SEQUENCER
|
||||
|
|
|
@ -13,8 +13,9 @@ snd-$(CONFIG_SND_OSSEMUL) += sound_oss.o
|
|||
snd-$(CONFIG_SND_VMASTER) += vmaster.o
|
||||
snd-$(CONFIG_SND_JACK) += ctljack.o jack.o
|
||||
|
||||
snd-pcm-y := pcm.o pcm_native.o pcm_lib.o pcm_timer.o pcm_misc.o \
|
||||
snd-pcm-y := pcm.o pcm_native.o pcm_lib.o pcm_misc.o \
|
||||
pcm_memory.o memalloc.o
|
||||
snd-pcm-$(CONFIG_SND_PCM_TIMER) += pcm_timer.o
|
||||
snd-pcm-$(CONFIG_SND_DMA_SGBUF) += sgbuf.o
|
||||
snd-pcm-$(CONFIG_SND_PCM_ELD) += pcm_drm_eld.o
|
||||
snd-pcm-$(CONFIG_SND_PCM_IEC958) += pcm_iec958.o
|
||||
|
|
|
@ -1177,7 +1177,8 @@ static void snd_mixer_oss_proc_write(struct snd_info_entry *entry,
|
|||
struct snd_mixer_oss *mixer = entry->private_data;
|
||||
char line[128], str[32], idxstr[16];
|
||||
const char *cptr;
|
||||
int ch, idx;
|
||||
unsigned int idx;
|
||||
int ch;
|
||||
struct snd_mixer_oss_assign_table *tbl;
|
||||
struct slot *slot;
|
||||
|
||||
|
|
|
@ -1014,9 +1014,6 @@ void snd_pcm_detach_substream(struct snd_pcm_substream *substream)
|
|||
snd_free_pages((void*)runtime->control,
|
||||
PAGE_ALIGN(sizeof(struct snd_pcm_mmap_control)));
|
||||
kfree(runtime->hw_constraints.rules);
|
||||
#ifdef CONFIG_SND_PCM_XRUN_DEBUG
|
||||
kfree(runtime->hwptr_log);
|
||||
#endif
|
||||
kfree(runtime);
|
||||
substream->runtime = NULL;
|
||||
put_pid(substream->pid);
|
||||
|
|
|
@ -801,7 +801,7 @@ void snd_interval_mulkdiv(const struct snd_interval *a, unsigned int k,
|
|||
* negative error code.
|
||||
*/
|
||||
int snd_interval_ratnum(struct snd_interval *i,
|
||||
unsigned int rats_count, struct snd_ratnum *rats,
|
||||
unsigned int rats_count, const struct snd_ratnum *rats,
|
||||
unsigned int *nump, unsigned int *denp)
|
||||
{
|
||||
unsigned int best_num, best_den;
|
||||
|
@ -920,7 +920,8 @@ EXPORT_SYMBOL(snd_interval_ratnum);
|
|||
* negative error code.
|
||||
*/
|
||||
static int snd_interval_ratden(struct snd_interval *i,
|
||||
unsigned int rats_count, struct snd_ratden *rats,
|
||||
unsigned int rats_count,
|
||||
const struct snd_ratden *rats,
|
||||
unsigned int *nump, unsigned int *denp)
|
||||
{
|
||||
unsigned int best_num, best_diff, best_den;
|
||||
|
@ -1339,7 +1340,7 @@ EXPORT_SYMBOL(snd_pcm_hw_constraint_ranges);
|
|||
static int snd_pcm_hw_rule_ratnums(struct snd_pcm_hw_params *params,
|
||||
struct snd_pcm_hw_rule *rule)
|
||||
{
|
||||
struct snd_pcm_hw_constraint_ratnums *r = rule->private;
|
||||
const struct snd_pcm_hw_constraint_ratnums *r = rule->private;
|
||||
unsigned int num = 0, den = 0;
|
||||
int err;
|
||||
err = snd_interval_ratnum(hw_param_interval(params, rule->var),
|
||||
|
@ -1363,10 +1364,10 @@ static int snd_pcm_hw_rule_ratnums(struct snd_pcm_hw_params *params,
|
|||
int snd_pcm_hw_constraint_ratnums(struct snd_pcm_runtime *runtime,
|
||||
unsigned int cond,
|
||||
snd_pcm_hw_param_t var,
|
||||
struct snd_pcm_hw_constraint_ratnums *r)
|
||||
const struct snd_pcm_hw_constraint_ratnums *r)
|
||||
{
|
||||
return snd_pcm_hw_rule_add(runtime, cond, var,
|
||||
snd_pcm_hw_rule_ratnums, r,
|
||||
snd_pcm_hw_rule_ratnums, (void *)r,
|
||||
var, -1);
|
||||
}
|
||||
|
||||
|
@ -1375,7 +1376,7 @@ EXPORT_SYMBOL(snd_pcm_hw_constraint_ratnums);
|
|||
static int snd_pcm_hw_rule_ratdens(struct snd_pcm_hw_params *params,
|
||||
struct snd_pcm_hw_rule *rule)
|
||||
{
|
||||
struct snd_pcm_hw_constraint_ratdens *r = rule->private;
|
||||
const struct snd_pcm_hw_constraint_ratdens *r = rule->private;
|
||||
unsigned int num = 0, den = 0;
|
||||
int err = snd_interval_ratden(hw_param_interval(params, rule->var),
|
||||
r->nrats, r->rats, &num, &den);
|
||||
|
@ -1398,10 +1399,10 @@ static int snd_pcm_hw_rule_ratdens(struct snd_pcm_hw_params *params,
|
|||
int snd_pcm_hw_constraint_ratdens(struct snd_pcm_runtime *runtime,
|
||||
unsigned int cond,
|
||||
snd_pcm_hw_param_t var,
|
||||
struct snd_pcm_hw_constraint_ratdens *r)
|
||||
const struct snd_pcm_hw_constraint_ratdens *r)
|
||||
{
|
||||
return snd_pcm_hw_rule_add(runtime, cond, var,
|
||||
snd_pcm_hw_rule_ratdens, r,
|
||||
snd_pcm_hw_rule_ratdens, (void *)r,
|
||||
var, -1);
|
||||
}
|
||||
|
||||
|
@ -1875,20 +1876,17 @@ void snd_pcm_period_elapsed(struct snd_pcm_substream *substream)
|
|||
return;
|
||||
runtime = substream->runtime;
|
||||
|
||||
if (runtime->transfer_ack_begin)
|
||||
runtime->transfer_ack_begin(substream);
|
||||
|
||||
snd_pcm_stream_lock_irqsave(substream, flags);
|
||||
if (!snd_pcm_running(substream) ||
|
||||
snd_pcm_update_hw_ptr0(substream, 1) < 0)
|
||||
goto _end;
|
||||
|
||||
#ifdef CONFIG_SND_PCM_TIMER
|
||||
if (substream->timer_running)
|
||||
snd_timer_interrupt(substream->timer, 1);
|
||||
#endif
|
||||
_end:
|
||||
snd_pcm_stream_unlock_irqrestore(substream, flags);
|
||||
if (runtime->transfer_ack_end)
|
||||
runtime->transfer_ack_end(substream);
|
||||
kill_fasync(&runtime->fasync, SIGIO, POLL_IN);
|
||||
}
|
||||
|
||||
|
|
|
@ -486,6 +486,16 @@ static void snd_pcm_set_state(struct snd_pcm_substream *substream, int state)
|
|||
snd_pcm_stream_unlock_irq(substream);
|
||||
}
|
||||
|
||||
static inline void snd_pcm_timer_notify(struct snd_pcm_substream *substream,
|
||||
int event)
|
||||
{
|
||||
#ifdef CONFIG_SND_PCM_TIMER
|
||||
if (substream->timer)
|
||||
snd_timer_notify(substream->timer, event,
|
||||
&substream->runtime->trigger_tstamp);
|
||||
#endif
|
||||
}
|
||||
|
||||
static int snd_pcm_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
|
@ -650,7 +660,8 @@ static int snd_pcm_sw_params(struct snd_pcm_substream *substream,
|
|||
}
|
||||
snd_pcm_stream_unlock_irq(substream);
|
||||
|
||||
if (params->tstamp_mode > SNDRV_PCM_TSTAMP_LAST)
|
||||
if (params->tstamp_mode < 0 ||
|
||||
params->tstamp_mode > SNDRV_PCM_TSTAMP_LAST)
|
||||
return -EINVAL;
|
||||
if (params->proto >= SNDRV_PROTOCOL_VERSION(2, 0, 12) &&
|
||||
params->tstamp_type > SNDRV_PCM_TSTAMP_TYPE_LAST)
|
||||
|
@ -1042,9 +1053,7 @@ static void snd_pcm_post_start(struct snd_pcm_substream *substream, int state)
|
|||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
|
||||
runtime->silence_size > 0)
|
||||
snd_pcm_playback_silence(substream, ULONG_MAX);
|
||||
if (substream->timer)
|
||||
snd_timer_notify(substream->timer, SNDRV_TIMER_EVENT_MSTART,
|
||||
&runtime->trigger_tstamp);
|
||||
snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MSTART);
|
||||
}
|
||||
|
||||
static struct action_ops snd_pcm_action_start = {
|
||||
|
@ -1092,9 +1101,7 @@ static void snd_pcm_post_stop(struct snd_pcm_substream *substream, int state)
|
|||
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);
|
||||
snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MSTOP);
|
||||
}
|
||||
wake_up(&runtime->sleep);
|
||||
wake_up(&runtime->tsleep);
|
||||
|
@ -1208,18 +1215,12 @@ static void snd_pcm_post_pause(struct snd_pcm_substream *substream, int push)
|
|||
snd_pcm_trigger_tstamp(substream);
|
||||
if (push) {
|
||||
runtime->status->state = SNDRV_PCM_STATE_PAUSED;
|
||||
if (substream->timer)
|
||||
snd_timer_notify(substream->timer,
|
||||
SNDRV_TIMER_EVENT_MPAUSE,
|
||||
&runtime->trigger_tstamp);
|
||||
snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MPAUSE);
|
||||
wake_up(&runtime->sleep);
|
||||
wake_up(&runtime->tsleep);
|
||||
} else {
|
||||
runtime->status->state = SNDRV_PCM_STATE_RUNNING;
|
||||
if (substream->timer)
|
||||
snd_timer_notify(substream->timer,
|
||||
SNDRV_TIMER_EVENT_MCONTINUE,
|
||||
&runtime->trigger_tstamp);
|
||||
snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MCONTINUE);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1267,9 +1268,7 @@ static void snd_pcm_post_suspend(struct snd_pcm_substream *substream, int state)
|
|||
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);
|
||||
snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MSUSPEND);
|
||||
wake_up(&runtime->sleep);
|
||||
wake_up(&runtime->tsleep);
|
||||
}
|
||||
|
@ -1373,9 +1372,7 @@ 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);
|
||||
snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MRESUME);
|
||||
}
|
||||
|
||||
static struct action_ops snd_pcm_action_resume = {
|
||||
|
@ -2226,7 +2223,8 @@ void snd_pcm_release_substream(struct snd_pcm_substream *substream)
|
|||
|
||||
snd_pcm_drop(substream);
|
||||
if (substream->hw_opened) {
|
||||
if (substream->ops->hw_free != NULL)
|
||||
if (substream->ops->hw_free &&
|
||||
substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
|
||||
substream->ops->hw_free(substream);
|
||||
substream->ops->close(substream);
|
||||
substream->hw_opened = 0;
|
||||
|
|
|
@ -91,8 +91,7 @@ snd_seq_oss_readq_clear(struct seq_oss_readq *q)
|
|||
q->head = q->tail = 0;
|
||||
}
|
||||
/* if someone sleeping, wake'em up */
|
||||
if (waitqueue_active(&q->midi_sleep))
|
||||
wake_up(&q->midi_sleep);
|
||||
wake_up(&q->midi_sleep);
|
||||
q->input_time = (unsigned long)-1;
|
||||
}
|
||||
|
||||
|
@ -138,8 +137,7 @@ snd_seq_oss_readq_put_event(struct seq_oss_readq *q, union evrec *ev)
|
|||
q->qlen++;
|
||||
|
||||
/* wake up sleeper */
|
||||
if (waitqueue_active(&q->midi_sleep))
|
||||
wake_up(&q->midi_sleep);
|
||||
wake_up(&q->midi_sleep);
|
||||
|
||||
spin_unlock_irqrestore(&q->lock, flags);
|
||||
|
||||
|
|
|
@ -138,9 +138,7 @@ snd_seq_oss_writeq_wakeup(struct seq_oss_writeq *q, abstime_t time)
|
|||
spin_lock_irqsave(&q->sync_lock, flags);
|
||||
q->sync_time = time;
|
||||
q->sync_event_put = 0;
|
||||
if (waitqueue_active(&q->sync_sleep)) {
|
||||
wake_up(&q->sync_sleep);
|
||||
}
|
||||
wake_up(&q->sync_sleep);
|
||||
spin_unlock_irqrestore(&q->sync_lock, flags);
|
||||
}
|
||||
|
||||
|
|
|
@ -120,4 +120,31 @@ config SND_BEBOB
|
|||
To compile this driver as a module, choose M here: the module
|
||||
will be called snd-bebob.
|
||||
|
||||
config SND_FIREWIRE_DIGI00X
|
||||
tristate "Digidesign Digi 002/003 family support"
|
||||
select SND_FIREWIRE_LIB
|
||||
select SND_HWDEP
|
||||
help
|
||||
Say Y here to include support for Digidesign Digi 002/003 family.
|
||||
* Digi 002 Console
|
||||
* Digi 002 Rack
|
||||
* Digi 003 Console
|
||||
* Digi 003 Rack
|
||||
* Digi 003 Rack+
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called snd-firewire-digi00x.
|
||||
|
||||
config SND_FIREWIRE_TASCAM
|
||||
tristate "TASCAM FireWire series support"
|
||||
select SND_FIREWIRE_LIB
|
||||
select SND_HWDEP
|
||||
help
|
||||
Say Y here to include support for TASCAM.
|
||||
* FW-1884
|
||||
* FW-1082
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called snd-firewire-tascam.
|
||||
|
||||
endif # SND_FIREWIRE
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
snd-firewire-lib-objs := lib.o iso-resources.o packets-buffer.o \
|
||||
fcp.o cmp.o amdtp.o
|
||||
snd-oxfw-objs := oxfw.o
|
||||
fcp.o cmp.o amdtp-stream.o amdtp-am824.o
|
||||
snd-isight-objs := isight.o
|
||||
snd-scs1x-objs := scs1x.o
|
||||
|
||||
|
@ -11,3 +10,5 @@ obj-$(CONFIG_SND_ISIGHT) += snd-isight.o
|
|||
obj-$(CONFIG_SND_SCS1X) += snd-scs1x.o
|
||||
obj-$(CONFIG_SND_FIREWORKS) += fireworks/
|
||||
obj-$(CONFIG_SND_BEBOB) += bebob/
|
||||
obj-$(CONFIG_SND_FIREWIRE_DIGI00X) += digi00x/
|
||||
obj-$(CONFIG_SND_FIREWIRE_TASCAM) += tascam/
|
||||
|
|
|
@ -0,0 +1,465 @@
|
|||
/*
|
||||
* AM824 format in Audio and Music Data Transmission Protocol (IEC 61883-6)
|
||||
*
|
||||
* Copyright (c) Clemens Ladisch <clemens@ladisch.de>
|
||||
* Copyright (c) 2015 Takashi Sakamoto <o-takashi@sakamocchi.jp>
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "amdtp-am824.h"
|
||||
|
||||
#define CIP_FMT_AM 0x10
|
||||
|
||||
/* "Clock-based rate control mode" is just supported. */
|
||||
#define AMDTP_FDF_AM824 0x00
|
||||
|
||||
/*
|
||||
* Nominally 3125 bytes/second, but the MIDI port's clock might be
|
||||
* 1% too slow, and the bus clock 100 ppm too fast.
|
||||
*/
|
||||
#define MIDI_BYTES_PER_SECOND 3093
|
||||
|
||||
/*
|
||||
* Several devices look only at the first eight data blocks.
|
||||
* In any case, this is more than enough for the MIDI data rate.
|
||||
*/
|
||||
#define MAX_MIDI_RX_BLOCKS 8
|
||||
|
||||
struct amdtp_am824 {
|
||||
struct snd_rawmidi_substream *midi[AM824_MAX_CHANNELS_FOR_MIDI * 8];
|
||||
int midi_fifo_limit;
|
||||
int midi_fifo_used[AM824_MAX_CHANNELS_FOR_MIDI * 8];
|
||||
unsigned int pcm_channels;
|
||||
unsigned int midi_ports;
|
||||
|
||||
u8 pcm_positions[AM824_MAX_CHANNELS_FOR_PCM];
|
||||
u8 midi_position;
|
||||
|
||||
void (*transfer_samples)(struct amdtp_stream *s,
|
||||
struct snd_pcm_substream *pcm,
|
||||
__be32 *buffer, unsigned int frames);
|
||||
|
||||
unsigned int frame_multiplier;
|
||||
};
|
||||
|
||||
/**
|
||||
* amdtp_am824_set_parameters - set stream parameters
|
||||
* @s: the AMDTP stream to configure
|
||||
* @rate: the sample rate
|
||||
* @pcm_channels: the number of PCM samples in each data block, to be encoded
|
||||
* as AM824 multi-bit linear audio
|
||||
* @midi_ports: the number of MIDI ports (i.e., MPX-MIDI Data Channels)
|
||||
* @double_pcm_frames: one data block transfers two PCM frames
|
||||
*
|
||||
* The parameters must be set before the stream is started, and must not be
|
||||
* changed while the stream is running.
|
||||
*/
|
||||
int amdtp_am824_set_parameters(struct amdtp_stream *s, unsigned int rate,
|
||||
unsigned int pcm_channels,
|
||||
unsigned int midi_ports,
|
||||
bool double_pcm_frames)
|
||||
{
|
||||
struct amdtp_am824 *p = s->protocol;
|
||||
unsigned int midi_channels;
|
||||
unsigned int i;
|
||||
int err;
|
||||
|
||||
if (amdtp_stream_running(s))
|
||||
return -EINVAL;
|
||||
|
||||
if (pcm_channels > AM824_MAX_CHANNELS_FOR_PCM)
|
||||
return -EINVAL;
|
||||
|
||||
midi_channels = DIV_ROUND_UP(midi_ports, 8);
|
||||
if (midi_channels > AM824_MAX_CHANNELS_FOR_MIDI)
|
||||
return -EINVAL;
|
||||
|
||||
if (WARN_ON(amdtp_stream_running(s)) ||
|
||||
WARN_ON(pcm_channels > AM824_MAX_CHANNELS_FOR_PCM) ||
|
||||
WARN_ON(midi_channels > AM824_MAX_CHANNELS_FOR_MIDI))
|
||||
return -EINVAL;
|
||||
|
||||
err = amdtp_stream_set_parameters(s, rate,
|
||||
pcm_channels + midi_channels);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
s->fdf = AMDTP_FDF_AM824 | s->sfc;
|
||||
|
||||
p->pcm_channels = pcm_channels;
|
||||
p->midi_ports = midi_ports;
|
||||
|
||||
/*
|
||||
* In IEC 61883-6, one data block represents one event. In ALSA, one
|
||||
* event equals to one PCM frame. But Dice has a quirk at higher
|
||||
* sampling rate to transfer two PCM frames in one data block.
|
||||
*/
|
||||
if (double_pcm_frames)
|
||||
p->frame_multiplier = 2;
|
||||
else
|
||||
p->frame_multiplier = 1;
|
||||
|
||||
/* init the position map for PCM and MIDI channels */
|
||||
for (i = 0; i < pcm_channels; i++)
|
||||
p->pcm_positions[i] = i;
|
||||
p->midi_position = p->pcm_channels;
|
||||
|
||||
/*
|
||||
* We do not know the actual MIDI FIFO size of most devices. Just
|
||||
* assume two bytes, i.e., one byte can be received over the bus while
|
||||
* the previous one is transmitted over MIDI.
|
||||
* (The value here is adjusted for midi_ratelimit_per_packet().)
|
||||
*/
|
||||
p->midi_fifo_limit = rate - MIDI_BYTES_PER_SECOND * s->syt_interval + 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(amdtp_am824_set_parameters);
|
||||
|
||||
/**
|
||||
* amdtp_am824_set_pcm_position - set an index of data channel for a channel
|
||||
* of PCM frame
|
||||
* @s: the AMDTP stream
|
||||
* @index: the index of data channel in an data block
|
||||
* @position: the channel of PCM frame
|
||||
*/
|
||||
void amdtp_am824_set_pcm_position(struct amdtp_stream *s, unsigned int index,
|
||||
unsigned int position)
|
||||
{
|
||||
struct amdtp_am824 *p = s->protocol;
|
||||
|
||||
if (index < p->pcm_channels)
|
||||
p->pcm_positions[index] = position;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(amdtp_am824_set_pcm_position);
|
||||
|
||||
/**
|
||||
* amdtp_am824_set_midi_position - set a index of data channel for MIDI
|
||||
* conformant data channel
|
||||
* @s: the AMDTP stream
|
||||
* @position: the index of data channel in an data block
|
||||
*/
|
||||
void amdtp_am824_set_midi_position(struct amdtp_stream *s,
|
||||
unsigned int position)
|
||||
{
|
||||
struct amdtp_am824 *p = s->protocol;
|
||||
|
||||
p->midi_position = position;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(amdtp_am824_set_midi_position);
|
||||
|
||||
static void write_pcm_s32(struct amdtp_stream *s,
|
||||
struct snd_pcm_substream *pcm,
|
||||
__be32 *buffer, unsigned int frames)
|
||||
{
|
||||
struct amdtp_am824 *p = s->protocol;
|
||||
struct snd_pcm_runtime *runtime = pcm->runtime;
|
||||
unsigned int channels, remaining_frames, i, c;
|
||||
const u32 *src;
|
||||
|
||||
channels = p->pcm_channels;
|
||||
src = (void *)runtime->dma_area +
|
||||
frames_to_bytes(runtime, s->pcm_buffer_pointer);
|
||||
remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer;
|
||||
|
||||
for (i = 0; i < frames; ++i) {
|
||||
for (c = 0; c < channels; ++c) {
|
||||
buffer[p->pcm_positions[c]] =
|
||||
cpu_to_be32((*src >> 8) | 0x40000000);
|
||||
src++;
|
||||
}
|
||||
buffer += s->data_block_quadlets;
|
||||
if (--remaining_frames == 0)
|
||||
src = (void *)runtime->dma_area;
|
||||
}
|
||||
}
|
||||
|
||||
static void write_pcm_s16(struct amdtp_stream *s,
|
||||
struct snd_pcm_substream *pcm,
|
||||
__be32 *buffer, unsigned int frames)
|
||||
{
|
||||
struct amdtp_am824 *p = s->protocol;
|
||||
struct snd_pcm_runtime *runtime = pcm->runtime;
|
||||
unsigned int channels, remaining_frames, i, c;
|
||||
const u16 *src;
|
||||
|
||||
channels = p->pcm_channels;
|
||||
src = (void *)runtime->dma_area +
|
||||
frames_to_bytes(runtime, s->pcm_buffer_pointer);
|
||||
remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer;
|
||||
|
||||
for (i = 0; i < frames; ++i) {
|
||||
for (c = 0; c < channels; ++c) {
|
||||
buffer[p->pcm_positions[c]] =
|
||||
cpu_to_be32((*src << 8) | 0x42000000);
|
||||
src++;
|
||||
}
|
||||
buffer += s->data_block_quadlets;
|
||||
if (--remaining_frames == 0)
|
||||
src = (void *)runtime->dma_area;
|
||||
}
|
||||
}
|
||||
|
||||
static void read_pcm_s32(struct amdtp_stream *s,
|
||||
struct snd_pcm_substream *pcm,
|
||||
__be32 *buffer, unsigned int frames)
|
||||
{
|
||||
struct amdtp_am824 *p = s->protocol;
|
||||
struct snd_pcm_runtime *runtime = pcm->runtime;
|
||||
unsigned int channels, remaining_frames, i, c;
|
||||
u32 *dst;
|
||||
|
||||
channels = p->pcm_channels;
|
||||
dst = (void *)runtime->dma_area +
|
||||
frames_to_bytes(runtime, s->pcm_buffer_pointer);
|
||||
remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer;
|
||||
|
||||
for (i = 0; i < frames; ++i) {
|
||||
for (c = 0; c < channels; ++c) {
|
||||
*dst = be32_to_cpu(buffer[p->pcm_positions[c]]) << 8;
|
||||
dst++;
|
||||
}
|
||||
buffer += s->data_block_quadlets;
|
||||
if (--remaining_frames == 0)
|
||||
dst = (void *)runtime->dma_area;
|
||||
}
|
||||
}
|
||||
|
||||
static void write_pcm_silence(struct amdtp_stream *s,
|
||||
__be32 *buffer, unsigned int frames)
|
||||
{
|
||||
struct amdtp_am824 *p = s->protocol;
|
||||
unsigned int i, c, channels = p->pcm_channels;
|
||||
|
||||
for (i = 0; i < frames; ++i) {
|
||||
for (c = 0; c < channels; ++c)
|
||||
buffer[p->pcm_positions[c]] = cpu_to_be32(0x40000000);
|
||||
buffer += s->data_block_quadlets;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* amdtp_am824_set_pcm_format - set the PCM format
|
||||
* @s: the AMDTP stream to configure
|
||||
* @format: the format of the ALSA PCM device
|
||||
*
|
||||
* The sample format must be set after the other parameters (rate/PCM channels/
|
||||
* MIDI) and before the stream is started, and must not be changed while the
|
||||
* stream is running.
|
||||
*/
|
||||
void amdtp_am824_set_pcm_format(struct amdtp_stream *s, snd_pcm_format_t format)
|
||||
{
|
||||
struct amdtp_am824 *p = s->protocol;
|
||||
|
||||
if (WARN_ON(amdtp_stream_pcm_running(s)))
|
||||
return;
|
||||
|
||||
switch (format) {
|
||||
default:
|
||||
WARN_ON(1);
|
||||
/* fall through */
|
||||
case SNDRV_PCM_FORMAT_S16:
|
||||
if (s->direction == AMDTP_OUT_STREAM) {
|
||||
p->transfer_samples = write_pcm_s16;
|
||||
break;
|
||||
}
|
||||
WARN_ON(1);
|
||||
/* fall through */
|
||||
case SNDRV_PCM_FORMAT_S32:
|
||||
if (s->direction == AMDTP_OUT_STREAM)
|
||||
p->transfer_samples = write_pcm_s32;
|
||||
else
|
||||
p->transfer_samples = read_pcm_s32;
|
||||
break;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(amdtp_am824_set_pcm_format);
|
||||
|
||||
/**
|
||||
* amdtp_am824_add_pcm_hw_constraints - add hw constraints for PCM substream
|
||||
* @s: the AMDTP stream for AM824 data block, must be initialized.
|
||||
* @runtime: the PCM substream runtime
|
||||
*
|
||||
*/
|
||||
int amdtp_am824_add_pcm_hw_constraints(struct amdtp_stream *s,
|
||||
struct snd_pcm_runtime *runtime)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = amdtp_stream_add_pcm_hw_constraints(s, runtime);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* AM824 in IEC 61883-6 can deliver 24bit data. */
|
||||
return snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(amdtp_am824_add_pcm_hw_constraints);
|
||||
|
||||
/**
|
||||
* amdtp_am824_midi_trigger - start/stop playback/capture with a MIDI device
|
||||
* @s: the AMDTP stream
|
||||
* @port: index of MIDI port
|
||||
* @midi: the MIDI device to be started, or %NULL to stop the current device
|
||||
*
|
||||
* Call this function on a running isochronous stream to enable the actual
|
||||
* transmission of MIDI data. This function should be called from the MIDI
|
||||
* device's .trigger callback.
|
||||
*/
|
||||
void amdtp_am824_midi_trigger(struct amdtp_stream *s, unsigned int port,
|
||||
struct snd_rawmidi_substream *midi)
|
||||
{
|
||||
struct amdtp_am824 *p = s->protocol;
|
||||
|
||||
if (port < p->midi_ports)
|
||||
ACCESS_ONCE(p->midi[port]) = midi;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(amdtp_am824_midi_trigger);
|
||||
|
||||
/*
|
||||
* To avoid sending MIDI bytes at too high a rate, assume that the receiving
|
||||
* device has a FIFO, and track how much it is filled. This values increases
|
||||
* by one whenever we send one byte in a packet, but the FIFO empties at
|
||||
* a constant rate independent of our packet rate. One packet has syt_interval
|
||||
* samples, so the number of bytes that empty out of the FIFO, per packet(!),
|
||||
* is MIDI_BYTES_PER_SECOND * syt_interval / sample_rate. To avoid storing
|
||||
* fractional values, the values in midi_fifo_used[] are measured in bytes
|
||||
* multiplied by the sample rate.
|
||||
*/
|
||||
static bool midi_ratelimit_per_packet(struct amdtp_stream *s, unsigned int port)
|
||||
{
|
||||
struct amdtp_am824 *p = s->protocol;
|
||||
int used;
|
||||
|
||||
used = p->midi_fifo_used[port];
|
||||
if (used == 0) /* common shortcut */
|
||||
return true;
|
||||
|
||||
used -= MIDI_BYTES_PER_SECOND * s->syt_interval;
|
||||
used = max(used, 0);
|
||||
p->midi_fifo_used[port] = used;
|
||||
|
||||
return used < p->midi_fifo_limit;
|
||||
}
|
||||
|
||||
static void midi_rate_use_one_byte(struct amdtp_stream *s, unsigned int port)
|
||||
{
|
||||
struct amdtp_am824 *p = s->protocol;
|
||||
|
||||
p->midi_fifo_used[port] += amdtp_rate_table[s->sfc];
|
||||
}
|
||||
|
||||
static void write_midi_messages(struct amdtp_stream *s, __be32 *buffer,
|
||||
unsigned int frames)
|
||||
{
|
||||
struct amdtp_am824 *p = s->protocol;
|
||||
unsigned int f, port;
|
||||
u8 *b;
|
||||
|
||||
for (f = 0; f < frames; f++) {
|
||||
b = (u8 *)&buffer[p->midi_position];
|
||||
|
||||
port = (s->data_block_counter + f) % 8;
|
||||
if (f < MAX_MIDI_RX_BLOCKS &&
|
||||
midi_ratelimit_per_packet(s, port) &&
|
||||
p->midi[port] != NULL &&
|
||||
snd_rawmidi_transmit(p->midi[port], &b[1], 1) == 1) {
|
||||
midi_rate_use_one_byte(s, port);
|
||||
b[0] = 0x81;
|
||||
} else {
|
||||
b[0] = 0x80;
|
||||
b[1] = 0;
|
||||
}
|
||||
b[2] = 0;
|
||||
b[3] = 0;
|
||||
|
||||
buffer += s->data_block_quadlets;
|
||||
}
|
||||
}
|
||||
|
||||
static void read_midi_messages(struct amdtp_stream *s,
|
||||
__be32 *buffer, unsigned int frames)
|
||||
{
|
||||
struct amdtp_am824 *p = s->protocol;
|
||||
unsigned int f, port;
|
||||
int len;
|
||||
u8 *b;
|
||||
|
||||
for (f = 0; f < frames; f++) {
|
||||
port = (s->data_block_counter + f) % 8;
|
||||
b = (u8 *)&buffer[p->midi_position];
|
||||
|
||||
len = b[0] - 0x80;
|
||||
if ((1 <= len) && (len <= 3) && (p->midi[port]))
|
||||
snd_rawmidi_receive(p->midi[port], b + 1, len);
|
||||
|
||||
buffer += s->data_block_quadlets;
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned int process_rx_data_blocks(struct amdtp_stream *s, __be32 *buffer,
|
||||
unsigned int data_blocks, unsigned int *syt)
|
||||
{
|
||||
struct amdtp_am824 *p = s->protocol;
|
||||
struct snd_pcm_substream *pcm = ACCESS_ONCE(s->pcm);
|
||||
unsigned int pcm_frames;
|
||||
|
||||
if (pcm) {
|
||||
p->transfer_samples(s, pcm, buffer, data_blocks);
|
||||
pcm_frames = data_blocks * p->frame_multiplier;
|
||||
} else {
|
||||
write_pcm_silence(s, buffer, data_blocks);
|
||||
pcm_frames = 0;
|
||||
}
|
||||
|
||||
if (p->midi_ports)
|
||||
write_midi_messages(s, buffer, data_blocks);
|
||||
|
||||
return pcm_frames;
|
||||
}
|
||||
|
||||
static unsigned int process_tx_data_blocks(struct amdtp_stream *s, __be32 *buffer,
|
||||
unsigned int data_blocks, unsigned int *syt)
|
||||
{
|
||||
struct amdtp_am824 *p = s->protocol;
|
||||
struct snd_pcm_substream *pcm = ACCESS_ONCE(s->pcm);
|
||||
unsigned int pcm_frames;
|
||||
|
||||
if (pcm) {
|
||||
p->transfer_samples(s, pcm, buffer, data_blocks);
|
||||
pcm_frames = data_blocks * p->frame_multiplier;
|
||||
} else {
|
||||
pcm_frames = 0;
|
||||
}
|
||||
|
||||
if (p->midi_ports)
|
||||
read_midi_messages(s, buffer, data_blocks);
|
||||
|
||||
return pcm_frames;
|
||||
}
|
||||
|
||||
/**
|
||||
* amdtp_am824_init - initialize an AMDTP stream structure to handle AM824
|
||||
* data block
|
||||
* @s: the AMDTP stream to initialize
|
||||
* @unit: the target of the stream
|
||||
* @dir: the direction of stream
|
||||
* @flags: the packet transmission method to use
|
||||
*/
|
||||
int amdtp_am824_init(struct amdtp_stream *s, struct fw_unit *unit,
|
||||
enum amdtp_stream_direction dir, enum cip_flags flags)
|
||||
{
|
||||
amdtp_stream_process_data_blocks_t process_data_blocks;
|
||||
|
||||
if (dir == AMDTP_IN_STREAM)
|
||||
process_data_blocks = process_tx_data_blocks;
|
||||
else
|
||||
process_data_blocks = process_rx_data_blocks;
|
||||
|
||||
return amdtp_stream_init(s, unit, dir, flags, CIP_FMT_AM,
|
||||
process_data_blocks,
|
||||
sizeof(struct amdtp_am824));
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(amdtp_am824_init);
|
|
@ -0,0 +1,52 @@
|
|||
#ifndef SOUND_FIREWIRE_AMDTP_AM824_H_INCLUDED
|
||||
#define SOUND_FIREWIRE_AMDTP_AM824_H_INCLUDED
|
||||
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/rawmidi.h>
|
||||
|
||||
#include "amdtp-stream.h"
|
||||
|
||||
#define AM824_IN_PCM_FORMAT_BITS SNDRV_PCM_FMTBIT_S32
|
||||
|
||||
#define AM824_OUT_PCM_FORMAT_BITS (SNDRV_PCM_FMTBIT_S16 | \
|
||||
SNDRV_PCM_FMTBIT_S32)
|
||||
|
||||
/*
|
||||
* This module supports maximum 64 PCM channels for one PCM stream
|
||||
* This is for our convenience.
|
||||
*/
|
||||
#define AM824_MAX_CHANNELS_FOR_PCM 64
|
||||
|
||||
/*
|
||||
* AMDTP packet can include channels for MIDI conformant data.
|
||||
* Each MIDI conformant data channel includes 8 MPX-MIDI data stream.
|
||||
* Each MPX-MIDI data stream includes one data stream from/to MIDI ports.
|
||||
*
|
||||
* This module supports maximum 1 MIDI conformant data channels.
|
||||
* Then this AMDTP packets can transfer maximum 8 MIDI data streams.
|
||||
*/
|
||||
#define AM824_MAX_CHANNELS_FOR_MIDI 1
|
||||
|
||||
int amdtp_am824_set_parameters(struct amdtp_stream *s, unsigned int rate,
|
||||
unsigned int pcm_channels,
|
||||
unsigned int midi_ports,
|
||||
bool double_pcm_frames);
|
||||
|
||||
void amdtp_am824_set_pcm_position(struct amdtp_stream *s, unsigned int index,
|
||||
unsigned int position);
|
||||
|
||||
void amdtp_am824_set_midi_position(struct amdtp_stream *s,
|
||||
unsigned int position);
|
||||
|
||||
int amdtp_am824_add_pcm_hw_constraints(struct amdtp_stream *s,
|
||||
struct snd_pcm_runtime *runtime);
|
||||
|
||||
void amdtp_am824_set_pcm_format(struct amdtp_stream *s,
|
||||
snd_pcm_format_t format);
|
||||
|
||||
void amdtp_am824_midi_trigger(struct amdtp_stream *s, unsigned int port,
|
||||
struct snd_rawmidi_substream *midi);
|
||||
|
||||
int amdtp_am824_init(struct amdtp_stream *s, struct fw_unit *unit,
|
||||
enum amdtp_stream_direction dir, enum cip_flags flags);
|
||||
#endif
|
|
@ -11,28 +11,14 @@
|
|||
#include <linux/firewire.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/sched.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/rawmidi.h>
|
||||
#include "amdtp.h"
|
||||
#include "amdtp-stream.h"
|
||||
|
||||
#define TICKS_PER_CYCLE 3072
|
||||
#define CYCLES_PER_SECOND 8000
|
||||
#define TICKS_PER_SECOND (TICKS_PER_CYCLE * CYCLES_PER_SECOND)
|
||||
|
||||
/*
|
||||
* Nominally 3125 bytes/second, but the MIDI port's clock might be
|
||||
* 1% too slow, and the bus clock 100 ppm too fast.
|
||||
*/
|
||||
#define MIDI_BYTES_PER_SECOND 3093
|
||||
|
||||
/*
|
||||
* Several devices look only at the first eight data blocks.
|
||||
* In any case, this is more than enough for the MIDI data rate.
|
||||
*/
|
||||
#define MAX_MIDI_RX_BLOCKS 8
|
||||
|
||||
#define TRANSFER_DELAY_TICKS 0x2e00 /* 479.17 microseconds */
|
||||
|
||||
/* isochronous header parameters */
|
||||
|
@ -55,12 +41,8 @@
|
|||
#define CIP_SYT_MASK 0x0000ffff
|
||||
#define CIP_SYT_NO_INFO 0xffff
|
||||
|
||||
/*
|
||||
* Audio and Music transfer protocol specific parameters
|
||||
* only "Clock-based rate control mode" is supported
|
||||
*/
|
||||
#define CIP_FMT_AM (0x10 << CIP_FMT_SHIFT)
|
||||
#define AMDTP_FDF_AM824 (0 << (CIP_FDF_SHIFT + 3))
|
||||
/* Audio and Music transfer protocol specific parameters */
|
||||
#define CIP_FMT_AM 0x10
|
||||
#define AMDTP_FDF_NO_DATA 0xff
|
||||
|
||||
/* TODO: make these configurable */
|
||||
|
@ -78,10 +60,23 @@ static void pcm_period_tasklet(unsigned long data);
|
|||
* @unit: the target of the stream
|
||||
* @dir: the direction of stream
|
||||
* @flags: the packet transmission method to use
|
||||
* @fmt: the value of fmt field in CIP header
|
||||
* @process_data_blocks: callback handler to process data blocks
|
||||
* @protocol_size: the size to allocate newly for protocol
|
||||
*/
|
||||
int amdtp_stream_init(struct amdtp_stream *s, struct fw_unit *unit,
|
||||
enum amdtp_stream_direction dir, enum cip_flags flags)
|
||||
enum amdtp_stream_direction dir, enum cip_flags flags,
|
||||
unsigned int fmt,
|
||||
amdtp_stream_process_data_blocks_t process_data_blocks,
|
||||
unsigned int protocol_size)
|
||||
{
|
||||
if (process_data_blocks == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
s->protocol = kzalloc(protocol_size, GFP_KERNEL);
|
||||
if (!s->protocol)
|
||||
return -ENOMEM;
|
||||
|
||||
s->unit = unit;
|
||||
s->direction = dir;
|
||||
s->flags = flags;
|
||||
|
@ -94,6 +89,9 @@ int amdtp_stream_init(struct amdtp_stream *s, struct fw_unit *unit,
|
|||
s->callbacked = false;
|
||||
s->sync_slave = NULL;
|
||||
|
||||
s->fmt = fmt;
|
||||
s->process_data_blocks = process_data_blocks;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(amdtp_stream_init);
|
||||
|
@ -105,6 +103,7 @@ EXPORT_SYMBOL(amdtp_stream_init);
|
|||
void amdtp_stream_destroy(struct amdtp_stream *s)
|
||||
{
|
||||
WARN_ON(amdtp_stream_running(s));
|
||||
kfree(s->protocol);
|
||||
mutex_destroy(&s->mutex);
|
||||
}
|
||||
EXPORT_SYMBOL(amdtp_stream_destroy);
|
||||
|
@ -141,11 +140,6 @@ int amdtp_stream_add_pcm_hw_constraints(struct amdtp_stream *s,
|
|||
{
|
||||
int err;
|
||||
|
||||
/* AM824 in IEC 61883-6 can deliver 24bit data */
|
||||
err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
/*
|
||||
* Currently firewire-lib processes 16 packets in one software
|
||||
* interrupt callback. This equals to 2msec but actually the
|
||||
|
@ -190,39 +184,25 @@ EXPORT_SYMBOL(amdtp_stream_add_pcm_hw_constraints);
|
|||
* amdtp_stream_set_parameters - set stream parameters
|
||||
* @s: the AMDTP stream to configure
|
||||
* @rate: the sample rate
|
||||
* @pcm_channels: the number of PCM samples in each data block, to be encoded
|
||||
* as AM824 multi-bit linear audio
|
||||
* @midi_ports: the number of MIDI ports (i.e., MPX-MIDI Data Channels)
|
||||
* @data_block_quadlets: the size of a data block in quadlet unit
|
||||
*
|
||||
* The parameters must be set before the stream is started, and must not be
|
||||
* changed while the stream is running.
|
||||
*/
|
||||
void amdtp_stream_set_parameters(struct amdtp_stream *s,
|
||||
unsigned int rate,
|
||||
unsigned int pcm_channels,
|
||||
unsigned int midi_ports)
|
||||
int amdtp_stream_set_parameters(struct amdtp_stream *s, unsigned int rate,
|
||||
unsigned int data_block_quadlets)
|
||||
{
|
||||
unsigned int i, sfc, midi_channels;
|
||||
unsigned int sfc;
|
||||
|
||||
midi_channels = DIV_ROUND_UP(midi_ports, 8);
|
||||
|
||||
if (WARN_ON(amdtp_stream_running(s)) |
|
||||
WARN_ON(pcm_channels > AMDTP_MAX_CHANNELS_FOR_PCM) |
|
||||
WARN_ON(midi_channels > AMDTP_MAX_CHANNELS_FOR_MIDI))
|
||||
return;
|
||||
|
||||
for (sfc = 0; sfc < ARRAY_SIZE(amdtp_rate_table); ++sfc)
|
||||
for (sfc = 0; sfc < ARRAY_SIZE(amdtp_rate_table); ++sfc) {
|
||||
if (amdtp_rate_table[sfc] == rate)
|
||||
goto sfc_found;
|
||||
WARN_ON(1);
|
||||
return;
|
||||
break;
|
||||
}
|
||||
if (sfc == ARRAY_SIZE(amdtp_rate_table))
|
||||
return -EINVAL;
|
||||
|
||||
sfc_found:
|
||||
s->pcm_channels = pcm_channels;
|
||||
s->sfc = sfc;
|
||||
s->data_block_quadlets = s->pcm_channels + midi_channels;
|
||||
s->midi_ports = midi_ports;
|
||||
|
||||
s->data_block_quadlets = data_block_quadlets;
|
||||
s->syt_interval = amdtp_syt_intervals[sfc];
|
||||
|
||||
/* default buffering in the device */
|
||||
|
@ -231,18 +211,7 @@ sfc_found:
|
|||
/* additional buffering needed to adjust for no-data packets */
|
||||
s->transfer_delay += TICKS_PER_SECOND * s->syt_interval / rate;
|
||||
|
||||
/* init the position map for PCM and MIDI channels */
|
||||
for (i = 0; i < pcm_channels; i++)
|
||||
s->pcm_positions[i] = i;
|
||||
s->midi_position = s->pcm_channels;
|
||||
|
||||
/*
|
||||
* We do not know the actual MIDI FIFO size of most devices. Just
|
||||
* assume two bytes, i.e., one byte can be received over the bus while
|
||||
* the previous one is transmitted over MIDI.
|
||||
* (The value here is adjusted for midi_ratelimit_per_packet().)
|
||||
*/
|
||||
s->midi_fifo_limit = rate - MIDI_BYTES_PER_SECOND * s->syt_interval + 1;
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(amdtp_stream_set_parameters);
|
||||
|
||||
|
@ -264,52 +233,6 @@ unsigned int amdtp_stream_get_max_payload(struct amdtp_stream *s)
|
|||
}
|
||||
EXPORT_SYMBOL(amdtp_stream_get_max_payload);
|
||||
|
||||
static void write_pcm_s16(struct amdtp_stream *s,
|
||||
struct snd_pcm_substream *pcm,
|
||||
__be32 *buffer, unsigned int frames);
|
||||
static void write_pcm_s32(struct amdtp_stream *s,
|
||||
struct snd_pcm_substream *pcm,
|
||||
__be32 *buffer, unsigned int frames);
|
||||
static void read_pcm_s32(struct amdtp_stream *s,
|
||||
struct snd_pcm_substream *pcm,
|
||||
__be32 *buffer, unsigned int frames);
|
||||
|
||||
/**
|
||||
* amdtp_stream_set_pcm_format - set the PCM format
|
||||
* @s: the AMDTP stream to configure
|
||||
* @format: the format of the ALSA PCM device
|
||||
*
|
||||
* The sample format must be set after the other parameters (rate/PCM channels/
|
||||
* MIDI) and before the stream is started, and must not be changed while the
|
||||
* stream is running.
|
||||
*/
|
||||
void amdtp_stream_set_pcm_format(struct amdtp_stream *s,
|
||||
snd_pcm_format_t format)
|
||||
{
|
||||
if (WARN_ON(amdtp_stream_pcm_running(s)))
|
||||
return;
|
||||
|
||||
switch (format) {
|
||||
default:
|
||||
WARN_ON(1);
|
||||
/* fall through */
|
||||
case SNDRV_PCM_FORMAT_S16:
|
||||
if (s->direction == AMDTP_OUT_STREAM) {
|
||||
s->transfer_samples = write_pcm_s16;
|
||||
break;
|
||||
}
|
||||
WARN_ON(1);
|
||||
/* fall through */
|
||||
case SNDRV_PCM_FORMAT_S32:
|
||||
if (s->direction == AMDTP_OUT_STREAM)
|
||||
s->transfer_samples = write_pcm_s32;
|
||||
else
|
||||
s->transfer_samples = read_pcm_s32;
|
||||
break;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(amdtp_stream_set_pcm_format);
|
||||
|
||||
/**
|
||||
* amdtp_stream_pcm_prepare - prepare PCM device for running
|
||||
* @s: the AMDTP stream
|
||||
|
@ -412,182 +335,12 @@ static unsigned int calculate_syt(struct amdtp_stream *s,
|
|||
}
|
||||
}
|
||||
|
||||
static void write_pcm_s32(struct amdtp_stream *s,
|
||||
struct snd_pcm_substream *pcm,
|
||||
__be32 *buffer, unsigned int frames)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = pcm->runtime;
|
||||
unsigned int channels, remaining_frames, i, c;
|
||||
const u32 *src;
|
||||
|
||||
channels = s->pcm_channels;
|
||||
src = (void *)runtime->dma_area +
|
||||
frames_to_bytes(runtime, s->pcm_buffer_pointer);
|
||||
remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer;
|
||||
|
||||
for (i = 0; i < frames; ++i) {
|
||||
for (c = 0; c < channels; ++c) {
|
||||
buffer[s->pcm_positions[c]] =
|
||||
cpu_to_be32((*src >> 8) | 0x40000000);
|
||||
src++;
|
||||
}
|
||||
buffer += s->data_block_quadlets;
|
||||
if (--remaining_frames == 0)
|
||||
src = (void *)runtime->dma_area;
|
||||
}
|
||||
}
|
||||
|
||||
static void write_pcm_s16(struct amdtp_stream *s,
|
||||
struct snd_pcm_substream *pcm,
|
||||
__be32 *buffer, unsigned int frames)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = pcm->runtime;
|
||||
unsigned int channels, remaining_frames, i, c;
|
||||
const u16 *src;
|
||||
|
||||
channels = s->pcm_channels;
|
||||
src = (void *)runtime->dma_area +
|
||||
frames_to_bytes(runtime, s->pcm_buffer_pointer);
|
||||
remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer;
|
||||
|
||||
for (i = 0; i < frames; ++i) {
|
||||
for (c = 0; c < channels; ++c) {
|
||||
buffer[s->pcm_positions[c]] =
|
||||
cpu_to_be32((*src << 8) | 0x42000000);
|
||||
src++;
|
||||
}
|
||||
buffer += s->data_block_quadlets;
|
||||
if (--remaining_frames == 0)
|
||||
src = (void *)runtime->dma_area;
|
||||
}
|
||||
}
|
||||
|
||||
static void read_pcm_s32(struct amdtp_stream *s,
|
||||
struct snd_pcm_substream *pcm,
|
||||
__be32 *buffer, unsigned int frames)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = pcm->runtime;
|
||||
unsigned int channels, remaining_frames, i, c;
|
||||
u32 *dst;
|
||||
|
||||
channels = s->pcm_channels;
|
||||
dst = (void *)runtime->dma_area +
|
||||
frames_to_bytes(runtime, s->pcm_buffer_pointer);
|
||||
remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer;
|
||||
|
||||
for (i = 0; i < frames; ++i) {
|
||||
for (c = 0; c < channels; ++c) {
|
||||
*dst = be32_to_cpu(buffer[s->pcm_positions[c]]) << 8;
|
||||
dst++;
|
||||
}
|
||||
buffer += s->data_block_quadlets;
|
||||
if (--remaining_frames == 0)
|
||||
dst = (void *)runtime->dma_area;
|
||||
}
|
||||
}
|
||||
|
||||
static void write_pcm_silence(struct amdtp_stream *s,
|
||||
__be32 *buffer, unsigned int frames)
|
||||
{
|
||||
unsigned int i, c;
|
||||
|
||||
for (i = 0; i < frames; ++i) {
|
||||
for (c = 0; c < s->pcm_channels; ++c)
|
||||
buffer[s->pcm_positions[c]] = cpu_to_be32(0x40000000);
|
||||
buffer += s->data_block_quadlets;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* To avoid sending MIDI bytes at too high a rate, assume that the receiving
|
||||
* device has a FIFO, and track how much it is filled. This values increases
|
||||
* by one whenever we send one byte in a packet, but the FIFO empties at
|
||||
* a constant rate independent of our packet rate. One packet has syt_interval
|
||||
* samples, so the number of bytes that empty out of the FIFO, per packet(!),
|
||||
* is MIDI_BYTES_PER_SECOND * syt_interval / sample_rate. To avoid storing
|
||||
* fractional values, the values in midi_fifo_used[] are measured in bytes
|
||||
* multiplied by the sample rate.
|
||||
*/
|
||||
static bool midi_ratelimit_per_packet(struct amdtp_stream *s, unsigned int port)
|
||||
{
|
||||
int used;
|
||||
|
||||
used = s->midi_fifo_used[port];
|
||||
if (used == 0) /* common shortcut */
|
||||
return true;
|
||||
|
||||
used -= MIDI_BYTES_PER_SECOND * s->syt_interval;
|
||||
used = max(used, 0);
|
||||
s->midi_fifo_used[port] = used;
|
||||
|
||||
return used < s->midi_fifo_limit;
|
||||
}
|
||||
|
||||
static void midi_rate_use_one_byte(struct amdtp_stream *s, unsigned int port)
|
||||
{
|
||||
s->midi_fifo_used[port] += amdtp_rate_table[s->sfc];
|
||||
}
|
||||
|
||||
static void write_midi_messages(struct amdtp_stream *s,
|
||||
__be32 *buffer, unsigned int frames)
|
||||
{
|
||||
unsigned int f, port;
|
||||
u8 *b;
|
||||
|
||||
for (f = 0; f < frames; f++) {
|
||||
b = (u8 *)&buffer[s->midi_position];
|
||||
|
||||
port = (s->data_block_counter + f) % 8;
|
||||
if (f < MAX_MIDI_RX_BLOCKS &&
|
||||
midi_ratelimit_per_packet(s, port) &&
|
||||
s->midi[port] != NULL &&
|
||||
snd_rawmidi_transmit(s->midi[port], &b[1], 1) == 1) {
|
||||
midi_rate_use_one_byte(s, port);
|
||||
b[0] = 0x81;
|
||||
} else {
|
||||
b[0] = 0x80;
|
||||
b[1] = 0;
|
||||
}
|
||||
b[2] = 0;
|
||||
b[3] = 0;
|
||||
|
||||
buffer += s->data_block_quadlets;
|
||||
}
|
||||
}
|
||||
|
||||
static void read_midi_messages(struct amdtp_stream *s,
|
||||
__be32 *buffer, unsigned int frames)
|
||||
{
|
||||
unsigned int f, port;
|
||||
int len;
|
||||
u8 *b;
|
||||
|
||||
for (f = 0; f < frames; f++) {
|
||||
port = (s->data_block_counter + f) % 8;
|
||||
b = (u8 *)&buffer[s->midi_position];
|
||||
|
||||
len = b[0] - 0x80;
|
||||
if ((1 <= len) && (len <= 3) && (s->midi[port]))
|
||||
snd_rawmidi_receive(s->midi[port], b + 1, len);
|
||||
|
||||
buffer += s->data_block_quadlets;
|
||||
}
|
||||
}
|
||||
|
||||
static void update_pcm_pointers(struct amdtp_stream *s,
|
||||
struct snd_pcm_substream *pcm,
|
||||
unsigned int frames)
|
||||
{
|
||||
unsigned int ptr;
|
||||
|
||||
/*
|
||||
* In IEC 61883-6, one data block represents one event. In ALSA, one
|
||||
* event equals to one PCM frame. But Dice has a quirk to transfer
|
||||
* two PCM frames in one data block.
|
||||
*/
|
||||
if (s->double_pcm_frames)
|
||||
frames *= 2;
|
||||
|
||||
ptr = s->pcm_buffer_pointer + frames;
|
||||
if (ptr >= pcm->runtime->buffer_size)
|
||||
ptr -= pcm->runtime->buffer_size;
|
||||
|
@ -656,23 +409,19 @@ static int handle_out_packet(struct amdtp_stream *s, unsigned int data_blocks,
|
|||
{
|
||||
__be32 *buffer;
|
||||
unsigned int payload_length;
|
||||
unsigned int pcm_frames;
|
||||
struct snd_pcm_substream *pcm;
|
||||
|
||||
buffer = s->buffer.packets[s->packet_index].buffer;
|
||||
pcm_frames = s->process_data_blocks(s, buffer + 2, data_blocks, &syt);
|
||||
|
||||
buffer[0] = cpu_to_be32(ACCESS_ONCE(s->source_node_id_field) |
|
||||
(s->data_block_quadlets << CIP_DBS_SHIFT) |
|
||||
s->data_block_counter);
|
||||
buffer[1] = cpu_to_be32(CIP_EOH | CIP_FMT_AM | AMDTP_FDF_AM824 |
|
||||
(s->sfc << CIP_FDF_SHIFT) | syt);
|
||||
buffer += 2;
|
||||
|
||||
pcm = ACCESS_ONCE(s->pcm);
|
||||
if (pcm)
|
||||
s->transfer_samples(s, pcm, buffer, data_blocks);
|
||||
else
|
||||
write_pcm_silence(s, buffer, data_blocks);
|
||||
if (s->midi_ports)
|
||||
write_midi_messages(s, buffer, data_blocks);
|
||||
buffer[1] = cpu_to_be32(CIP_EOH |
|
||||
((s->fmt << CIP_FMT_SHIFT) & CIP_FMT_MASK) |
|
||||
((s->fdf << CIP_FDF_SHIFT) & CIP_FDF_MASK) |
|
||||
(syt & CIP_SYT_MASK));
|
||||
|
||||
s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff;
|
||||
|
||||
|
@ -680,8 +429,9 @@ static int handle_out_packet(struct amdtp_stream *s, unsigned int data_blocks,
|
|||
if (queue_out_packet(s, payload_length, false) < 0)
|
||||
return -EIO;
|
||||
|
||||
if (pcm)
|
||||
update_pcm_pointers(s, pcm, data_blocks);
|
||||
pcm = ACCESS_ONCE(s->pcm);
|
||||
if (pcm && pcm_frames > 0)
|
||||
update_pcm_pointers(s, pcm, pcm_frames);
|
||||
|
||||
/* No need to return the number of handled data blocks. */
|
||||
return 0;
|
||||
|
@ -689,11 +439,13 @@ static int handle_out_packet(struct amdtp_stream *s, unsigned int data_blocks,
|
|||
|
||||
static int handle_in_packet(struct amdtp_stream *s,
|
||||
unsigned int payload_quadlets, __be32 *buffer,
|
||||
unsigned int *data_blocks)
|
||||
unsigned int *data_blocks, unsigned int syt)
|
||||
{
|
||||
u32 cip_header[2];
|
||||
unsigned int fmt, fdf;
|
||||
unsigned int data_block_quadlets, data_block_counter, dbc_interval;
|
||||
struct snd_pcm_substream *pcm = NULL;
|
||||
struct snd_pcm_substream *pcm;
|
||||
unsigned int pcm_frames;
|
||||
bool lost;
|
||||
|
||||
cip_header[0] = be32_to_cpu(buffer[0]);
|
||||
|
@ -704,19 +456,30 @@ static int handle_in_packet(struct amdtp_stream *s,
|
|||
* For convenience, also check FMT field is AM824 or not.
|
||||
*/
|
||||
if (((cip_header[0] & CIP_EOH_MASK) == CIP_EOH) ||
|
||||
((cip_header[1] & CIP_EOH_MASK) != CIP_EOH) ||
|
||||
((cip_header[1] & CIP_FMT_MASK) != CIP_FMT_AM)) {
|
||||
((cip_header[1] & CIP_EOH_MASK) != CIP_EOH)) {
|
||||
dev_info_ratelimited(&s->unit->device,
|
||||
"Invalid CIP header for AMDTP: %08X:%08X\n",
|
||||
cip_header[0], cip_header[1]);
|
||||
*data_blocks = 0;
|
||||
pcm_frames = 0;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Check valid protocol or not. */
|
||||
fmt = (cip_header[1] & CIP_FMT_MASK) >> CIP_FMT_SHIFT;
|
||||
if (fmt != s->fmt) {
|
||||
dev_info_ratelimited(&s->unit->device,
|
||||
"Detect unexpected protocol: %08x %08x\n",
|
||||
cip_header[0], cip_header[1]);
|
||||
*data_blocks = 0;
|
||||
pcm_frames = 0;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Calculate data blocks */
|
||||
fdf = (cip_header[1] & CIP_FDF_MASK) >> CIP_FDF_SHIFT;
|
||||
if (payload_quadlets < 3 ||
|
||||
((cip_header[1] & CIP_FDF_MASK) ==
|
||||
(AMDTP_FDF_NO_DATA << CIP_FDF_SHIFT))) {
|
||||
(fmt == CIP_FMT_AM && fdf == AMDTP_FDF_NO_DATA)) {
|
||||
*data_blocks = 0;
|
||||
} else {
|
||||
data_block_quadlets =
|
||||
|
@ -763,16 +526,7 @@ static int handle_in_packet(struct amdtp_stream *s,
|
|||
return -EIO;
|
||||
}
|
||||
|
||||
if (*data_blocks > 0) {
|
||||
buffer += 2;
|
||||
|
||||
pcm = ACCESS_ONCE(s->pcm);
|
||||
if (pcm)
|
||||
s->transfer_samples(s, pcm, buffer, *data_blocks);
|
||||
|
||||
if (s->midi_ports)
|
||||
read_midi_messages(s, buffer, *data_blocks);
|
||||
}
|
||||
pcm_frames = s->process_data_blocks(s, buffer + 2, *data_blocks, &syt);
|
||||
|
||||
if (s->flags & CIP_DBC_IS_END_EVENT)
|
||||
s->data_block_counter = data_block_counter;
|
||||
|
@ -783,8 +537,9 @@ end:
|
|||
if (queue_in_packet(s) < 0)
|
||||
return -EIO;
|
||||
|
||||
if (pcm)
|
||||
update_pcm_pointers(s, pcm, *data_blocks);
|
||||
pcm = ACCESS_ONCE(s->pcm);
|
||||
if (pcm && pcm_frames > 0)
|
||||
update_pcm_pointers(s, pcm, pcm_frames);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -854,15 +609,15 @@ static void in_stream_callback(struct fw_iso_context *context, u32 cycle,
|
|||
break;
|
||||
}
|
||||
|
||||
syt = be32_to_cpu(buffer[1]) & CIP_SYT_MASK;
|
||||
if (handle_in_packet(s, payload_quadlets, buffer,
|
||||
&data_blocks) < 0) {
|
||||
&data_blocks, syt) < 0) {
|
||||
s->packet_index = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Process sync slave stream */
|
||||
if (s->sync_slave && s->sync_slave->callbacked) {
|
||||
syt = be32_to_cpu(buffer[1]) & CIP_SYT_MASK;
|
||||
if (handle_out_packet(s->sync_slave,
|
||||
data_blocks, syt) < 0) {
|
||||
s->packet_index = -1;
|
|
@ -4,6 +4,7 @@
|
|||
#include <linux/err.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/sched.h>
|
||||
#include <sound/asound.h>
|
||||
#include "packets-buffer.h"
|
||||
|
||||
|
@ -80,100 +81,78 @@ enum cip_sfc {
|
|||
CIP_SFC_COUNT
|
||||
};
|
||||
|
||||
#define AMDTP_IN_PCM_FORMAT_BITS SNDRV_PCM_FMTBIT_S32
|
||||
|
||||
#define AMDTP_OUT_PCM_FORMAT_BITS (SNDRV_PCM_FMTBIT_S16 | \
|
||||
SNDRV_PCM_FMTBIT_S32)
|
||||
|
||||
|
||||
/*
|
||||
* This module supports maximum 64 PCM channels for one PCM stream
|
||||
* This is for our convenience.
|
||||
*/
|
||||
#define AMDTP_MAX_CHANNELS_FOR_PCM 64
|
||||
|
||||
/*
|
||||
* AMDTP packet can include channels for MIDI conformant data.
|
||||
* Each MIDI conformant data channel includes 8 MPX-MIDI data stream.
|
||||
* Each MPX-MIDI data stream includes one data stream from/to MIDI ports.
|
||||
*
|
||||
* This module supports maximum 1 MIDI conformant data channels.
|
||||
* Then this AMDTP packets can transfer maximum 8 MIDI data streams.
|
||||
*/
|
||||
#define AMDTP_MAX_CHANNELS_FOR_MIDI 1
|
||||
|
||||
struct fw_unit;
|
||||
struct fw_iso_context;
|
||||
struct snd_pcm_substream;
|
||||
struct snd_pcm_runtime;
|
||||
struct snd_rawmidi_substream;
|
||||
|
||||
enum amdtp_stream_direction {
|
||||
AMDTP_OUT_STREAM = 0,
|
||||
AMDTP_IN_STREAM
|
||||
};
|
||||
|
||||
struct amdtp_stream;
|
||||
typedef unsigned int (*amdtp_stream_process_data_blocks_t)(
|
||||
struct amdtp_stream *s,
|
||||
__be32 *buffer,
|
||||
unsigned int data_blocks,
|
||||
unsigned int *syt);
|
||||
struct amdtp_stream {
|
||||
struct fw_unit *unit;
|
||||
enum cip_flags flags;
|
||||
enum amdtp_stream_direction direction;
|
||||
struct fw_iso_context *context;
|
||||
struct mutex mutex;
|
||||
|
||||
enum cip_sfc sfc;
|
||||
unsigned int data_block_quadlets;
|
||||
unsigned int pcm_channels;
|
||||
unsigned int midi_ports;
|
||||
void (*transfer_samples)(struct amdtp_stream *s,
|
||||
struct snd_pcm_substream *pcm,
|
||||
__be32 *buffer, unsigned int frames);
|
||||
u8 pcm_positions[AMDTP_MAX_CHANNELS_FOR_PCM];
|
||||
u8 midi_position;
|
||||
|
||||
unsigned int syt_interval;
|
||||
unsigned int transfer_delay;
|
||||
unsigned int source_node_id_field;
|
||||
/* For packet processing. */
|
||||
struct fw_iso_context *context;
|
||||
struct iso_packets_buffer buffer;
|
||||
|
||||
struct snd_pcm_substream *pcm;
|
||||
struct tasklet_struct period_tasklet;
|
||||
|
||||
int packet_index;
|
||||
|
||||
/* For CIP headers. */
|
||||
unsigned int source_node_id_field;
|
||||
unsigned int data_block_quadlets;
|
||||
unsigned int data_block_counter;
|
||||
|
||||
unsigned int data_block_state;
|
||||
|
||||
unsigned int last_syt_offset;
|
||||
unsigned int syt_offset_state;
|
||||
|
||||
unsigned int pcm_buffer_pointer;
|
||||
unsigned int pcm_period_pointer;
|
||||
bool pointer_flush;
|
||||
bool double_pcm_frames;
|
||||
|
||||
struct snd_rawmidi_substream *midi[AMDTP_MAX_CHANNELS_FOR_MIDI * 8];
|
||||
int midi_fifo_limit;
|
||||
int midi_fifo_used[AMDTP_MAX_CHANNELS_FOR_MIDI * 8];
|
||||
|
||||
unsigned int fmt;
|
||||
unsigned int fdf;
|
||||
/* quirk: fixed interval of dbc between previos/current packets. */
|
||||
unsigned int tx_dbc_interval;
|
||||
/* quirk: indicate the value of dbc field in a first packet. */
|
||||
unsigned int tx_first_dbc;
|
||||
|
||||
/* Internal flags. */
|
||||
enum cip_sfc sfc;
|
||||
unsigned int syt_interval;
|
||||
unsigned int transfer_delay;
|
||||
unsigned int data_block_state;
|
||||
unsigned int last_syt_offset;
|
||||
unsigned int syt_offset_state;
|
||||
|
||||
/* For a PCM substream processing. */
|
||||
struct snd_pcm_substream *pcm;
|
||||
struct tasklet_struct period_tasklet;
|
||||
unsigned int pcm_buffer_pointer;
|
||||
unsigned int pcm_period_pointer;
|
||||
bool pointer_flush;
|
||||
|
||||
/* To wait for first packet. */
|
||||
bool callbacked;
|
||||
wait_queue_head_t callback_wait;
|
||||
struct amdtp_stream *sync_slave;
|
||||
|
||||
/* For backends to process data blocks. */
|
||||
void *protocol;
|
||||
amdtp_stream_process_data_blocks_t process_data_blocks;
|
||||
};
|
||||
|
||||
int amdtp_stream_init(struct amdtp_stream *s, struct fw_unit *unit,
|
||||
enum amdtp_stream_direction dir,
|
||||
enum cip_flags flags);
|
||||
enum amdtp_stream_direction dir, enum cip_flags flags,
|
||||
unsigned int fmt,
|
||||
amdtp_stream_process_data_blocks_t process_data_blocks,
|
||||
unsigned int protocol_size);
|
||||
void amdtp_stream_destroy(struct amdtp_stream *s);
|
||||
|
||||
void amdtp_stream_set_parameters(struct amdtp_stream *s,
|
||||
unsigned int rate,
|
||||
unsigned int pcm_channels,
|
||||
unsigned int midi_ports);
|
||||
int amdtp_stream_set_parameters(struct amdtp_stream *s, unsigned int rate,
|
||||
unsigned int data_block_quadlets);
|
||||
unsigned int amdtp_stream_get_max_payload(struct amdtp_stream *s);
|
||||
|
||||
int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed);
|
||||
|
@ -182,8 +161,7 @@ void amdtp_stream_stop(struct amdtp_stream *s);
|
|||
|
||||
int amdtp_stream_add_pcm_hw_constraints(struct amdtp_stream *s,
|
||||
struct snd_pcm_runtime *runtime);
|
||||
void amdtp_stream_set_pcm_format(struct amdtp_stream *s,
|
||||
snd_pcm_format_t format);
|
||||
|
||||
void amdtp_stream_pcm_prepare(struct amdtp_stream *s);
|
||||
unsigned long amdtp_stream_pcm_pointer(struct amdtp_stream *s);
|
||||
void amdtp_stream_pcm_abort(struct amdtp_stream *s);
|
||||
|
@ -240,24 +218,6 @@ static inline void amdtp_stream_pcm_trigger(struct amdtp_stream *s,
|
|||
ACCESS_ONCE(s->pcm) = pcm;
|
||||
}
|
||||
|
||||
/**
|
||||
* amdtp_stream_midi_trigger - start/stop playback/capture with a MIDI device
|
||||
* @s: the AMDTP stream
|
||||
* @port: index of MIDI port
|
||||
* @midi: the MIDI device to be started, or %NULL to stop the current device
|
||||
*
|
||||
* Call this function on a running isochronous stream to enable the actual
|
||||
* transmission of MIDI data. This function should be called from the MIDI
|
||||
* device's .trigger callback.
|
||||
*/
|
||||
static inline void amdtp_stream_midi_trigger(struct amdtp_stream *s,
|
||||
unsigned int port,
|
||||
struct snd_rawmidi_substream *midi)
|
||||
{
|
||||
if (port < s->midi_ports)
|
||||
ACCESS_ONCE(s->midi[port]) = midi;
|
||||
}
|
||||
|
||||
static inline bool cip_sfc_is_base_44100(enum cip_sfc sfc)
|
||||
{
|
||||
return sfc & 1;
|
|
@ -1,4 +1,4 @@
|
|||
snd-bebob-objs := bebob_command.o bebob_stream.o bebob_proc.o bebob_midi.o \
|
||||
bebob_pcm.o bebob_hwdep.o bebob_terratec.o bebob_yamaha.o \
|
||||
bebob_focusrite.o bebob_maudio.o bebob.o
|
||||
obj-m += snd-bebob.o
|
||||
obj-$(CONFIG_SND_BEBOB) += snd-bebob.o
|
||||
|
|
|
@ -41,7 +41,8 @@ static DECLARE_BITMAP(devices_used, SNDRV_CARDS);
|
|||
#define VEN_EDIROL 0x000040ab
|
||||
#define VEN_PRESONUS 0x00000a92
|
||||
#define VEN_BRIDGECO 0x000007f5
|
||||
#define VEN_MACKIE 0x0000000f
|
||||
#define VEN_MACKIE1 0x0000000f
|
||||
#define VEN_MACKIE2 0x00000ff2
|
||||
#define VEN_STANTON 0x00001260
|
||||
#define VEN_TASCAM 0x0000022e
|
||||
#define VEN_BEHRINGER 0x00001564
|
||||
|
@ -334,7 +335,7 @@ static void bebob_remove(struct fw_unit *unit)
|
|||
snd_card_free_when_closed(bebob->card);
|
||||
}
|
||||
|
||||
static struct snd_bebob_rate_spec normal_rate_spec = {
|
||||
static const struct snd_bebob_rate_spec normal_rate_spec = {
|
||||
.get = &snd_bebob_stream_get_rate,
|
||||
.set = &snd_bebob_stream_set_rate
|
||||
};
|
||||
|
@ -360,9 +361,9 @@ static const struct ieee1394_device_id bebob_id_table[] = {
|
|||
/* BridgeCo, Audio5 */
|
||||
SND_BEBOB_DEV_ENTRY(VEN_BRIDGECO, 0x00010049, &spec_normal),
|
||||
/* Mackie, Onyx 1220/1620/1640 (Firewire I/O Card) */
|
||||
SND_BEBOB_DEV_ENTRY(VEN_MACKIE, 0x00010065, &spec_normal),
|
||||
SND_BEBOB_DEV_ENTRY(VEN_MACKIE2, 0x00010065, &spec_normal),
|
||||
/* Mackie, d.2 (Firewire Option) */
|
||||
SND_BEBOB_DEV_ENTRY(VEN_MACKIE, 0x00010067, &spec_normal),
|
||||
SND_BEBOB_DEV_ENTRY(VEN_MACKIE1, 0x00010067, &spec_normal),
|
||||
/* Stanton, ScratchAmp */
|
||||
SND_BEBOB_DEV_ENTRY(VEN_STANTON, 0x00000001, &spec_normal),
|
||||
/* Tascam, IF-FW DM */
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
#include "../fcp.h"
|
||||
#include "../packets-buffer.h"
|
||||
#include "../iso-resources.h"
|
||||
#include "../amdtp.h"
|
||||
#include "../amdtp-am824.h"
|
||||
#include "../cmp.h"
|
||||
|
||||
/* basic register addresses on DM1000/DM1100/DM1500 */
|
||||
|
@ -70,9 +70,9 @@ struct snd_bebob_meter_spec {
|
|||
int (*get)(struct snd_bebob *bebob, u32 *target, unsigned int size);
|
||||
};
|
||||
struct snd_bebob_spec {
|
||||
struct snd_bebob_clock_spec *clock;
|
||||
struct snd_bebob_rate_spec *rate;
|
||||
struct snd_bebob_meter_spec *meter;
|
||||
const struct snd_bebob_clock_spec *clock;
|
||||
const struct snd_bebob_rate_spec *rate;
|
||||
const struct snd_bebob_meter_spec *meter;
|
||||
};
|
||||
|
||||
struct snd_bebob {
|
||||
|
@ -235,19 +235,19 @@ int snd_bebob_create_pcm_devices(struct snd_bebob *bebob);
|
|||
int snd_bebob_create_hwdep_device(struct snd_bebob *bebob);
|
||||
|
||||
/* model specific operations */
|
||||
extern struct snd_bebob_spec phase88_rack_spec;
|
||||
extern struct snd_bebob_spec phase24_series_spec;
|
||||
extern struct snd_bebob_spec yamaha_go_spec;
|
||||
extern struct snd_bebob_spec saffirepro_26_spec;
|
||||
extern struct snd_bebob_spec saffirepro_10_spec;
|
||||
extern struct snd_bebob_spec saffire_le_spec;
|
||||
extern struct snd_bebob_spec saffire_spec;
|
||||
extern struct snd_bebob_spec maudio_fw410_spec;
|
||||
extern struct snd_bebob_spec maudio_audiophile_spec;
|
||||
extern struct snd_bebob_spec maudio_solo_spec;
|
||||
extern struct snd_bebob_spec maudio_ozonic_spec;
|
||||
extern struct snd_bebob_spec maudio_nrv10_spec;
|
||||
extern struct snd_bebob_spec maudio_special_spec;
|
||||
extern const struct snd_bebob_spec phase88_rack_spec;
|
||||
extern const struct snd_bebob_spec phase24_series_spec;
|
||||
extern const struct snd_bebob_spec yamaha_go_spec;
|
||||
extern const struct snd_bebob_spec saffirepro_26_spec;
|
||||
extern const struct snd_bebob_spec saffirepro_10_spec;
|
||||
extern const struct snd_bebob_spec saffire_le_spec;
|
||||
extern const struct snd_bebob_spec saffire_spec;
|
||||
extern const struct snd_bebob_spec maudio_fw410_spec;
|
||||
extern const struct snd_bebob_spec maudio_audiophile_spec;
|
||||
extern const struct snd_bebob_spec maudio_solo_spec;
|
||||
extern const struct snd_bebob_spec maudio_ozonic_spec;
|
||||
extern const struct snd_bebob_spec maudio_nrv10_spec;
|
||||
extern const struct snd_bebob_spec maudio_special_spec;
|
||||
int snd_bebob_maudio_special_discover(struct snd_bebob *bebob, bool is1814);
|
||||
int snd_bebob_maudio_load_firmware(struct fw_unit *unit);
|
||||
|
||||
|
|
|
@ -200,7 +200,7 @@ end:
|
|||
return err;
|
||||
}
|
||||
|
||||
struct snd_bebob_spec saffire_le_spec;
|
||||
const struct snd_bebob_spec saffire_le_spec;
|
||||
static enum snd_bebob_clock_type saffire_both_clk_src_types[] = {
|
||||
SND_BEBOB_CLOCK_TYPE_INTERNAL,
|
||||
SND_BEBOB_CLOCK_TYPE_EXTERNAL,
|
||||
|
@ -229,7 +229,7 @@ static const char *const saffire_meter_labels[] = {
|
|||
static int
|
||||
saffire_meter_get(struct snd_bebob *bebob, u32 *buf, unsigned int size)
|
||||
{
|
||||
struct snd_bebob_meter_spec *spec = bebob->spec->meter;
|
||||
const struct snd_bebob_meter_spec *spec = bebob->spec->meter;
|
||||
unsigned int channels;
|
||||
u64 offset;
|
||||
int err;
|
||||
|
@ -260,60 +260,60 @@ saffire_meter_get(struct snd_bebob *bebob, u32 *buf, unsigned int size)
|
|||
return err;
|
||||
}
|
||||
|
||||
static struct snd_bebob_rate_spec saffirepro_both_rate_spec = {
|
||||
static const struct snd_bebob_rate_spec saffirepro_both_rate_spec = {
|
||||
.get = &saffirepro_both_clk_freq_get,
|
||||
.set = &saffirepro_both_clk_freq_set,
|
||||
};
|
||||
/* Saffire Pro 26 I/O */
|
||||
static struct snd_bebob_clock_spec saffirepro_26_clk_spec = {
|
||||
static const struct snd_bebob_clock_spec saffirepro_26_clk_spec = {
|
||||
.num = ARRAY_SIZE(saffirepro_26_clk_src_types),
|
||||
.types = saffirepro_26_clk_src_types,
|
||||
.get = &saffirepro_both_clk_src_get,
|
||||
};
|
||||
struct snd_bebob_spec saffirepro_26_spec = {
|
||||
const struct snd_bebob_spec saffirepro_26_spec = {
|
||||
.clock = &saffirepro_26_clk_spec,
|
||||
.rate = &saffirepro_both_rate_spec,
|
||||
.meter = NULL
|
||||
};
|
||||
/* Saffire Pro 10 I/O */
|
||||
static struct snd_bebob_clock_spec saffirepro_10_clk_spec = {
|
||||
static const struct snd_bebob_clock_spec saffirepro_10_clk_spec = {
|
||||
.num = ARRAY_SIZE(saffirepro_10_clk_src_types),
|
||||
.types = saffirepro_10_clk_src_types,
|
||||
.get = &saffirepro_both_clk_src_get,
|
||||
};
|
||||
struct snd_bebob_spec saffirepro_10_spec = {
|
||||
const struct snd_bebob_spec saffirepro_10_spec = {
|
||||
.clock = &saffirepro_10_clk_spec,
|
||||
.rate = &saffirepro_both_rate_spec,
|
||||
.meter = NULL
|
||||
};
|
||||
|
||||
static struct snd_bebob_rate_spec saffire_both_rate_spec = {
|
||||
static const struct snd_bebob_rate_spec saffire_both_rate_spec = {
|
||||
.get = &snd_bebob_stream_get_rate,
|
||||
.set = &snd_bebob_stream_set_rate,
|
||||
};
|
||||
static struct snd_bebob_clock_spec saffire_both_clk_spec = {
|
||||
static const struct snd_bebob_clock_spec saffire_both_clk_spec = {
|
||||
.num = ARRAY_SIZE(saffire_both_clk_src_types),
|
||||
.types = saffire_both_clk_src_types,
|
||||
.get = &saffire_both_clk_src_get,
|
||||
};
|
||||
/* Saffire LE */
|
||||
static struct snd_bebob_meter_spec saffire_le_meter_spec = {
|
||||
static const struct snd_bebob_meter_spec saffire_le_meter_spec = {
|
||||
.num = ARRAY_SIZE(saffire_le_meter_labels),
|
||||
.labels = saffire_le_meter_labels,
|
||||
.get = &saffire_meter_get,
|
||||
};
|
||||
struct snd_bebob_spec saffire_le_spec = {
|
||||
const struct snd_bebob_spec saffire_le_spec = {
|
||||
.clock = &saffire_both_clk_spec,
|
||||
.rate = &saffire_both_rate_spec,
|
||||
.meter = &saffire_le_meter_spec
|
||||
};
|
||||
/* Saffire */
|
||||
static struct snd_bebob_meter_spec saffire_meter_spec = {
|
||||
static const struct snd_bebob_meter_spec saffire_meter_spec = {
|
||||
.num = ARRAY_SIZE(saffire_meter_labels),
|
||||
.labels = saffire_meter_labels,
|
||||
.get = &saffire_meter_get,
|
||||
};
|
||||
struct snd_bebob_spec saffire_spec = {
|
||||
const struct snd_bebob_spec saffire_spec = {
|
||||
.clock = &saffire_both_clk_spec,
|
||||
.rate = &saffire_both_rate_spec,
|
||||
.meter = &saffire_meter_spec
|
||||
|
|
|
@ -628,7 +628,7 @@ static const char *const special_meter_labels[] = {
|
|||
static int
|
||||
special_meter_get(struct snd_bebob *bebob, u32 *target, unsigned int size)
|
||||
{
|
||||
u16 *buf;
|
||||
__be16 *buf;
|
||||
unsigned int i, c, channels;
|
||||
int err;
|
||||
|
||||
|
@ -687,7 +687,7 @@ static const char *const nrv10_meter_labels[] = {
|
|||
static int
|
||||
normal_meter_get(struct snd_bebob *bebob, u32 *buf, unsigned int size)
|
||||
{
|
||||
struct snd_bebob_meter_spec *spec = bebob->spec->meter;
|
||||
const struct snd_bebob_meter_spec *spec = bebob->spec->meter;
|
||||
unsigned int c, channels;
|
||||
int err;
|
||||
|
||||
|
@ -712,85 +712,85 @@ end:
|
|||
}
|
||||
|
||||
/* for special customized devices */
|
||||
static struct snd_bebob_rate_spec special_rate_spec = {
|
||||
static const struct snd_bebob_rate_spec special_rate_spec = {
|
||||
.get = &special_get_rate,
|
||||
.set = &special_set_rate,
|
||||
};
|
||||
static struct snd_bebob_clock_spec special_clk_spec = {
|
||||
static const struct snd_bebob_clock_spec special_clk_spec = {
|
||||
.num = ARRAY_SIZE(special_clk_types),
|
||||
.types = special_clk_types,
|
||||
.get = &special_clk_get,
|
||||
};
|
||||
static struct snd_bebob_meter_spec special_meter_spec = {
|
||||
static const struct snd_bebob_meter_spec special_meter_spec = {
|
||||
.num = ARRAY_SIZE(special_meter_labels),
|
||||
.labels = special_meter_labels,
|
||||
.get = &special_meter_get
|
||||
};
|
||||
struct snd_bebob_spec maudio_special_spec = {
|
||||
const struct snd_bebob_spec maudio_special_spec = {
|
||||
.clock = &special_clk_spec,
|
||||
.rate = &special_rate_spec,
|
||||
.meter = &special_meter_spec
|
||||
};
|
||||
|
||||
/* Firewire 410 specification */
|
||||
static struct snd_bebob_rate_spec usual_rate_spec = {
|
||||
static const struct snd_bebob_rate_spec usual_rate_spec = {
|
||||
.get = &snd_bebob_stream_get_rate,
|
||||
.set = &snd_bebob_stream_set_rate,
|
||||
};
|
||||
static struct snd_bebob_meter_spec fw410_meter_spec = {
|
||||
static const struct snd_bebob_meter_spec fw410_meter_spec = {
|
||||
.num = ARRAY_SIZE(fw410_meter_labels),
|
||||
.labels = fw410_meter_labels,
|
||||
.get = &normal_meter_get
|
||||
};
|
||||
struct snd_bebob_spec maudio_fw410_spec = {
|
||||
const struct snd_bebob_spec maudio_fw410_spec = {
|
||||
.clock = NULL,
|
||||
.rate = &usual_rate_spec,
|
||||
.meter = &fw410_meter_spec
|
||||
};
|
||||
|
||||
/* Firewire Audiophile specification */
|
||||
static struct snd_bebob_meter_spec audiophile_meter_spec = {
|
||||
static const struct snd_bebob_meter_spec audiophile_meter_spec = {
|
||||
.num = ARRAY_SIZE(audiophile_meter_labels),
|
||||
.labels = audiophile_meter_labels,
|
||||
.get = &normal_meter_get
|
||||
};
|
||||
struct snd_bebob_spec maudio_audiophile_spec = {
|
||||
const struct snd_bebob_spec maudio_audiophile_spec = {
|
||||
.clock = NULL,
|
||||
.rate = &usual_rate_spec,
|
||||
.meter = &audiophile_meter_spec
|
||||
};
|
||||
|
||||
/* Firewire Solo specification */
|
||||
static struct snd_bebob_meter_spec solo_meter_spec = {
|
||||
static const struct snd_bebob_meter_spec solo_meter_spec = {
|
||||
.num = ARRAY_SIZE(solo_meter_labels),
|
||||
.labels = solo_meter_labels,
|
||||
.get = &normal_meter_get
|
||||
};
|
||||
struct snd_bebob_spec maudio_solo_spec = {
|
||||
const struct snd_bebob_spec maudio_solo_spec = {
|
||||
.clock = NULL,
|
||||
.rate = &usual_rate_spec,
|
||||
.meter = &solo_meter_spec
|
||||
};
|
||||
|
||||
/* Ozonic specification */
|
||||
static struct snd_bebob_meter_spec ozonic_meter_spec = {
|
||||
static const struct snd_bebob_meter_spec ozonic_meter_spec = {
|
||||
.num = ARRAY_SIZE(ozonic_meter_labels),
|
||||
.labels = ozonic_meter_labels,
|
||||
.get = &normal_meter_get
|
||||
};
|
||||
struct snd_bebob_spec maudio_ozonic_spec = {
|
||||
const struct snd_bebob_spec maudio_ozonic_spec = {
|
||||
.clock = NULL,
|
||||
.rate = &usual_rate_spec,
|
||||
.meter = &ozonic_meter_spec
|
||||
};
|
||||
|
||||
/* NRV10 specification */
|
||||
static struct snd_bebob_meter_spec nrv10_meter_spec = {
|
||||
static const struct snd_bebob_meter_spec nrv10_meter_spec = {
|
||||
.num = ARRAY_SIZE(nrv10_meter_labels),
|
||||
.labels = nrv10_meter_labels,
|
||||
.get = &normal_meter_get
|
||||
};
|
||||
struct snd_bebob_spec maudio_nrv10_spec = {
|
||||
const struct snd_bebob_spec maudio_nrv10_spec = {
|
||||
.clock = NULL,
|
||||
.rate = &usual_rate_spec,
|
||||
.meter = &nrv10_meter_spec
|
||||
|
|
|
@ -72,11 +72,11 @@ static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up)
|
|||
spin_lock_irqsave(&bebob->lock, flags);
|
||||
|
||||
if (up)
|
||||
amdtp_stream_midi_trigger(&bebob->tx_stream,
|
||||
substrm->number, substrm);
|
||||
amdtp_am824_midi_trigger(&bebob->tx_stream,
|
||||
substrm->number, substrm);
|
||||
else
|
||||
amdtp_stream_midi_trigger(&bebob->tx_stream,
|
||||
substrm->number, NULL);
|
||||
amdtp_am824_midi_trigger(&bebob->tx_stream,
|
||||
substrm->number, NULL);
|
||||
|
||||
spin_unlock_irqrestore(&bebob->lock, flags);
|
||||
}
|
||||
|
@ -89,11 +89,11 @@ static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up)
|
|||
spin_lock_irqsave(&bebob->lock, flags);
|
||||
|
||||
if (up)
|
||||
amdtp_stream_midi_trigger(&bebob->rx_stream,
|
||||
substrm->number, substrm);
|
||||
amdtp_am824_midi_trigger(&bebob->rx_stream,
|
||||
substrm->number, substrm);
|
||||
else
|
||||
amdtp_stream_midi_trigger(&bebob->rx_stream,
|
||||
substrm->number, NULL);
|
||||
amdtp_am824_midi_trigger(&bebob->rx_stream,
|
||||
substrm->number, NULL);
|
||||
|
||||
spin_unlock_irqrestore(&bebob->lock, flags);
|
||||
}
|
||||
|
|
|
@ -122,11 +122,11 @@ pcm_init_hw_params(struct snd_bebob *bebob,
|
|||
SNDRV_PCM_INFO_MMAP_VALID;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
|
||||
runtime->hw.formats = AMDTP_IN_PCM_FORMAT_BITS;
|
||||
runtime->hw.formats = AM824_IN_PCM_FORMAT_BITS;
|
||||
s = &bebob->tx_stream;
|
||||
formations = bebob->tx_stream_formations;
|
||||
} else {
|
||||
runtime->hw.formats = AMDTP_OUT_PCM_FORMAT_BITS;
|
||||
runtime->hw.formats = AM824_OUT_PCM_FORMAT_BITS;
|
||||
s = &bebob->rx_stream;
|
||||
formations = bebob->rx_stream_formations;
|
||||
}
|
||||
|
@ -146,7 +146,7 @@ pcm_init_hw_params(struct snd_bebob *bebob,
|
|||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
err = amdtp_stream_add_pcm_hw_constraints(s, runtime);
|
||||
err = amdtp_am824_add_pcm_hw_constraints(s, runtime);
|
||||
end:
|
||||
return err;
|
||||
}
|
||||
|
@ -155,7 +155,7 @@ static int
|
|||
pcm_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_bebob *bebob = substream->private_data;
|
||||
struct snd_bebob_rate_spec *spec = bebob->spec->rate;
|
||||
const struct snd_bebob_rate_spec *spec = bebob->spec->rate;
|
||||
unsigned int sampling_rate;
|
||||
enum snd_bebob_clock_type src;
|
||||
int err;
|
||||
|
@ -220,8 +220,8 @@ pcm_capture_hw_params(struct snd_pcm_substream *substream,
|
|||
|
||||
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN)
|
||||
atomic_inc(&bebob->substreams_counter);
|
||||
amdtp_stream_set_pcm_format(&bebob->tx_stream,
|
||||
params_format(hw_params));
|
||||
|
||||
amdtp_am824_set_pcm_format(&bebob->tx_stream, params_format(hw_params));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -239,8 +239,8 @@ pcm_playback_hw_params(struct snd_pcm_substream *substream,
|
|||
|
||||
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN)
|
||||
atomic_inc(&bebob->substreams_counter);
|
||||
amdtp_stream_set_pcm_format(&bebob->rx_stream,
|
||||
params_format(hw_params));
|
||||
|
||||
amdtp_am824_set_pcm_format(&bebob->rx_stream, params_format(hw_params));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ proc_read_meters(struct snd_info_entry *entry,
|
|||
struct snd_info_buffer *buffer)
|
||||
{
|
||||
struct snd_bebob *bebob = entry->private_data;
|
||||
struct snd_bebob_meter_spec *spec = bebob->spec->meter;
|
||||
const struct snd_bebob_meter_spec *spec = bebob->spec->meter;
|
||||
u32 *buf;
|
||||
unsigned int i, c, channels, size;
|
||||
|
||||
|
@ -138,8 +138,8 @@ proc_read_clock(struct snd_info_entry *entry,
|
|||
"SYT-Match",
|
||||
};
|
||||
struct snd_bebob *bebob = entry->private_data;
|
||||
struct snd_bebob_rate_spec *rate_spec = bebob->spec->rate;
|
||||
struct snd_bebob_clock_spec *clk_spec = bebob->spec->clock;
|
||||
const struct snd_bebob_rate_spec *rate_spec = bebob->spec->rate;
|
||||
const struct snd_bebob_clock_spec *clk_spec = bebob->spec->clock;
|
||||
enum snd_bebob_clock_type src;
|
||||
unsigned int rate;
|
||||
|
||||
|
|
|
@ -119,7 +119,7 @@ end:
|
|||
int snd_bebob_stream_get_clock_src(struct snd_bebob *bebob,
|
||||
enum snd_bebob_clock_type *src)
|
||||
{
|
||||
struct snd_bebob_clock_spec *clk_spec = bebob->spec->clock;
|
||||
const struct snd_bebob_clock_spec *clk_spec = bebob->spec->clock;
|
||||
u8 addr[AVC_BRIDGECO_ADDR_BYTES], input[7];
|
||||
unsigned int id;
|
||||
enum avc_bridgeco_plug_type type;
|
||||
|
@ -338,7 +338,7 @@ map_data_channels(struct snd_bebob *bebob, struct amdtp_stream *s)
|
|||
err = -ENOSYS;
|
||||
goto end;
|
||||
}
|
||||
s->midi_position = stm_pos;
|
||||
amdtp_am824_set_midi_position(s, stm_pos);
|
||||
midi = stm_pos;
|
||||
break;
|
||||
/* for PCM data channel */
|
||||
|
@ -354,11 +354,12 @@ map_data_channels(struct snd_bebob *bebob, struct amdtp_stream *s)
|
|||
case 0x09: /* Digital */
|
||||
default:
|
||||
location = pcm + sec_loc;
|
||||
if (location >= AMDTP_MAX_CHANNELS_FOR_PCM) {
|
||||
if (location >= AM824_MAX_CHANNELS_FOR_PCM) {
|
||||
err = -ENOSYS;
|
||||
goto end;
|
||||
}
|
||||
s->pcm_positions[location] = stm_pos;
|
||||
amdtp_am824_set_pcm_position(s, location,
|
||||
stm_pos);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -427,12 +428,19 @@ make_both_connections(struct snd_bebob *bebob, unsigned int rate)
|
|||
index = get_formation_index(rate);
|
||||
pcm_channels = bebob->tx_stream_formations[index].pcm;
|
||||
midi_channels = bebob->tx_stream_formations[index].midi;
|
||||
amdtp_stream_set_parameters(&bebob->tx_stream,
|
||||
rate, pcm_channels, midi_channels * 8);
|
||||
err = amdtp_am824_set_parameters(&bebob->tx_stream, rate,
|
||||
pcm_channels, midi_channels * 8,
|
||||
false);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
pcm_channels = bebob->rx_stream_formations[index].pcm;
|
||||
midi_channels = bebob->rx_stream_formations[index].midi;
|
||||
amdtp_stream_set_parameters(&bebob->rx_stream,
|
||||
rate, pcm_channels, midi_channels * 8);
|
||||
err = amdtp_am824_set_parameters(&bebob->rx_stream, rate,
|
||||
pcm_channels, midi_channels * 8,
|
||||
false);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
/* establish connections for both streams */
|
||||
err = cmp_connection_establish(&bebob->out_conn,
|
||||
|
@ -530,8 +538,8 @@ int snd_bebob_stream_init_duplex(struct snd_bebob *bebob)
|
|||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
err = amdtp_stream_init(&bebob->tx_stream, bebob->unit,
|
||||
AMDTP_IN_STREAM, CIP_BLOCKING);
|
||||
err = amdtp_am824_init(&bebob->tx_stream, bebob->unit,
|
||||
AMDTP_IN_STREAM, CIP_BLOCKING);
|
||||
if (err < 0) {
|
||||
amdtp_stream_destroy(&bebob->tx_stream);
|
||||
destroy_both_connections(bebob);
|
||||
|
@ -559,8 +567,8 @@ int snd_bebob_stream_init_duplex(struct snd_bebob *bebob)
|
|||
if (bebob->maudio_special_quirk)
|
||||
bebob->tx_stream.flags |= CIP_EMPTY_HAS_WRONG_DBC;
|
||||
|
||||
err = amdtp_stream_init(&bebob->rx_stream, bebob->unit,
|
||||
AMDTP_OUT_STREAM, CIP_BLOCKING);
|
||||
err = amdtp_am824_init(&bebob->rx_stream, bebob->unit,
|
||||
AMDTP_OUT_STREAM, CIP_BLOCKING);
|
||||
if (err < 0) {
|
||||
amdtp_stream_destroy(&bebob->tx_stream);
|
||||
amdtp_stream_destroy(&bebob->rx_stream);
|
||||
|
@ -572,7 +580,7 @@ end:
|
|||
|
||||
int snd_bebob_stream_start_duplex(struct snd_bebob *bebob, unsigned int rate)
|
||||
{
|
||||
struct snd_bebob_rate_spec *rate_spec = bebob->spec->rate;
|
||||
const struct snd_bebob_rate_spec *rate_spec = bebob->spec->rate;
|
||||
struct amdtp_stream *master, *slave;
|
||||
enum cip_flags sync_mode;
|
||||
unsigned int curr_rate;
|
||||
|
@ -864,8 +872,8 @@ parse_stream_formation(u8 *buf, unsigned int len,
|
|||
}
|
||||
}
|
||||
|
||||
if (formation[i].pcm > AMDTP_MAX_CHANNELS_FOR_PCM ||
|
||||
formation[i].midi > AMDTP_MAX_CHANNELS_FOR_MIDI)
|
||||
if (formation[i].pcm > AM824_MAX_CHANNELS_FOR_PCM ||
|
||||
formation[i].midi > AM824_MAX_CHANNELS_FOR_MIDI)
|
||||
return -ENOSYS;
|
||||
|
||||
return 0;
|
||||
|
@ -959,7 +967,7 @@ end:
|
|||
|
||||
int snd_bebob_stream_discover(struct snd_bebob *bebob)
|
||||
{
|
||||
struct snd_bebob_clock_spec *clk_spec = bebob->spec->clock;
|
||||
const struct snd_bebob_clock_spec *clk_spec = bebob->spec->clock;
|
||||
u8 plugs[AVC_PLUG_INFO_BUF_BYTES], addr[AVC_BRIDGECO_ADDR_BYTES];
|
||||
enum avc_bridgeco_plug_type type;
|
||||
unsigned int i;
|
||||
|
|
|
@ -55,30 +55,30 @@ phase24_series_clk_src_get(struct snd_bebob *bebob, unsigned int *id)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_bebob_rate_spec phase_series_rate_spec = {
|
||||
static const struct snd_bebob_rate_spec phase_series_rate_spec = {
|
||||
.get = &snd_bebob_stream_get_rate,
|
||||
.set = &snd_bebob_stream_set_rate,
|
||||
};
|
||||
|
||||
/* PHASE 88 Rack FW */
|
||||
static struct snd_bebob_clock_spec phase88_rack_clk = {
|
||||
static const struct snd_bebob_clock_spec phase88_rack_clk = {
|
||||
.num = ARRAY_SIZE(phase88_rack_clk_src_types),
|
||||
.types = phase88_rack_clk_src_types,
|
||||
.get = &phase88_rack_clk_src_get,
|
||||
};
|
||||
struct snd_bebob_spec phase88_rack_spec = {
|
||||
const struct snd_bebob_spec phase88_rack_spec = {
|
||||
.clock = &phase88_rack_clk,
|
||||
.rate = &phase_series_rate_spec,
|
||||
.meter = NULL
|
||||
};
|
||||
|
||||
/* 'PHASE 24 FW' and 'PHASE X24 FW' */
|
||||
static struct snd_bebob_clock_spec phase24_series_clk = {
|
||||
static const struct snd_bebob_clock_spec phase24_series_clk = {
|
||||
.num = ARRAY_SIZE(phase24_series_clk_src_types),
|
||||
.types = phase24_series_clk_src_types,
|
||||
.get = &phase24_series_clk_src_get,
|
||||
};
|
||||
struct snd_bebob_spec phase24_series_spec = {
|
||||
const struct snd_bebob_spec phase24_series_spec = {
|
||||
.clock = &phase24_series_clk,
|
||||
.rate = &phase_series_rate_spec,
|
||||
.meter = NULL
|
||||
|
|
|
@ -46,16 +46,16 @@ clk_src_get(struct snd_bebob *bebob, unsigned int *id)
|
|||
|
||||
return 0;
|
||||
}
|
||||
static struct snd_bebob_clock_spec clock_spec = {
|
||||
static const struct snd_bebob_clock_spec clock_spec = {
|
||||
.num = ARRAY_SIZE(clk_src_types),
|
||||
.types = clk_src_types,
|
||||
.get = &clk_src_get,
|
||||
};
|
||||
static struct snd_bebob_rate_spec rate_spec = {
|
||||
static const struct snd_bebob_rate_spec rate_spec = {
|
||||
.get = &snd_bebob_stream_get_rate,
|
||||
.set = &snd_bebob_stream_set_rate,
|
||||
};
|
||||
struct snd_bebob_spec yamaha_go_spec = {
|
||||
const struct snd_bebob_spec yamaha_go_spec = {
|
||||
.clock = &clock_spec,
|
||||
.rate = &rate_spec,
|
||||
.meter = NULL
|
||||
|
|
|
@ -1,3 +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
|
||||
obj-$(CONFIG_SND_DICE) += snd-dice.o
|
||||
|
|
|
@ -52,10 +52,10 @@ static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up)
|
|||
spin_lock_irqsave(&dice->lock, flags);
|
||||
|
||||
if (up)
|
||||
amdtp_stream_midi_trigger(&dice->tx_stream,
|
||||
amdtp_am824_midi_trigger(&dice->tx_stream,
|
||||
substrm->number, substrm);
|
||||
else
|
||||
amdtp_stream_midi_trigger(&dice->tx_stream,
|
||||
amdtp_am824_midi_trigger(&dice->tx_stream,
|
||||
substrm->number, NULL);
|
||||
|
||||
spin_unlock_irqrestore(&dice->lock, flags);
|
||||
|
@ -69,11 +69,11 @@ static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up)
|
|||
spin_lock_irqsave(&dice->lock, flags);
|
||||
|
||||
if (up)
|
||||
amdtp_stream_midi_trigger(&dice->rx_stream,
|
||||
substrm->number, substrm);
|
||||
amdtp_am824_midi_trigger(&dice->rx_stream,
|
||||
substrm->number, substrm);
|
||||
else
|
||||
amdtp_stream_midi_trigger(&dice->rx_stream,
|
||||
substrm->number, NULL);
|
||||
amdtp_am824_midi_trigger(&dice->rx_stream,
|
||||
substrm->number, NULL);
|
||||
|
||||
spin_unlock_irqrestore(&dice->lock, flags);
|
||||
}
|
||||
|
|
|
@ -133,11 +133,11 @@ static int init_hw_info(struct snd_dice *dice,
|
|||
SNDRV_PCM_INFO_BLOCK_TRANSFER;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
|
||||
hw->formats = AMDTP_IN_PCM_FORMAT_BITS;
|
||||
hw->formats = AM824_IN_PCM_FORMAT_BITS;
|
||||
stream = &dice->tx_stream;
|
||||
pcm_channels = dice->tx_channels;
|
||||
} else {
|
||||
hw->formats = AMDTP_OUT_PCM_FORMAT_BITS;
|
||||
hw->formats = AM824_OUT_PCM_FORMAT_BITS;
|
||||
stream = &dice->rx_stream;
|
||||
pcm_channels = dice->rx_channels;
|
||||
}
|
||||
|
@ -156,7 +156,7 @@ static int init_hw_info(struct snd_dice *dice,
|
|||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
err = amdtp_stream_add_pcm_hw_constraints(stream, runtime);
|
||||
err = amdtp_am824_add_pcm_hw_constraints(stream, runtime);
|
||||
end:
|
||||
return err;
|
||||
}
|
||||
|
@ -243,8 +243,7 @@ static int capture_hw_params(struct snd_pcm_substream *substream,
|
|||
mutex_unlock(&dice->mutex);
|
||||
}
|
||||
|
||||
amdtp_stream_set_pcm_format(&dice->tx_stream,
|
||||
params_format(hw_params));
|
||||
amdtp_am824_set_pcm_format(&dice->tx_stream, params_format(hw_params));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -265,8 +264,7 @@ static int playback_hw_params(struct snd_pcm_substream *substream,
|
|||
mutex_unlock(&dice->mutex);
|
||||
}
|
||||
|
||||
amdtp_stream_set_pcm_format(&dice->rx_stream,
|
||||
params_format(hw_params));
|
||||
amdtp_am824_set_pcm_format(&dice->rx_stream, params_format(hw_params));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -44,16 +44,16 @@ int snd_dice_stream_get_rate_mode(struct snd_dice *dice, unsigned int rate,
|
|||
static void release_resources(struct snd_dice *dice,
|
||||
struct fw_iso_resources *resources)
|
||||
{
|
||||
unsigned int channel;
|
||||
__be32 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);
|
||||
&channel, sizeof(channel));
|
||||
else
|
||||
snd_dice_transaction_write_rx(dice, RX_ISOCHRONOUS,
|
||||
&channel, 4);
|
||||
&channel, sizeof(channel));
|
||||
|
||||
fw_iso_resources_free(resources);
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ static int keep_resources(struct snd_dice *dice,
|
|||
struct fw_iso_resources *resources,
|
||||
unsigned int max_payload_bytes)
|
||||
{
|
||||
unsigned int channel;
|
||||
__be32 channel;
|
||||
int err;
|
||||
|
||||
err = fw_iso_resources_allocate(resources, max_payload_bytes,
|
||||
|
@ -74,10 +74,10 @@ static int keep_resources(struct snd_dice *dice,
|
|||
channel = cpu_to_be32(resources->channel);
|
||||
if (resources == &dice->tx_resources)
|
||||
err = snd_dice_transaction_write_tx(dice, TX_ISOCHRONOUS,
|
||||
&channel, 4);
|
||||
&channel, sizeof(channel));
|
||||
else
|
||||
err = snd_dice_transaction_write_rx(dice, RX_ISOCHRONOUS,
|
||||
&channel, 4);
|
||||
&channel, sizeof(channel));
|
||||
if (err < 0)
|
||||
release_resources(dice, resources);
|
||||
end:
|
||||
|
@ -100,6 +100,7 @@ static int start_stream(struct snd_dice *dice, struct amdtp_stream *stream,
|
|||
{
|
||||
struct fw_iso_resources *resources;
|
||||
unsigned int i, mode, pcm_chs, midi_ports;
|
||||
bool double_pcm_frames;
|
||||
int err;
|
||||
|
||||
err = snd_dice_stream_get_rate_mode(dice, rate, &mode);
|
||||
|
@ -125,21 +126,24 @@ static int start_stream(struct snd_dice *dice, struct amdtp_stream *stream,
|
|||
* For this quirk, blocking mode is required and PCM buffer size should
|
||||
* be aligned to SYT_INTERVAL.
|
||||
*/
|
||||
if (mode > 1) {
|
||||
double_pcm_frames = mode > 1;
|
||||
if (double_pcm_frames) {
|
||||
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) {
|
||||
err = amdtp_am824_set_parameters(stream, rate, pcm_chs, midi_ports,
|
||||
double_pcm_frames);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
if (double_pcm_frames) {
|
||||
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;
|
||||
amdtp_am824_set_pcm_position(stream, i, i * 2);
|
||||
amdtp_am824_set_pcm_position(stream, i + pcm_chs,
|
||||
i * 2 + 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -302,7 +306,7 @@ static int init_stream(struct snd_dice *dice, struct amdtp_stream *stream)
|
|||
goto end;
|
||||
resources->channels_mask = 0x00000000ffffffffuLL;
|
||||
|
||||
err = amdtp_stream_init(stream, dice->unit, dir, CIP_BLOCKING);
|
||||
err = amdtp_am824_init(stream, dice->unit, dir, CIP_BLOCKING);
|
||||
if (err < 0) {
|
||||
amdtp_stream_destroy(stream);
|
||||
fw_iso_resources_destroy(resources);
|
||||
|
|
|
@ -29,7 +29,8 @@ static int dice_interface_check(struct fw_unit *unit)
|
|||
struct fw_csr_iterator it;
|
||||
int key, val, vendor = -1, model = -1, err;
|
||||
unsigned int category, i;
|
||||
__be32 *pointers, value;
|
||||
__be32 *pointers;
|
||||
u32 value;
|
||||
__be32 version;
|
||||
|
||||
pointers = kmalloc_array(ARRAY_SIZE(min_values), sizeof(__be32),
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
#include <sound/pcm_params.h>
|
||||
#include <sound/rawmidi.h>
|
||||
|
||||
#include "../amdtp.h"
|
||||
#include "../amdtp-am824.h"
|
||||
#include "../iso-resources.h"
|
||||
#include "../lib.h"
|
||||
#include "dice-interface.h"
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
snd-firewire-digi00x-objs := amdtp-dot.o digi00x-stream.o digi00x-proc.o \
|
||||
digi00x-pcm.o digi00x-hwdep.o \
|
||||
digi00x-transaction.o digi00x-midi.o digi00x.o
|
||||
obj-$(CONFIG_SND_FIREWIRE_DIGI00X) += snd-firewire-digi00x.o
|
|
@ -0,0 +1,442 @@
|
|||
/*
|
||||
* amdtp-dot.c - a part of driver for Digidesign Digi 002/003 family
|
||||
*
|
||||
* Copyright (c) 2014-2015 Takashi Sakamoto
|
||||
* Copyright (C) 2012 Robin Gareus <robin@gareus.org>
|
||||
* Copyright (C) 2012 Damien Zammit <damien@zamaudio.com>
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#include <sound/pcm.h>
|
||||
#include "digi00x.h"
|
||||
|
||||
#define CIP_FMT_AM 0x10
|
||||
|
||||
/* 'Clock-based rate control mode' is just supported. */
|
||||
#define AMDTP_FDF_AM824 0x00
|
||||
|
||||
/*
|
||||
* Nominally 3125 bytes/second, but the MIDI port's clock might be
|
||||
* 1% too slow, and the bus clock 100 ppm too fast.
|
||||
*/
|
||||
#define MIDI_BYTES_PER_SECOND 3093
|
||||
|
||||
/*
|
||||
* Several devices look only at the first eight data blocks.
|
||||
* In any case, this is more than enough for the MIDI data rate.
|
||||
*/
|
||||
#define MAX_MIDI_RX_BLOCKS 8
|
||||
|
||||
/*
|
||||
* The double-oh-three algorithm was discovered by Robin Gareus and Damien
|
||||
* Zammit in 2012, with reverse-engineering for Digi 003 Rack.
|
||||
*/
|
||||
struct dot_state {
|
||||
u8 carry;
|
||||
u8 idx;
|
||||
unsigned int off;
|
||||
};
|
||||
|
||||
struct amdtp_dot {
|
||||
unsigned int pcm_channels;
|
||||
struct dot_state state;
|
||||
|
||||
unsigned int midi_ports;
|
||||
/* 2 = MAX(DOT_MIDI_IN_PORTS, DOT_MIDI_OUT_PORTS) */
|
||||
struct snd_rawmidi_substream *midi[2];
|
||||
int midi_fifo_used[2];
|
||||
int midi_fifo_limit;
|
||||
|
||||
void (*transfer_samples)(struct amdtp_stream *s,
|
||||
struct snd_pcm_substream *pcm,
|
||||
__be32 *buffer, unsigned int frames);
|
||||
};
|
||||
|
||||
/*
|
||||
* double-oh-three look up table
|
||||
*
|
||||
* @param idx index byte (audio-sample data) 0x00..0xff
|
||||
* @param off channel offset shift
|
||||
* @return salt to XOR with given data
|
||||
*/
|
||||
#define BYTE_PER_SAMPLE (4)
|
||||
#define MAGIC_DOT_BYTE (2)
|
||||
#define MAGIC_BYTE_OFF(x) (((x) * BYTE_PER_SAMPLE) + MAGIC_DOT_BYTE)
|
||||
static const u8 dot_scrt(const u8 idx, const unsigned int off)
|
||||
{
|
||||
/*
|
||||
* the length of the added pattern only depends on the lower nibble
|
||||
* of the last non-zero data
|
||||
*/
|
||||
static const u8 len[16] = {0, 1, 3, 5, 7, 9, 11, 13, 14,
|
||||
12, 10, 8, 6, 4, 2, 0};
|
||||
|
||||
/*
|
||||
* the lower nibble of the salt. Interleaved sequence.
|
||||
* this is walked backwards according to len[]
|
||||
*/
|
||||
static const u8 nib[15] = {0x8, 0x7, 0x9, 0x6, 0xa, 0x5, 0xb, 0x4,
|
||||
0xc, 0x3, 0xd, 0x2, 0xe, 0x1, 0xf};
|
||||
|
||||
/* circular list for the salt's hi nibble. */
|
||||
static const u8 hir[15] = {0x0, 0x6, 0xf, 0x8, 0x7, 0x5, 0x3, 0x4,
|
||||
0xc, 0xd, 0xe, 0x1, 0x2, 0xb, 0xa};
|
||||
|
||||
/*
|
||||
* start offset for upper nibble mapping.
|
||||
* note: 9 is /special/. In the case where the high nibble == 0x9,
|
||||
* hir[] is not used and - coincidentally - the salt's hi nibble is
|
||||
* 0x09 regardless of the offset.
|
||||
*/
|
||||
static const u8 hio[16] = {0, 11, 12, 6, 7, 5, 1, 4,
|
||||
3, 0x00, 14, 13, 8, 9, 10, 2};
|
||||
|
||||
const u8 ln = idx & 0xf;
|
||||
const u8 hn = (idx >> 4) & 0xf;
|
||||
const u8 hr = (hn == 0x9) ? 0x9 : hir[(hio[hn] + off) % 15];
|
||||
|
||||
if (len[ln] < off)
|
||||
return 0x00;
|
||||
|
||||
return ((nib[14 + off - len[ln]]) | (hr << 4));
|
||||
}
|
||||
|
||||
static void dot_encode_step(struct dot_state *state, __be32 *const buffer)
|
||||
{
|
||||
u8 * const data = (u8 *) buffer;
|
||||
|
||||
if (data[MAGIC_DOT_BYTE] != 0x00) {
|
||||
state->off = 0;
|
||||
state->idx = data[MAGIC_DOT_BYTE] ^ state->carry;
|
||||
}
|
||||
data[MAGIC_DOT_BYTE] ^= state->carry;
|
||||
state->carry = dot_scrt(state->idx, ++(state->off));
|
||||
}
|
||||
|
||||
int amdtp_dot_set_parameters(struct amdtp_stream *s, unsigned int rate,
|
||||
unsigned int pcm_channels)
|
||||
{
|
||||
struct amdtp_dot *p = s->protocol;
|
||||
int err;
|
||||
|
||||
if (amdtp_stream_running(s))
|
||||
return -EBUSY;
|
||||
|
||||
/*
|
||||
* A first data channel is for MIDI conformant data channel, the rest is
|
||||
* Multi Bit Linear Audio data channel.
|
||||
*/
|
||||
err = amdtp_stream_set_parameters(s, rate, pcm_channels + 1);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
s->fdf = AMDTP_FDF_AM824 | s->sfc;
|
||||
|
||||
p->pcm_channels = pcm_channels;
|
||||
|
||||
if (s->direction == AMDTP_IN_STREAM)
|
||||
p->midi_ports = DOT_MIDI_IN_PORTS;
|
||||
else
|
||||
p->midi_ports = DOT_MIDI_OUT_PORTS;
|
||||
|
||||
/*
|
||||
* We do not know the actual MIDI FIFO size of most devices. Just
|
||||
* assume two bytes, i.e., one byte can be received over the bus while
|
||||
* the previous one is transmitted over MIDI.
|
||||
* (The value here is adjusted for midi_ratelimit_per_packet().)
|
||||
*/
|
||||
p->midi_fifo_limit = rate - MIDI_BYTES_PER_SECOND * s->syt_interval + 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void write_pcm_s32(struct amdtp_stream *s, struct snd_pcm_substream *pcm,
|
||||
__be32 *buffer, unsigned int frames)
|
||||
{
|
||||
struct amdtp_dot *p = s->protocol;
|
||||
struct snd_pcm_runtime *runtime = pcm->runtime;
|
||||
unsigned int channels, remaining_frames, i, c;
|
||||
const u32 *src;
|
||||
|
||||
channels = p->pcm_channels;
|
||||
src = (void *)runtime->dma_area +
|
||||
frames_to_bytes(runtime, s->pcm_buffer_pointer);
|
||||
remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer;
|
||||
|
||||
buffer++;
|
||||
for (i = 0; i < frames; ++i) {
|
||||
for (c = 0; c < channels; ++c) {
|
||||
buffer[c] = cpu_to_be32((*src >> 8) | 0x40000000);
|
||||
dot_encode_step(&p->state, &buffer[c]);
|
||||
src++;
|
||||
}
|
||||
buffer += s->data_block_quadlets;
|
||||
if (--remaining_frames == 0)
|
||||
src = (void *)runtime->dma_area;
|
||||
}
|
||||
}
|
||||
|
||||
static void write_pcm_s16(struct amdtp_stream *s, struct snd_pcm_substream *pcm,
|
||||
__be32 *buffer, unsigned int frames)
|
||||
{
|
||||
struct amdtp_dot *p = s->protocol;
|
||||
struct snd_pcm_runtime *runtime = pcm->runtime;
|
||||
unsigned int channels, remaining_frames, i, c;
|
||||
const u16 *src;
|
||||
|
||||
channels = p->pcm_channels;
|
||||
src = (void *)runtime->dma_area +
|
||||
frames_to_bytes(runtime, s->pcm_buffer_pointer);
|
||||
remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer;
|
||||
|
||||
buffer++;
|
||||
for (i = 0; i < frames; ++i) {
|
||||
for (c = 0; c < channels; ++c) {
|
||||
buffer[c] = cpu_to_be32((*src << 8) | 0x40000000);
|
||||
dot_encode_step(&p->state, &buffer[c]);
|
||||
src++;
|
||||
}
|
||||
buffer += s->data_block_quadlets;
|
||||
if (--remaining_frames == 0)
|
||||
src = (void *)runtime->dma_area;
|
||||
}
|
||||
}
|
||||
|
||||
static void read_pcm_s32(struct amdtp_stream *s, struct snd_pcm_substream *pcm,
|
||||
__be32 *buffer, unsigned int frames)
|
||||
{
|
||||
struct amdtp_dot *p = s->protocol;
|
||||
struct snd_pcm_runtime *runtime = pcm->runtime;
|
||||
unsigned int channels, remaining_frames, i, c;
|
||||
u32 *dst;
|
||||
|
||||
channels = p->pcm_channels;
|
||||
dst = (void *)runtime->dma_area +
|
||||
frames_to_bytes(runtime, s->pcm_buffer_pointer);
|
||||
remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer;
|
||||
|
||||
buffer++;
|
||||
for (i = 0; i < frames; ++i) {
|
||||
for (c = 0; c < channels; ++c) {
|
||||
*dst = be32_to_cpu(buffer[c]) << 8;
|
||||
dst++;
|
||||
}
|
||||
buffer += s->data_block_quadlets;
|
||||
if (--remaining_frames == 0)
|
||||
dst = (void *)runtime->dma_area;
|
||||
}
|
||||
}
|
||||
|
||||
static void write_pcm_silence(struct amdtp_stream *s, __be32 *buffer,
|
||||
unsigned int data_blocks)
|
||||
{
|
||||
struct amdtp_dot *p = s->protocol;
|
||||
unsigned int channels, i, c;
|
||||
|
||||
channels = p->pcm_channels;
|
||||
|
||||
buffer++;
|
||||
for (i = 0; i < data_blocks; ++i) {
|
||||
for (c = 0; c < channels; ++c)
|
||||
buffer[c] = cpu_to_be32(0x40000000);
|
||||
buffer += s->data_block_quadlets;
|
||||
}
|
||||
}
|
||||
|
||||
static bool midi_ratelimit_per_packet(struct amdtp_stream *s, unsigned int port)
|
||||
{
|
||||
struct amdtp_dot *p = s->protocol;
|
||||
int used;
|
||||
|
||||
used = p->midi_fifo_used[port];
|
||||
if (used == 0)
|
||||
return true;
|
||||
|
||||
used -= MIDI_BYTES_PER_SECOND * s->syt_interval;
|
||||
used = max(used, 0);
|
||||
p->midi_fifo_used[port] = used;
|
||||
|
||||
return used < p->midi_fifo_limit;
|
||||
}
|
||||
|
||||
static inline void midi_use_bytes(struct amdtp_stream *s,
|
||||
unsigned int port, unsigned int count)
|
||||
{
|
||||
struct amdtp_dot *p = s->protocol;
|
||||
|
||||
p->midi_fifo_used[port] += amdtp_rate_table[s->sfc] * count;
|
||||
}
|
||||
|
||||
static void write_midi_messages(struct amdtp_stream *s, __be32 *buffer,
|
||||
unsigned int data_blocks)
|
||||
{
|
||||
struct amdtp_dot *p = s->protocol;
|
||||
unsigned int f, port;
|
||||
int len;
|
||||
u8 *b;
|
||||
|
||||
for (f = 0; f < data_blocks; f++) {
|
||||
port = (s->data_block_counter + f) % 8;
|
||||
b = (u8 *)&buffer[0];
|
||||
|
||||
len = 0;
|
||||
if (port < p->midi_ports &&
|
||||
midi_ratelimit_per_packet(s, port) &&
|
||||
p->midi[port] != NULL)
|
||||
len = snd_rawmidi_transmit(p->midi[port], b + 1, 2);
|
||||
|
||||
if (len > 0) {
|
||||
b[3] = (0x10 << port) | len;
|
||||
midi_use_bytes(s, port, len);
|
||||
} else {
|
||||
b[1] = 0;
|
||||
b[2] = 0;
|
||||
b[3] = 0;
|
||||
}
|
||||
b[0] = 0x80;
|
||||
|
||||
buffer += s->data_block_quadlets;
|
||||
}
|
||||
}
|
||||
|
||||
static void read_midi_messages(struct amdtp_stream *s, __be32 *buffer,
|
||||
unsigned int data_blocks)
|
||||
{
|
||||
struct amdtp_dot *p = s->protocol;
|
||||
unsigned int f, port, len;
|
||||
u8 *b;
|
||||
|
||||
for (f = 0; f < data_blocks; f++) {
|
||||
b = (u8 *)&buffer[0];
|
||||
port = b[3] >> 4;
|
||||
len = b[3] & 0x0f;
|
||||
|
||||
if (port < p->midi_ports && p->midi[port] && len > 0)
|
||||
snd_rawmidi_receive(p->midi[port], b + 1, len);
|
||||
|
||||
buffer += s->data_block_quadlets;
|
||||
}
|
||||
}
|
||||
|
||||
int amdtp_dot_add_pcm_hw_constraints(struct amdtp_stream *s,
|
||||
struct snd_pcm_runtime *runtime)
|
||||
{
|
||||
int err;
|
||||
|
||||
/* This protocol delivers 24 bit data in 32bit data channel. */
|
||||
err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return amdtp_stream_add_pcm_hw_constraints(s, runtime);
|
||||
}
|
||||
|
||||
void amdtp_dot_set_pcm_format(struct amdtp_stream *s, snd_pcm_format_t format)
|
||||
{
|
||||
struct amdtp_dot *p = s->protocol;
|
||||
|
||||
if (WARN_ON(amdtp_stream_pcm_running(s)))
|
||||
return;
|
||||
|
||||
switch (format) {
|
||||
default:
|
||||
WARN_ON(1);
|
||||
/* fall through */
|
||||
case SNDRV_PCM_FORMAT_S16:
|
||||
if (s->direction == AMDTP_OUT_STREAM) {
|
||||
p->transfer_samples = write_pcm_s16;
|
||||
break;
|
||||
}
|
||||
WARN_ON(1);
|
||||
/* fall through */
|
||||
case SNDRV_PCM_FORMAT_S32:
|
||||
if (s->direction == AMDTP_OUT_STREAM)
|
||||
p->transfer_samples = write_pcm_s32;
|
||||
else
|
||||
p->transfer_samples = read_pcm_s32;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void amdtp_dot_midi_trigger(struct amdtp_stream *s, unsigned int port,
|
||||
struct snd_rawmidi_substream *midi)
|
||||
{
|
||||
struct amdtp_dot *p = s->protocol;
|
||||
|
||||
if (port < p->midi_ports)
|
||||
ACCESS_ONCE(p->midi[port]) = midi;
|
||||
}
|
||||
|
||||
static unsigned int process_tx_data_blocks(struct amdtp_stream *s,
|
||||
__be32 *buffer,
|
||||
unsigned int data_blocks,
|
||||
unsigned int *syt)
|
||||
{
|
||||
struct amdtp_dot *p = (struct amdtp_dot *)s->protocol;
|
||||
struct snd_pcm_substream *pcm;
|
||||
unsigned int pcm_frames;
|
||||
|
||||
pcm = ACCESS_ONCE(s->pcm);
|
||||
if (pcm) {
|
||||
p->transfer_samples(s, pcm, buffer, data_blocks);
|
||||
pcm_frames = data_blocks;
|
||||
} else {
|
||||
pcm_frames = 0;
|
||||
}
|
||||
|
||||
read_midi_messages(s, buffer, data_blocks);
|
||||
|
||||
return pcm_frames;
|
||||
}
|
||||
|
||||
static unsigned int process_rx_data_blocks(struct amdtp_stream *s,
|
||||
__be32 *buffer,
|
||||
unsigned int data_blocks,
|
||||
unsigned int *syt)
|
||||
{
|
||||
struct amdtp_dot *p = (struct amdtp_dot *)s->protocol;
|
||||
struct snd_pcm_substream *pcm;
|
||||
unsigned int pcm_frames;
|
||||
|
||||
pcm = ACCESS_ONCE(s->pcm);
|
||||
if (pcm) {
|
||||
p->transfer_samples(s, pcm, buffer, data_blocks);
|
||||
pcm_frames = data_blocks;
|
||||
} else {
|
||||
write_pcm_silence(s, buffer, data_blocks);
|
||||
pcm_frames = 0;
|
||||
}
|
||||
|
||||
write_midi_messages(s, buffer, data_blocks);
|
||||
|
||||
return pcm_frames;
|
||||
}
|
||||
|
||||
int amdtp_dot_init(struct amdtp_stream *s, struct fw_unit *unit,
|
||||
enum amdtp_stream_direction dir)
|
||||
{
|
||||
amdtp_stream_process_data_blocks_t process_data_blocks;
|
||||
enum cip_flags flags;
|
||||
|
||||
/* Use different mode between incoming/outgoing. */
|
||||
if (dir == AMDTP_IN_STREAM) {
|
||||
flags = CIP_NONBLOCKING | CIP_SKIP_INIT_DBC_CHECK;
|
||||
process_data_blocks = process_tx_data_blocks;
|
||||
} else {
|
||||
flags = CIP_BLOCKING;
|
||||
process_data_blocks = process_rx_data_blocks;
|
||||
}
|
||||
|
||||
return amdtp_stream_init(s, unit, dir, flags, CIP_FMT_AM,
|
||||
process_data_blocks, sizeof(struct amdtp_dot));
|
||||
}
|
||||
|
||||
void amdtp_dot_reset(struct amdtp_stream *s)
|
||||
{
|
||||
struct amdtp_dot *p = s->protocol;
|
||||
|
||||
p->state.carry = 0x00;
|
||||
p->state.idx = 0x00;
|
||||
p->state.off = 0;
|
||||
}
|
|
@ -0,0 +1,200 @@
|
|||
/*
|
||||
* digi00x-hwdep.c - a part of driver for Digidesign Digi 002/003 family
|
||||
*
|
||||
* Copyright (c) 2014-2015 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
|
||||
* 4.get asynchronous messaging
|
||||
*/
|
||||
|
||||
#include "digi00x.h"
|
||||
|
||||
static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
|
||||
loff_t *offset)
|
||||
{
|
||||
struct snd_dg00x *dg00x = hwdep->private_data;
|
||||
DEFINE_WAIT(wait);
|
||||
union snd_firewire_event event;
|
||||
|
||||
spin_lock_irq(&dg00x->lock);
|
||||
|
||||
while (!dg00x->dev_lock_changed && dg00x->msg == 0) {
|
||||
prepare_to_wait(&dg00x->hwdep_wait, &wait, TASK_INTERRUPTIBLE);
|
||||
spin_unlock_irq(&dg00x->lock);
|
||||
schedule();
|
||||
finish_wait(&dg00x->hwdep_wait, &wait);
|
||||
if (signal_pending(current))
|
||||
return -ERESTARTSYS;
|
||||
spin_lock_irq(&dg00x->lock);
|
||||
}
|
||||
|
||||
memset(&event, 0, sizeof(event));
|
||||
if (dg00x->dev_lock_changed) {
|
||||
event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS;
|
||||
event.lock_status.status = (dg00x->dev_lock_count > 0);
|
||||
dg00x->dev_lock_changed = false;
|
||||
|
||||
count = min_t(long, count, sizeof(event.lock_status));
|
||||
} else {
|
||||
event.digi00x_message.type =
|
||||
SNDRV_FIREWIRE_EVENT_DIGI00X_MESSAGE;
|
||||
event.digi00x_message.message = dg00x->msg;
|
||||
dg00x->msg = 0;
|
||||
|
||||
count = min_t(long, count, sizeof(event.digi00x_message));
|
||||
}
|
||||
|
||||
spin_unlock_irq(&dg00x->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_dg00x *dg00x = hwdep->private_data;
|
||||
unsigned int events;
|
||||
|
||||
poll_wait(file, &dg00x->hwdep_wait, wait);
|
||||
|
||||
spin_lock_irq(&dg00x->lock);
|
||||
if (dg00x->dev_lock_changed || dg00x->msg)
|
||||
events = POLLIN | POLLRDNORM;
|
||||
else
|
||||
events = 0;
|
||||
spin_unlock_irq(&dg00x->lock);
|
||||
|
||||
return events;
|
||||
}
|
||||
|
||||
static int hwdep_get_info(struct snd_dg00x *dg00x, void __user *arg)
|
||||
{
|
||||
struct fw_device *dev = fw_parent_device(dg00x->unit);
|
||||
struct snd_firewire_get_info info;
|
||||
|
||||
memset(&info, 0, sizeof(info));
|
||||
info.type = SNDRV_FIREWIRE_TYPE_DIGI00X;
|
||||
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_dg00x *dg00x)
|
||||
{
|
||||
int err;
|
||||
|
||||
spin_lock_irq(&dg00x->lock);
|
||||
|
||||
if (dg00x->dev_lock_count == 0) {
|
||||
dg00x->dev_lock_count = -1;
|
||||
err = 0;
|
||||
} else {
|
||||
err = -EBUSY;
|
||||
}
|
||||
|
||||
spin_unlock_irq(&dg00x->lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int hwdep_unlock(struct snd_dg00x *dg00x)
|
||||
{
|
||||
int err;
|
||||
|
||||
spin_lock_irq(&dg00x->lock);
|
||||
|
||||
if (dg00x->dev_lock_count == -1) {
|
||||
dg00x->dev_lock_count = 0;
|
||||
err = 0;
|
||||
} else {
|
||||
err = -EBADFD;
|
||||
}
|
||||
|
||||
spin_unlock_irq(&dg00x->lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int hwdep_release(struct snd_hwdep *hwdep, struct file *file)
|
||||
{
|
||||
struct snd_dg00x *dg00x = hwdep->private_data;
|
||||
|
||||
spin_lock_irq(&dg00x->lock);
|
||||
if (dg00x->dev_lock_count == -1)
|
||||
dg00x->dev_lock_count = 0;
|
||||
spin_unlock_irq(&dg00x->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct snd_dg00x *dg00x = hwdep->private_data;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_FIREWIRE_IOCTL_GET_INFO:
|
||||
return hwdep_get_info(dg00x, (void __user *)arg);
|
||||
case SNDRV_FIREWIRE_IOCTL_LOCK:
|
||||
return hwdep_lock(dg00x);
|
||||
case SNDRV_FIREWIRE_IOCTL_UNLOCK:
|
||||
return hwdep_unlock(dg00x);
|
||||
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
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
int snd_dg00x_create_hwdep_device(struct snd_dg00x *dg00x)
|
||||
{
|
||||
struct snd_hwdep *hwdep;
|
||||
int err;
|
||||
|
||||
err = snd_hwdep_new(dg00x->card, "Digi00x", 0, &hwdep);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
strcpy(hwdep->name, "Digi00x");
|
||||
hwdep->iface = SNDRV_HWDEP_IFACE_FW_DIGI00X;
|
||||
hwdep->ops = hwdep_ops;
|
||||
hwdep->private_data = dg00x;
|
||||
hwdep->exclusive = true;
|
||||
|
||||
return err;
|
||||
}
|
|
@ -0,0 +1,223 @@
|
|||
/*
|
||||
* digi00x-midi.h - a part of driver for Digidesign Digi 002/003 family
|
||||
*
|
||||
* Copyright (c) 2014-2015 Takashi Sakamoto
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#include "digi00x.h"
|
||||
|
||||
static int midi_phys_open(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
struct snd_dg00x *dg00x = substream->rmidi->private_data;
|
||||
int err;
|
||||
|
||||
err = snd_dg00x_stream_lock_try(dg00x);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
mutex_lock(&dg00x->mutex);
|
||||
dg00x->substreams_counter++;
|
||||
err = snd_dg00x_stream_start_duplex(dg00x, 0);
|
||||
mutex_unlock(&dg00x->mutex);
|
||||
if (err < 0)
|
||||
snd_dg00x_stream_lock_release(dg00x);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int midi_phys_close(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
struct snd_dg00x *dg00x = substream->rmidi->private_data;
|
||||
|
||||
mutex_lock(&dg00x->mutex);
|
||||
dg00x->substreams_counter--;
|
||||
snd_dg00x_stream_stop_duplex(dg00x);
|
||||
mutex_unlock(&dg00x->mutex);
|
||||
|
||||
snd_dg00x_stream_lock_release(dg00x);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void midi_phys_capture_trigger(struct snd_rawmidi_substream *substream,
|
||||
int up)
|
||||
{
|
||||
struct snd_dg00x *dg00x = substream->rmidi->private_data;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&dg00x->lock, flags);
|
||||
|
||||
if (up)
|
||||
amdtp_dot_midi_trigger(&dg00x->tx_stream, substream->number,
|
||||
substream);
|
||||
else
|
||||
amdtp_dot_midi_trigger(&dg00x->tx_stream, substream->number,
|
||||
NULL);
|
||||
|
||||
spin_unlock_irqrestore(&dg00x->lock, flags);
|
||||
}
|
||||
|
||||
static void midi_phys_playback_trigger(struct snd_rawmidi_substream *substream,
|
||||
int up)
|
||||
{
|
||||
struct snd_dg00x *dg00x = substream->rmidi->private_data;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&dg00x->lock, flags);
|
||||
|
||||
if (up)
|
||||
amdtp_dot_midi_trigger(&dg00x->rx_stream, substream->number,
|
||||
substream);
|
||||
else
|
||||
amdtp_dot_midi_trigger(&dg00x->rx_stream, substream->number,
|
||||
NULL);
|
||||
|
||||
spin_unlock_irqrestore(&dg00x->lock, flags);
|
||||
}
|
||||
|
||||
static struct snd_rawmidi_ops midi_phys_capture_ops = {
|
||||
.open = midi_phys_open,
|
||||
.close = midi_phys_close,
|
||||
.trigger = midi_phys_capture_trigger,
|
||||
};
|
||||
|
||||
static struct snd_rawmidi_ops midi_phys_playback_ops = {
|
||||
.open = midi_phys_open,
|
||||
.close = midi_phys_close,
|
||||
.trigger = midi_phys_playback_trigger,
|
||||
};
|
||||
|
||||
static int midi_ctl_open(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
/* Do nothing. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int midi_ctl_capture_close(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
/* Do nothing. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int midi_ctl_playback_close(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
struct snd_dg00x *dg00x = substream->rmidi->private_data;
|
||||
|
||||
snd_fw_async_midi_port_finish(&dg00x->out_control);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void midi_ctl_capture_trigger(struct snd_rawmidi_substream *substream,
|
||||
int up)
|
||||
{
|
||||
struct snd_dg00x *dg00x = substream->rmidi->private_data;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&dg00x->lock, flags);
|
||||
|
||||
if (up)
|
||||
dg00x->in_control = substream;
|
||||
else
|
||||
dg00x->in_control = NULL;
|
||||
|
||||
spin_unlock_irqrestore(&dg00x->lock, flags);
|
||||
}
|
||||
|
||||
static void midi_ctl_playback_trigger(struct snd_rawmidi_substream *substream,
|
||||
int up)
|
||||
{
|
||||
struct snd_dg00x *dg00x = substream->rmidi->private_data;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&dg00x->lock, flags);
|
||||
|
||||
if (up)
|
||||
snd_fw_async_midi_port_run(&dg00x->out_control, substream);
|
||||
|
||||
spin_unlock_irqrestore(&dg00x->lock, flags);
|
||||
}
|
||||
|
||||
static struct snd_rawmidi_ops midi_ctl_capture_ops = {
|
||||
.open = midi_ctl_open,
|
||||
.close = midi_ctl_capture_close,
|
||||
.trigger = midi_ctl_capture_trigger,
|
||||
};
|
||||
|
||||
static struct snd_rawmidi_ops midi_ctl_playback_ops = {
|
||||
.open = midi_ctl_open,
|
||||
.close = midi_ctl_playback_close,
|
||||
.trigger = midi_ctl_playback_trigger,
|
||||
};
|
||||
|
||||
static void set_midi_substream_names(struct snd_dg00x *dg00x,
|
||||
struct snd_rawmidi_str *str,
|
||||
bool is_ctl)
|
||||
{
|
||||
struct snd_rawmidi_substream *subs;
|
||||
|
||||
list_for_each_entry(subs, &str->substreams, list) {
|
||||
if (!is_ctl)
|
||||
snprintf(subs->name, sizeof(subs->name),
|
||||
"%s MIDI %d",
|
||||
dg00x->card->shortname, subs->number + 1);
|
||||
else
|
||||
/* This port is for asynchronous transaction. */
|
||||
snprintf(subs->name, sizeof(subs->name),
|
||||
"%s control",
|
||||
dg00x->card->shortname);
|
||||
}
|
||||
}
|
||||
|
||||
int snd_dg00x_create_midi_devices(struct snd_dg00x *dg00x)
|
||||
{
|
||||
struct snd_rawmidi *rmidi[2];
|
||||
struct snd_rawmidi_str *str;
|
||||
unsigned int i;
|
||||
int err;
|
||||
|
||||
/* Add physical midi ports. */
|
||||
err = snd_rawmidi_new(dg00x->card, dg00x->card->driver, 0,
|
||||
DOT_MIDI_OUT_PORTS, DOT_MIDI_IN_PORTS, &rmidi[0]);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
snprintf(rmidi[0]->name, sizeof(rmidi[0]->name),
|
||||
"%s MIDI", dg00x->card->shortname);
|
||||
|
||||
snd_rawmidi_set_ops(rmidi[0], SNDRV_RAWMIDI_STREAM_INPUT,
|
||||
&midi_phys_capture_ops);
|
||||
snd_rawmidi_set_ops(rmidi[0], SNDRV_RAWMIDI_STREAM_OUTPUT,
|
||||
&midi_phys_playback_ops);
|
||||
|
||||
/* Add a pair of control midi ports. */
|
||||
err = snd_rawmidi_new(dg00x->card, dg00x->card->driver, 1,
|
||||
1, 1, &rmidi[1]);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
snprintf(rmidi[1]->name, sizeof(rmidi[1]->name),
|
||||
"%s control", dg00x->card->shortname);
|
||||
|
||||
snd_rawmidi_set_ops(rmidi[1], SNDRV_RAWMIDI_STREAM_INPUT,
|
||||
&midi_ctl_capture_ops);
|
||||
snd_rawmidi_set_ops(rmidi[1], SNDRV_RAWMIDI_STREAM_OUTPUT,
|
||||
&midi_ctl_playback_ops);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(rmidi); i++) {
|
||||
rmidi[i]->private_data = dg00x;
|
||||
|
||||
rmidi[i]->info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
|
||||
str = &rmidi[i]->streams[SNDRV_RAWMIDI_STREAM_INPUT];
|
||||
set_midi_substream_names(dg00x, str, i);
|
||||
|
||||
rmidi[i]->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;
|
||||
str = &rmidi[i]->streams[SNDRV_RAWMIDI_STREAM_OUTPUT];
|
||||
set_midi_substream_names(dg00x, str, i);
|
||||
|
||||
rmidi[i]->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,373 @@
|
|||
/*
|
||||
* digi00x-pcm.c - a part of driver for Digidesign Digi 002/003 family
|
||||
*
|
||||
* Copyright (c) 2014-2015 Takashi Sakamoto
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#include "digi00x.h"
|
||||
|
||||
static int hw_rule_rate(struct snd_pcm_hw_params *params,
|
||||
struct snd_pcm_hw_rule *rule)
|
||||
{
|
||||
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,
|
||||
};
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < SND_DG00X_RATE_COUNT; i++) {
|
||||
if (!snd_interval_test(c,
|
||||
snd_dg00x_stream_pcm_channels[i]))
|
||||
continue;
|
||||
|
||||
t.min = min(t.min, snd_dg00x_stream_rates[i]);
|
||||
t.max = max(t.max, snd_dg00x_stream_rates[i]);
|
||||
}
|
||||
|
||||
return snd_interval_refine(r, &t);
|
||||
}
|
||||
|
||||
static int hw_rule_channels(struct snd_pcm_hw_params *params,
|
||||
struct snd_pcm_hw_rule *rule)
|
||||
{
|
||||
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_interval t = {
|
||||
.min = UINT_MAX, .max = 0, .integer = 1,
|
||||
};
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < SND_DG00X_RATE_COUNT; i++) {
|
||||
if (!snd_interval_test(r, snd_dg00x_stream_rates[i]))
|
||||
continue;
|
||||
|
||||
t.min = min(t.min, snd_dg00x_stream_pcm_channels[i]);
|
||||
t.max = max(t.max, snd_dg00x_stream_pcm_channels[i]);
|
||||
}
|
||||
|
||||
return snd_interval_refine(c, &t);
|
||||
}
|
||||
|
||||
static int pcm_init_hw_params(struct snd_dg00x *dg00x,
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
static const struct snd_pcm_hardware hardware = {
|
||||
.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,
|
||||
.rates = SNDRV_PCM_RATE_44100 |
|
||||
SNDRV_PCM_RATE_48000 |
|
||||
SNDRV_PCM_RATE_88200 |
|
||||
SNDRV_PCM_RATE_96000,
|
||||
.rate_min = 44100,
|
||||
.rate_max = 96000,
|
||||
.channels_min = 10,
|
||||
.channels_max = 18,
|
||||
.period_bytes_min = 4 * 18,
|
||||
.period_bytes_max = 4 * 18 * 2048,
|
||||
.buffer_bytes_max = 4 * 18 * 2048 * 2,
|
||||
.periods_min = 2,
|
||||
.periods_max = UINT_MAX,
|
||||
};
|
||||
struct amdtp_stream *s;
|
||||
int err;
|
||||
|
||||
substream->runtime->hw = hardware;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
|
||||
substream->runtime->hw.formats = SNDRV_PCM_FMTBIT_S32;
|
||||
s = &dg00x->tx_stream;
|
||||
} else {
|
||||
substream->runtime->hw.formats = SNDRV_PCM_FMTBIT_S16 |
|
||||
SNDRV_PCM_FMTBIT_S32;
|
||||
s = &dg00x->rx_stream;
|
||||
}
|
||||
|
||||
err = snd_pcm_hw_rule_add(substream->runtime, 0,
|
||||
SNDRV_PCM_HW_PARAM_CHANNELS,
|
||||
hw_rule_channels, NULL,
|
||||
SNDRV_PCM_HW_PARAM_RATE, -1);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = snd_pcm_hw_rule_add(substream->runtime, 0,
|
||||
SNDRV_PCM_HW_PARAM_RATE,
|
||||
hw_rule_rate, NULL,
|
||||
SNDRV_PCM_HW_PARAM_CHANNELS, -1);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return amdtp_dot_add_pcm_hw_constraints(s, substream->runtime);
|
||||
}
|
||||
|
||||
static int pcm_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_dg00x *dg00x = substream->private_data;
|
||||
enum snd_dg00x_clock clock;
|
||||
bool detect;
|
||||
unsigned int rate;
|
||||
int err;
|
||||
|
||||
err = snd_dg00x_stream_lock_try(dg00x);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
err = pcm_init_hw_params(dg00x, substream);
|
||||
if (err < 0)
|
||||
goto err_locked;
|
||||
|
||||
/* Check current clock source. */
|
||||
err = snd_dg00x_stream_get_clock(dg00x, &clock);
|
||||
if (err < 0)
|
||||
goto err_locked;
|
||||
if (clock != SND_DG00X_CLOCK_INTERNAL) {
|
||||
err = snd_dg00x_stream_check_external_clock(dg00x, &detect);
|
||||
if (err < 0)
|
||||
goto err_locked;
|
||||
if (!detect) {
|
||||
err = -EBUSY;
|
||||
goto err_locked;
|
||||
}
|
||||
}
|
||||
|
||||
if ((clock != SND_DG00X_CLOCK_INTERNAL) ||
|
||||
amdtp_stream_pcm_running(&dg00x->rx_stream) ||
|
||||
amdtp_stream_pcm_running(&dg00x->tx_stream)) {
|
||||
err = snd_dg00x_stream_get_external_rate(dg00x, &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_dg00x_stream_lock_release(dg00x);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int pcm_close(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_dg00x *dg00x = substream->private_data;
|
||||
|
||||
snd_dg00x_stream_lock_release(dg00x);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pcm_capture_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *hw_params)
|
||||
{
|
||||
struct snd_dg00x *dg00x = substream->private_data;
|
||||
int err;
|
||||
|
||||
err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
|
||||
params_buffer_bytes(hw_params));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
|
||||
mutex_lock(&dg00x->mutex);
|
||||
dg00x->substreams_counter++;
|
||||
mutex_unlock(&dg00x->mutex);
|
||||
}
|
||||
|
||||
amdtp_dot_set_pcm_format(&dg00x->tx_stream, params_format(hw_params));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pcm_playback_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *hw_params)
|
||||
{
|
||||
struct snd_dg00x *dg00x = substream->private_data;
|
||||
int err;
|
||||
|
||||
err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
|
||||
params_buffer_bytes(hw_params));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
|
||||
mutex_lock(&dg00x->mutex);
|
||||
dg00x->substreams_counter++;
|
||||
mutex_unlock(&dg00x->mutex);
|
||||
}
|
||||
|
||||
amdtp_dot_set_pcm_format(&dg00x->rx_stream, params_format(hw_params));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pcm_capture_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_dg00x *dg00x = substream->private_data;
|
||||
|
||||
mutex_lock(&dg00x->mutex);
|
||||
|
||||
if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
|
||||
dg00x->substreams_counter--;
|
||||
|
||||
snd_dg00x_stream_stop_duplex(dg00x);
|
||||
|
||||
mutex_unlock(&dg00x->mutex);
|
||||
|
||||
return snd_pcm_lib_free_vmalloc_buffer(substream);
|
||||
}
|
||||
|
||||
static int pcm_playback_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_dg00x *dg00x = substream->private_data;
|
||||
|
||||
mutex_lock(&dg00x->mutex);
|
||||
|
||||
if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
|
||||
dg00x->substreams_counter--;
|
||||
|
||||
snd_dg00x_stream_stop_duplex(dg00x);
|
||||
|
||||
mutex_unlock(&dg00x->mutex);
|
||||
|
||||
return snd_pcm_lib_free_vmalloc_buffer(substream);
|
||||
}
|
||||
|
||||
static int pcm_capture_prepare(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_dg00x *dg00x = substream->private_data;
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
int err;
|
||||
|
||||
mutex_lock(&dg00x->mutex);
|
||||
|
||||
err = snd_dg00x_stream_start_duplex(dg00x, runtime->rate);
|
||||
if (err >= 0)
|
||||
amdtp_stream_pcm_prepare(&dg00x->tx_stream);
|
||||
|
||||
mutex_unlock(&dg00x->mutex);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int pcm_playback_prepare(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_dg00x *dg00x = substream->private_data;
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
int err;
|
||||
|
||||
mutex_lock(&dg00x->mutex);
|
||||
|
||||
err = snd_dg00x_stream_start_duplex(dg00x, runtime->rate);
|
||||
if (err >= 0) {
|
||||
amdtp_stream_pcm_prepare(&dg00x->rx_stream);
|
||||
amdtp_dot_reset(&dg00x->rx_stream);
|
||||
}
|
||||
|
||||
mutex_unlock(&dg00x->mutex);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd)
|
||||
{
|
||||
struct snd_dg00x *dg00x = substream->private_data;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
amdtp_stream_pcm_trigger(&dg00x->tx_stream, substream);
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
amdtp_stream_pcm_trigger(&dg00x->tx_stream, NULL);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd)
|
||||
{
|
||||
struct snd_dg00x *dg00x = substream->private_data;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
amdtp_stream_pcm_trigger(&dg00x->rx_stream, substream);
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
amdtp_stream_pcm_trigger(&dg00x->rx_stream, NULL);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstrm)
|
||||
{
|
||||
struct snd_dg00x *dg00x = sbstrm->private_data;
|
||||
|
||||
return amdtp_stream_pcm_pointer(&dg00x->tx_stream);
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstrm)
|
||||
{
|
||||
struct snd_dg00x *dg00x = sbstrm->private_data;
|
||||
|
||||
return amdtp_stream_pcm_pointer(&dg00x->rx_stream);
|
||||
}
|
||||
|
||||
static struct snd_pcm_ops pcm_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,
|
||||
};
|
||||
|
||||
static struct snd_pcm_ops pcm_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,
|
||||
};
|
||||
|
||||
int snd_dg00x_create_pcm_devices(struct snd_dg00x *dg00x)
|
||||
{
|
||||
struct snd_pcm *pcm;
|
||||
int err;
|
||||
|
||||
err = snd_pcm_new(dg00x->card, dg00x->card->driver, 0, 1, 1, &pcm);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
pcm->private_data = dg00x;
|
||||
snprintf(pcm->name, sizeof(pcm->name),
|
||||
"%s PCM", dg00x->card->shortname);
|
||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_playback_ops);
|
||||
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pcm_capture_ops);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* digi00x-proc.c - a part of driver for Digidesign Digi 002/003 family
|
||||
*
|
||||
* Copyright (c) 2014-2015 Takashi Sakamoto
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#include "digi00x.h"
|
||||
|
||||
static int get_optical_iface_mode(struct snd_dg00x *dg00x,
|
||||
enum snd_dg00x_optical_mode *mode)
|
||||
{
|
||||
__be32 data;
|
||||
int err;
|
||||
|
||||
err = snd_fw_transaction(dg00x->unit, TCODE_READ_QUADLET_REQUEST,
|
||||
DG00X_ADDR_BASE + DG00X_OFFSET_OPT_IFACE_MODE,
|
||||
&data, sizeof(data), 0);
|
||||
if (err >= 0)
|
||||
*mode = be32_to_cpu(data) & 0x01;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void proc_read_clock(struct snd_info_entry *entry,
|
||||
struct snd_info_buffer *buf)
|
||||
{
|
||||
static const char *const source_name[] = {
|
||||
[SND_DG00X_CLOCK_INTERNAL] = "internal",
|
||||
[SND_DG00X_CLOCK_SPDIF] = "s/pdif",
|
||||
[SND_DG00X_CLOCK_ADAT] = "adat",
|
||||
[SND_DG00X_CLOCK_WORD] = "word clock",
|
||||
};
|
||||
static const char *const optical_name[] = {
|
||||
[SND_DG00X_OPT_IFACE_MODE_ADAT] = "adat",
|
||||
[SND_DG00X_OPT_IFACE_MODE_SPDIF] = "s/pdif",
|
||||
};
|
||||
struct snd_dg00x *dg00x = entry->private_data;
|
||||
enum snd_dg00x_optical_mode mode;
|
||||
unsigned int rate;
|
||||
enum snd_dg00x_clock clock;
|
||||
bool detect;
|
||||
|
||||
if (get_optical_iface_mode(dg00x, &mode) < 0)
|
||||
return;
|
||||
if (snd_dg00x_stream_get_local_rate(dg00x, &rate) < 0)
|
||||
return;
|
||||
if (snd_dg00x_stream_get_clock(dg00x, &clock) < 0)
|
||||
return;
|
||||
|
||||
snd_iprintf(buf, "Optical mode: %s\n", optical_name[mode]);
|
||||
snd_iprintf(buf, "Sampling Rate: %d\n", rate);
|
||||
snd_iprintf(buf, "Clock Source: %s\n", source_name[clock]);
|
||||
|
||||
if (clock == SND_DG00X_CLOCK_INTERNAL)
|
||||
return;
|
||||
|
||||
if (snd_dg00x_stream_check_external_clock(dg00x, &detect) < 0)
|
||||
return;
|
||||
snd_iprintf(buf, "External source: %s\n", detect ? "detected" : "not");
|
||||
if (!detect)
|
||||
return;
|
||||
|
||||
if (snd_dg00x_stream_get_external_rate(dg00x, &rate) >= 0)
|
||||
snd_iprintf(buf, "External sampling rate: %d\n", rate);
|
||||
}
|
||||
|
||||
void snd_dg00x_proc_init(struct snd_dg00x *dg00x)
|
||||
{
|
||||
struct snd_info_entry *root, *entry;
|
||||
|
||||
/*
|
||||
* All nodes are automatically removed at snd_card_disconnect(),
|
||||
* by following to link list.
|
||||
*/
|
||||
root = snd_info_create_card_entry(dg00x->card, "firewire",
|
||||
dg00x->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;
|
||||
}
|
||||
|
||||
entry = snd_info_create_card_entry(dg00x->card, "clock", root);
|
||||
if (entry == NULL) {
|
||||
snd_info_free_entry(root);
|
||||
return;
|
||||
}
|
||||
|
||||
snd_info_set_text_ops(entry, dg00x, proc_read_clock);
|
||||
if (snd_info_register(entry) < 0) {
|
||||
snd_info_free_entry(entry);
|
||||
snd_info_free_entry(root);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,422 @@
|
|||
/*
|
||||
* digi00x-stream.c - a part of driver for Digidesign Digi 002/003 family
|
||||
*
|
||||
* Copyright (c) 2014-2015 Takashi Sakamoto
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#include "digi00x.h"
|
||||
|
||||
#define CALLBACK_TIMEOUT 500
|
||||
|
||||
const unsigned int snd_dg00x_stream_rates[SND_DG00X_RATE_COUNT] = {
|
||||
[SND_DG00X_RATE_44100] = 44100,
|
||||
[SND_DG00X_RATE_48000] = 48000,
|
||||
[SND_DG00X_RATE_88200] = 88200,
|
||||
[SND_DG00X_RATE_96000] = 96000,
|
||||
};
|
||||
|
||||
/* Multi Bit Linear Audio data channels for each sampling transfer frequency. */
|
||||
const unsigned int
|
||||
snd_dg00x_stream_pcm_channels[SND_DG00X_RATE_COUNT] = {
|
||||
/* Analog/ADAT/SPDIF */
|
||||
[SND_DG00X_RATE_44100] = (8 + 8 + 2),
|
||||
[SND_DG00X_RATE_48000] = (8 + 8 + 2),
|
||||
/* Analog/SPDIF */
|
||||
[SND_DG00X_RATE_88200] = (8 + 2),
|
||||
[SND_DG00X_RATE_96000] = (8 + 2),
|
||||
};
|
||||
|
||||
int snd_dg00x_stream_get_local_rate(struct snd_dg00x *dg00x, unsigned int *rate)
|
||||
{
|
||||
u32 data;
|
||||
__be32 reg;
|
||||
int err;
|
||||
|
||||
err = snd_fw_transaction(dg00x->unit, TCODE_READ_QUADLET_REQUEST,
|
||||
DG00X_ADDR_BASE + DG00X_OFFSET_LOCAL_RATE,
|
||||
®, sizeof(reg), 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
data = be32_to_cpu(reg) & 0x0f;
|
||||
if (data < ARRAY_SIZE(snd_dg00x_stream_rates))
|
||||
*rate = snd_dg00x_stream_rates[data];
|
||||
else
|
||||
err = -EIO;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int snd_dg00x_stream_set_local_rate(struct snd_dg00x *dg00x, unsigned int rate)
|
||||
{
|
||||
__be32 reg;
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(snd_dg00x_stream_rates); i++) {
|
||||
if (rate == snd_dg00x_stream_rates[i])
|
||||
break;
|
||||
}
|
||||
if (i == ARRAY_SIZE(snd_dg00x_stream_rates))
|
||||
return -EINVAL;
|
||||
|
||||
reg = cpu_to_be32(i);
|
||||
return snd_fw_transaction(dg00x->unit, TCODE_WRITE_QUADLET_REQUEST,
|
||||
DG00X_ADDR_BASE + DG00X_OFFSET_LOCAL_RATE,
|
||||
®, sizeof(reg), 0);
|
||||
}
|
||||
|
||||
int snd_dg00x_stream_get_clock(struct snd_dg00x *dg00x,
|
||||
enum snd_dg00x_clock *clock)
|
||||
{
|
||||
__be32 reg;
|
||||
int err;
|
||||
|
||||
err = snd_fw_transaction(dg00x->unit, TCODE_READ_QUADLET_REQUEST,
|
||||
DG00X_ADDR_BASE + DG00X_OFFSET_CLOCK_SOURCE,
|
||||
®, sizeof(reg), 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
*clock = be32_to_cpu(reg) & 0x0f;
|
||||
if (*clock >= SND_DG00X_CLOCK_COUNT)
|
||||
err = -EIO;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int snd_dg00x_stream_check_external_clock(struct snd_dg00x *dg00x, bool *detect)
|
||||
{
|
||||
__be32 reg;
|
||||
int err;
|
||||
|
||||
err = snd_fw_transaction(dg00x->unit, TCODE_READ_QUADLET_REQUEST,
|
||||
DG00X_ADDR_BASE + DG00X_OFFSET_DETECT_EXTERNAL,
|
||||
®, sizeof(reg), 0);
|
||||
if (err >= 0)
|
||||
*detect = be32_to_cpu(reg) > 0;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int snd_dg00x_stream_get_external_rate(struct snd_dg00x *dg00x,
|
||||
unsigned int *rate)
|
||||
{
|
||||
u32 data;
|
||||
__be32 reg;
|
||||
int err;
|
||||
|
||||
err = snd_fw_transaction(dg00x->unit, TCODE_READ_QUADLET_REQUEST,
|
||||
DG00X_ADDR_BASE + DG00X_OFFSET_EXTERNAL_RATE,
|
||||
®, sizeof(reg), 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
data = be32_to_cpu(reg) & 0x0f;
|
||||
if (data < ARRAY_SIZE(snd_dg00x_stream_rates))
|
||||
*rate = snd_dg00x_stream_rates[data];
|
||||
/* This means desync. */
|
||||
else
|
||||
err = -EBUSY;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void finish_session(struct snd_dg00x *dg00x)
|
||||
{
|
||||
__be32 data = cpu_to_be32(0x00000003);
|
||||
|
||||
snd_fw_transaction(dg00x->unit, TCODE_WRITE_QUADLET_REQUEST,
|
||||
DG00X_ADDR_BASE + DG00X_OFFSET_STREAMING_SET,
|
||||
&data, sizeof(data), 0);
|
||||
}
|
||||
|
||||
static int begin_session(struct snd_dg00x *dg00x)
|
||||
{
|
||||
__be32 data;
|
||||
u32 curr;
|
||||
int err;
|
||||
|
||||
err = snd_fw_transaction(dg00x->unit, TCODE_READ_QUADLET_REQUEST,
|
||||
DG00X_ADDR_BASE + DG00X_OFFSET_STREAMING_STATE,
|
||||
&data, sizeof(data), 0);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
curr = be32_to_cpu(data);
|
||||
|
||||
if (curr == 0)
|
||||
curr = 2;
|
||||
|
||||
curr--;
|
||||
while (curr > 0) {
|
||||
data = cpu_to_be32(curr);
|
||||
err = snd_fw_transaction(dg00x->unit,
|
||||
TCODE_WRITE_QUADLET_REQUEST,
|
||||
DG00X_ADDR_BASE +
|
||||
DG00X_OFFSET_STREAMING_SET,
|
||||
&data, sizeof(data), 0);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
msleep(20);
|
||||
curr--;
|
||||
}
|
||||
|
||||
return 0;
|
||||
error:
|
||||
finish_session(dg00x);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void release_resources(struct snd_dg00x *dg00x)
|
||||
{
|
||||
__be32 data = 0;
|
||||
|
||||
/* Unregister isochronous channels for both direction. */
|
||||
snd_fw_transaction(dg00x->unit, TCODE_WRITE_QUADLET_REQUEST,
|
||||
DG00X_ADDR_BASE + DG00X_OFFSET_ISOC_CHANNELS,
|
||||
&data, sizeof(data), 0);
|
||||
|
||||
/* Release isochronous resources. */
|
||||
fw_iso_resources_free(&dg00x->tx_resources);
|
||||
fw_iso_resources_free(&dg00x->rx_resources);
|
||||
}
|
||||
|
||||
static int keep_resources(struct snd_dg00x *dg00x, unsigned int rate)
|
||||
{
|
||||
unsigned int i;
|
||||
__be32 data;
|
||||
int err;
|
||||
|
||||
/* Check sampling rate. */
|
||||
for (i = 0; i < SND_DG00X_RATE_COUNT; i++) {
|
||||
if (snd_dg00x_stream_rates[i] == rate)
|
||||
break;
|
||||
}
|
||||
if (i == SND_DG00X_RATE_COUNT)
|
||||
return -EINVAL;
|
||||
|
||||
/* Keep resources for out-stream. */
|
||||
err = amdtp_dot_set_parameters(&dg00x->rx_stream, rate,
|
||||
snd_dg00x_stream_pcm_channels[i]);
|
||||
if (err < 0)
|
||||
return err;
|
||||
err = fw_iso_resources_allocate(&dg00x->rx_resources,
|
||||
amdtp_stream_get_max_payload(&dg00x->rx_stream),
|
||||
fw_parent_device(dg00x->unit)->max_speed);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* Keep resources for in-stream. */
|
||||
err = amdtp_dot_set_parameters(&dg00x->tx_stream, rate,
|
||||
snd_dg00x_stream_pcm_channels[i]);
|
||||
if (err < 0)
|
||||
return err;
|
||||
err = fw_iso_resources_allocate(&dg00x->tx_resources,
|
||||
amdtp_stream_get_max_payload(&dg00x->tx_stream),
|
||||
fw_parent_device(dg00x->unit)->max_speed);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
/* Register isochronous channels for both direction. */
|
||||
data = cpu_to_be32((dg00x->tx_resources.channel << 16) |
|
||||
dg00x->rx_resources.channel);
|
||||
err = snd_fw_transaction(dg00x->unit, TCODE_WRITE_QUADLET_REQUEST,
|
||||
DG00X_ADDR_BASE + DG00X_OFFSET_ISOC_CHANNELS,
|
||||
&data, sizeof(data), 0);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
return 0;
|
||||
error:
|
||||
release_resources(dg00x);
|
||||
return err;
|
||||
}
|
||||
|
||||
int snd_dg00x_stream_init_duplex(struct snd_dg00x *dg00x)
|
||||
{
|
||||
int err;
|
||||
|
||||
/* For out-stream. */
|
||||
err = fw_iso_resources_init(&dg00x->rx_resources, dg00x->unit);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
err = amdtp_dot_init(&dg00x->rx_stream, dg00x->unit, AMDTP_OUT_STREAM);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
/* For in-stream. */
|
||||
err = fw_iso_resources_init(&dg00x->tx_resources, dg00x->unit);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
err = amdtp_dot_init(&dg00x->tx_stream, dg00x->unit, AMDTP_IN_STREAM);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
return 0;
|
||||
error:
|
||||
snd_dg00x_stream_destroy_duplex(dg00x);
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function should be called before starting streams or after stopping
|
||||
* streams.
|
||||
*/
|
||||
void snd_dg00x_stream_destroy_duplex(struct snd_dg00x *dg00x)
|
||||
{
|
||||
amdtp_stream_destroy(&dg00x->rx_stream);
|
||||
fw_iso_resources_destroy(&dg00x->rx_resources);
|
||||
|
||||
amdtp_stream_destroy(&dg00x->tx_stream);
|
||||
fw_iso_resources_destroy(&dg00x->tx_resources);
|
||||
}
|
||||
|
||||
int snd_dg00x_stream_start_duplex(struct snd_dg00x *dg00x, unsigned int rate)
|
||||
{
|
||||
unsigned int curr_rate;
|
||||
int err = 0;
|
||||
|
||||
if (dg00x->substreams_counter == 0)
|
||||
goto end;
|
||||
|
||||
/* Check current sampling rate. */
|
||||
err = snd_dg00x_stream_get_local_rate(dg00x, &curr_rate);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
if (rate == 0)
|
||||
rate = curr_rate;
|
||||
if (curr_rate != rate ||
|
||||
amdtp_streaming_error(&dg00x->tx_stream) ||
|
||||
amdtp_streaming_error(&dg00x->rx_stream)) {
|
||||
finish_session(dg00x);
|
||||
|
||||
amdtp_stream_stop(&dg00x->tx_stream);
|
||||
amdtp_stream_stop(&dg00x->rx_stream);
|
||||
release_resources(dg00x);
|
||||
}
|
||||
|
||||
/*
|
||||
* No packets are transmitted without receiving packets, reagardless of
|
||||
* which source of clock is used.
|
||||
*/
|
||||
if (!amdtp_stream_running(&dg00x->rx_stream)) {
|
||||
err = snd_dg00x_stream_set_local_rate(dg00x, rate);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
err = keep_resources(dg00x, rate);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
err = begin_session(dg00x);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
err = amdtp_stream_start(&dg00x->rx_stream,
|
||||
dg00x->rx_resources.channel,
|
||||
fw_parent_device(dg00x->unit)->max_speed);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
if (!amdtp_stream_wait_callback(&dg00x->rx_stream,
|
||||
CALLBACK_TIMEOUT)) {
|
||||
err = -ETIMEDOUT;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The value of SYT field in transmitted packets is always 0x0000. Thus,
|
||||
* duplex streams with timestamp synchronization cannot be built.
|
||||
*/
|
||||
if (!amdtp_stream_running(&dg00x->tx_stream)) {
|
||||
err = amdtp_stream_start(&dg00x->tx_stream,
|
||||
dg00x->tx_resources.channel,
|
||||
fw_parent_device(dg00x->unit)->max_speed);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
if (!amdtp_stream_wait_callback(&dg00x->tx_stream,
|
||||
CALLBACK_TIMEOUT)) {
|
||||
err = -ETIMEDOUT;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
end:
|
||||
return err;
|
||||
error:
|
||||
finish_session(dg00x);
|
||||
|
||||
amdtp_stream_stop(&dg00x->tx_stream);
|
||||
amdtp_stream_stop(&dg00x->rx_stream);
|
||||
release_resources(dg00x);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
void snd_dg00x_stream_stop_duplex(struct snd_dg00x *dg00x)
|
||||
{
|
||||
if (dg00x->substreams_counter > 0)
|
||||
return;
|
||||
|
||||
amdtp_stream_stop(&dg00x->tx_stream);
|
||||
amdtp_stream_stop(&dg00x->rx_stream);
|
||||
finish_session(dg00x);
|
||||
release_resources(dg00x);
|
||||
|
||||
/*
|
||||
* Just after finishing the session, the device may lost transmitting
|
||||
* functionality for a short time.
|
||||
*/
|
||||
msleep(50);
|
||||
}
|
||||
|
||||
void snd_dg00x_stream_update_duplex(struct snd_dg00x *dg00x)
|
||||
{
|
||||
fw_iso_resources_update(&dg00x->tx_resources);
|
||||
fw_iso_resources_update(&dg00x->rx_resources);
|
||||
|
||||
amdtp_stream_update(&dg00x->tx_stream);
|
||||
amdtp_stream_update(&dg00x->rx_stream);
|
||||
}
|
||||
|
||||
void snd_dg00x_stream_lock_changed(struct snd_dg00x *dg00x)
|
||||
{
|
||||
dg00x->dev_lock_changed = true;
|
||||
wake_up(&dg00x->hwdep_wait);
|
||||
}
|
||||
|
||||
int snd_dg00x_stream_lock_try(struct snd_dg00x *dg00x)
|
||||
{
|
||||
int err;
|
||||
|
||||
spin_lock_irq(&dg00x->lock);
|
||||
|
||||
/* user land lock this */
|
||||
if (dg00x->dev_lock_count < 0) {
|
||||
err = -EBUSY;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* this is the first time */
|
||||
if (dg00x->dev_lock_count++ == 0)
|
||||
snd_dg00x_stream_lock_changed(dg00x);
|
||||
err = 0;
|
||||
end:
|
||||
spin_unlock_irq(&dg00x->lock);
|
||||
return err;
|
||||
}
|
||||
|
||||
void snd_dg00x_stream_lock_release(struct snd_dg00x *dg00x)
|
||||
{
|
||||
spin_lock_irq(&dg00x->lock);
|
||||
|
||||
if (WARN_ON(dg00x->dev_lock_count <= 0))
|
||||
goto end;
|
||||
if (--dg00x->dev_lock_count == 0)
|
||||
snd_dg00x_stream_lock_changed(dg00x);
|
||||
end:
|
||||
spin_unlock_irq(&dg00x->lock);
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* digi00x-transaction.c - a part of driver for Digidesign Digi 002/003 family
|
||||
*
|
||||
* Copyright (c) 2014-2015 Takashi Sakamoto
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#include <sound/asound.h>
|
||||
#include "digi00x.h"
|
||||
|
||||
static int fill_midi_message(struct snd_rawmidi_substream *substream, u8 *buf)
|
||||
{
|
||||
int bytes;
|
||||
|
||||
buf[0] = 0x80;
|
||||
bytes = snd_rawmidi_transmit_peek(substream, buf + 1, 2);
|
||||
if (bytes >= 0)
|
||||
buf[3] = 0xc0 | bytes;
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
static void handle_midi_control(struct snd_dg00x *dg00x, __be32 *buf,
|
||||
unsigned int length)
|
||||
{
|
||||
struct snd_rawmidi_substream *substream;
|
||||
unsigned int i;
|
||||
unsigned int len;
|
||||
u8 *b;
|
||||
|
||||
substream = ACCESS_ONCE(dg00x->in_control);
|
||||
if (substream == NULL)
|
||||
return;
|
||||
|
||||
length /= 4;
|
||||
|
||||
for (i = 0; i < length; i++) {
|
||||
b = (u8 *)&buf[i];
|
||||
len = b[3] & 0xf;
|
||||
if (len > 0)
|
||||
snd_rawmidi_receive(dg00x->in_control, b + 1, len);
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_unknown_message(struct snd_dg00x *dg00x,
|
||||
unsigned long long offset, __be32 *buf)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&dg00x->lock, flags);
|
||||
dg00x->msg = be32_to_cpu(*buf);
|
||||
spin_unlock_irqrestore(&dg00x->lock, flags);
|
||||
|
||||
wake_up(&dg00x->hwdep_wait);
|
||||
}
|
||||
|
||||
static void handle_message(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_dg00x *dg00x = callback_data;
|
||||
__be32 *buf = (__be32 *)data;
|
||||
|
||||
if (offset == dg00x->async_handler.offset)
|
||||
handle_unknown_message(dg00x, offset, buf);
|
||||
else if (offset == dg00x->async_handler.offset + 4)
|
||||
handle_midi_control(dg00x, buf, length);
|
||||
|
||||
fw_send_response(card, request, RCODE_COMPLETE);
|
||||
}
|
||||
|
||||
int snd_dg00x_transaction_reregister(struct snd_dg00x *dg00x)
|
||||
{
|
||||
struct fw_device *device = fw_parent_device(dg00x->unit);
|
||||
__be32 data[2];
|
||||
int err;
|
||||
|
||||
/* Unknown. 4bytes. */
|
||||
data[0] = cpu_to_be32((device->card->node_id << 16) |
|
||||
(dg00x->async_handler.offset >> 32));
|
||||
data[1] = cpu_to_be32(dg00x->async_handler.offset);
|
||||
err = snd_fw_transaction(dg00x->unit, TCODE_WRITE_BLOCK_REQUEST,
|
||||
DG00X_ADDR_BASE + DG00X_OFFSET_MESSAGE_ADDR,
|
||||
&data, sizeof(data), 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
/* Asynchronous transactions for MIDI control message. */
|
||||
data[0] = cpu_to_be32((device->card->node_id << 16) |
|
||||
(dg00x->async_handler.offset >> 32));
|
||||
data[1] = cpu_to_be32(dg00x->async_handler.offset + 4);
|
||||
return snd_fw_transaction(dg00x->unit, TCODE_WRITE_BLOCK_REQUEST,
|
||||
DG00X_ADDR_BASE + DG00X_OFFSET_MIDI_CTL_ADDR,
|
||||
&data, sizeof(data), 0);
|
||||
}
|
||||
|
||||
int snd_dg00x_transaction_register(struct snd_dg00x *dg00x)
|
||||
{
|
||||
static const struct fw_address_region resp_register_region = {
|
||||
.start = 0xffffe0000000ull,
|
||||
.end = 0xffffe000ffffull,
|
||||
};
|
||||
int err;
|
||||
|
||||
dg00x->async_handler.length = 12;
|
||||
dg00x->async_handler.address_callback = handle_message;
|
||||
dg00x->async_handler.callback_data = dg00x;
|
||||
|
||||
err = fw_core_add_address_handler(&dg00x->async_handler,
|
||||
&resp_register_region);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = snd_dg00x_transaction_reregister(dg00x);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
err = snd_fw_async_midi_port_init(&dg00x->out_control, dg00x->unit,
|
||||
DG00X_ADDR_BASE + DG00X_OFFSET_MMC,
|
||||
4, fill_midi_message);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
return err;
|
||||
error:
|
||||
fw_core_remove_address_handler(&dg00x->async_handler);
|
||||
dg00x->async_handler.address_callback = NULL;
|
||||
return err;
|
||||
}
|
||||
|
||||
void snd_dg00x_transaction_unregister(struct snd_dg00x *dg00x)
|
||||
{
|
||||
snd_fw_async_midi_port_destroy(&dg00x->out_control);
|
||||
fw_core_remove_address_handler(&dg00x->async_handler);
|
||||
}
|
|
@ -0,0 +1,170 @@
|
|||
/*
|
||||
* digi00x.c - a part of driver for Digidesign Digi 002/003 family
|
||||
*
|
||||
* Copyright (c) 2014-2015 Takashi Sakamoto
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#include "digi00x.h"
|
||||
|
||||
MODULE_DESCRIPTION("Digidesign Digi 002/003 family Driver");
|
||||
MODULE_AUTHOR("Takashi Sakamoto <o-takashi@sakamocchi.jp>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
||||
#define VENDOR_DIGIDESIGN 0x00a07e
|
||||
#define MODEL_DIGI00X 0x000002
|
||||
|
||||
static int name_card(struct snd_dg00x *dg00x)
|
||||
{
|
||||
struct fw_device *fw_dev = fw_parent_device(dg00x->unit);
|
||||
char name[32] = {0};
|
||||
char *model;
|
||||
int err;
|
||||
|
||||
err = fw_csr_string(dg00x->unit->directory, CSR_MODEL, name,
|
||||
sizeof(name));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
model = skip_spaces(name);
|
||||
|
||||
strcpy(dg00x->card->driver, "Digi00x");
|
||||
strcpy(dg00x->card->shortname, model);
|
||||
strcpy(dg00x->card->mixername, model);
|
||||
snprintf(dg00x->card->longname, sizeof(dg00x->card->longname),
|
||||
"Digidesign %s, GUID %08x%08x at %s, S%d", model,
|
||||
fw_dev->config_rom[3], fw_dev->config_rom[4],
|
||||
dev_name(&dg00x->unit->device), 100 << fw_dev->max_speed);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dg00x_card_free(struct snd_card *card)
|
||||
{
|
||||
struct snd_dg00x *dg00x = card->private_data;
|
||||
|
||||
snd_dg00x_stream_destroy_duplex(dg00x);
|
||||
snd_dg00x_transaction_unregister(dg00x);
|
||||
|
||||
fw_unit_put(dg00x->unit);
|
||||
|
||||
mutex_destroy(&dg00x->mutex);
|
||||
}
|
||||
|
||||
static int snd_dg00x_probe(struct fw_unit *unit,
|
||||
const struct ieee1394_device_id *entry)
|
||||
{
|
||||
struct snd_card *card;
|
||||
struct snd_dg00x *dg00x;
|
||||
int err;
|
||||
|
||||
/* create card */
|
||||
err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE,
|
||||
sizeof(struct snd_dg00x), &card);
|
||||
if (err < 0)
|
||||
return err;
|
||||
card->private_free = dg00x_card_free;
|
||||
|
||||
/* initialize myself */
|
||||
dg00x = card->private_data;
|
||||
dg00x->card = card;
|
||||
dg00x->unit = fw_unit_get(unit);
|
||||
|
||||
mutex_init(&dg00x->mutex);
|
||||
spin_lock_init(&dg00x->lock);
|
||||
init_waitqueue_head(&dg00x->hwdep_wait);
|
||||
|
||||
err = name_card(dg00x);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
err = snd_dg00x_stream_init_duplex(dg00x);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
snd_dg00x_proc_init(dg00x);
|
||||
|
||||
err = snd_dg00x_create_pcm_devices(dg00x);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
err = snd_dg00x_create_midi_devices(dg00x);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
err = snd_dg00x_create_hwdep_device(dg00x);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
err = snd_dg00x_transaction_register(dg00x);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
err = snd_card_register(card);
|
||||
if (err < 0)
|
||||
goto error;
|
||||
|
||||
dev_set_drvdata(&unit->device, dg00x);
|
||||
|
||||
return err;
|
||||
error:
|
||||
snd_card_free(card);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void snd_dg00x_update(struct fw_unit *unit)
|
||||
{
|
||||
struct snd_dg00x *dg00x = dev_get_drvdata(&unit->device);
|
||||
|
||||
snd_dg00x_transaction_reregister(dg00x);
|
||||
|
||||
mutex_lock(&dg00x->mutex);
|
||||
snd_dg00x_stream_update_duplex(dg00x);
|
||||
mutex_unlock(&dg00x->mutex);
|
||||
}
|
||||
|
||||
static void snd_dg00x_remove(struct fw_unit *unit)
|
||||
{
|
||||
struct snd_dg00x *dg00x = dev_get_drvdata(&unit->device);
|
||||
|
||||
/* No need to wait for releasing card object in this context. */
|
||||
snd_card_free_when_closed(dg00x->card);
|
||||
}
|
||||
|
||||
static const struct ieee1394_device_id snd_dg00x_id_table[] = {
|
||||
/* Both of 002/003 use the same ID. */
|
||||
{
|
||||
.match_flags = IEEE1394_MATCH_VENDOR_ID |
|
||||
IEEE1394_MATCH_MODEL_ID,
|
||||
.vendor_id = VENDOR_DIGIDESIGN,
|
||||
.model_id = MODEL_DIGI00X,
|
||||
},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(ieee1394, snd_dg00x_id_table);
|
||||
|
||||
static struct fw_driver dg00x_driver = {
|
||||
.driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "snd-firewire-digi00x",
|
||||
.bus = &fw_bus_type,
|
||||
},
|
||||
.probe = snd_dg00x_probe,
|
||||
.update = snd_dg00x_update,
|
||||
.remove = snd_dg00x_remove,
|
||||
.id_table = snd_dg00x_id_table,
|
||||
};
|
||||
|
||||
static int __init snd_dg00x_init(void)
|
||||
{
|
||||
return driver_register(&dg00x_driver.driver);
|
||||
}
|
||||
|
||||
static void __exit snd_dg00x_exit(void)
|
||||
{
|
||||
driver_unregister(&dg00x_driver.driver);
|
||||
}
|
||||
|
||||
module_init(snd_dg00x_init);
|
||||
module_exit(snd_dg00x_exit);
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* digi00x.h - a part of driver for Digidesign Digi 002/003 family
|
||||
*
|
||||
* Copyright (c) 2014-2015 Takashi Sakamoto
|
||||
*
|
||||
* Licensed under the terms of the GNU General Public License, version 2.
|
||||
*/
|
||||
|
||||
#ifndef SOUND_DIGI00X_H_INCLUDED
|
||||
#define SOUND_DIGI00X_H_INCLUDED
|
||||
|
||||
#include <linux/compat.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/firewire.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/info.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/firewire.h>
|
||||
#include <sound/hwdep.h>
|
||||
#include <sound/rawmidi.h>
|
||||
|
||||
#include "../lib.h"
|
||||
#include "../iso-resources.h"
|
||||
#include "../amdtp-stream.h"
|
||||
|
||||
struct snd_dg00x {
|
||||
struct snd_card *card;
|
||||
struct fw_unit *unit;
|
||||
|
||||
struct mutex mutex;
|
||||
spinlock_t lock;
|
||||
|
||||
struct amdtp_stream tx_stream;
|
||||
struct fw_iso_resources tx_resources;
|
||||
|
||||
struct amdtp_stream rx_stream;
|
||||
struct fw_iso_resources rx_resources;
|
||||
|
||||
unsigned int substreams_counter;
|
||||
|
||||
/* for uapi */
|
||||
int dev_lock_count;
|
||||
bool dev_lock_changed;
|
||||
wait_queue_head_t hwdep_wait;
|
||||
|
||||
/* For asynchronous messages. */
|
||||
struct fw_address_handler async_handler;
|
||||
u32 msg;
|
||||
|
||||
/* For asynchronous MIDI controls. */
|
||||
struct snd_rawmidi_substream *in_control;
|
||||
struct snd_fw_async_midi_port out_control;
|
||||
};
|
||||
|
||||
#define DG00X_ADDR_BASE 0xffffe0000000ull
|
||||
|
||||
#define DG00X_OFFSET_STREAMING_STATE 0x0000
|
||||
#define DG00X_OFFSET_STREAMING_SET 0x0004
|
||||
#define DG00X_OFFSET_MIDI_CTL_ADDR 0x0008
|
||||
/* For LSB of the address 0x000c */
|
||||
/* unknown 0x0010 */
|
||||
#define DG00X_OFFSET_MESSAGE_ADDR 0x0014
|
||||
/* For LSB of the address 0x0018 */
|
||||
/* unknown 0x001c */
|
||||
/* unknown 0x0020 */
|
||||
/* not used 0x0024--0x00ff */
|
||||
#define DG00X_OFFSET_ISOC_CHANNELS 0x0100
|
||||
/* unknown 0x0104 */
|
||||
/* unknown 0x0108 */
|
||||
/* unknown 0x010c */
|
||||
#define DG00X_OFFSET_LOCAL_RATE 0x0110
|
||||
#define DG00X_OFFSET_EXTERNAL_RATE 0x0114
|
||||
#define DG00X_OFFSET_CLOCK_SOURCE 0x0118
|
||||
#define DG00X_OFFSET_OPT_IFACE_MODE 0x011c
|
||||
/* unknown 0x0120 */
|
||||
/* Mixer control on/off 0x0124 */
|
||||
/* unknown 0x0128 */
|
||||
#define DG00X_OFFSET_DETECT_EXTERNAL 0x012c
|
||||
/* unknown 0x0138 */
|
||||
#define DG00X_OFFSET_MMC 0x0400
|
||||
|
||||
enum snd_dg00x_rate {
|
||||
SND_DG00X_RATE_44100 = 0,
|
||||
SND_DG00X_RATE_48000,
|
||||
SND_DG00X_RATE_88200,
|
||||
SND_DG00X_RATE_96000,
|
||||
SND_DG00X_RATE_COUNT,
|
||||
};
|
||||
|
||||
enum snd_dg00x_clock {
|
||||
SND_DG00X_CLOCK_INTERNAL = 0,
|
||||
SND_DG00X_CLOCK_SPDIF,
|
||||
SND_DG00X_CLOCK_ADAT,
|
||||
SND_DG00X_CLOCK_WORD,
|
||||
SND_DG00X_CLOCK_COUNT,
|
||||
};
|
||||
|
||||
enum snd_dg00x_optical_mode {
|
||||
SND_DG00X_OPT_IFACE_MODE_ADAT = 0,
|
||||
SND_DG00X_OPT_IFACE_MODE_SPDIF,
|
||||
SND_DG00X_OPT_IFACE_MODE_COUNT,
|
||||
};
|
||||
|
||||
#define DOT_MIDI_IN_PORTS 1
|
||||
#define DOT_MIDI_OUT_PORTS 2
|
||||
|
||||
int amdtp_dot_init(struct amdtp_stream *s, struct fw_unit *unit,
|
||||
enum amdtp_stream_direction dir);
|
||||
int amdtp_dot_set_parameters(struct amdtp_stream *s, unsigned int rate,
|
||||
unsigned int pcm_channels);
|
||||
void amdtp_dot_reset(struct amdtp_stream *s);
|
||||
int amdtp_dot_add_pcm_hw_constraints(struct amdtp_stream *s,
|
||||
struct snd_pcm_runtime *runtime);
|
||||
void amdtp_dot_set_pcm_format(struct amdtp_stream *s, snd_pcm_format_t format);
|
||||
void amdtp_dot_midi_trigger(struct amdtp_stream *s, unsigned int port,
|
||||
struct snd_rawmidi_substream *midi);
|
||||
|
||||
int snd_dg00x_transaction_register(struct snd_dg00x *dg00x);
|
||||
int snd_dg00x_transaction_reregister(struct snd_dg00x *dg00x);
|
||||
void snd_dg00x_transaction_unregister(struct snd_dg00x *dg00x);
|
||||
|
||||
extern const unsigned int snd_dg00x_stream_rates[SND_DG00X_RATE_COUNT];
|
||||
extern const unsigned int snd_dg00x_stream_pcm_channels[SND_DG00X_RATE_COUNT];
|
||||
int snd_dg00x_stream_get_external_rate(struct snd_dg00x *dg00x,
|
||||
unsigned int *rate);
|
||||
int snd_dg00x_stream_get_local_rate(struct snd_dg00x *dg00x,
|
||||
unsigned int *rate);
|
||||
int snd_dg00x_stream_set_local_rate(struct snd_dg00x *dg00x, unsigned int rate);
|
||||
int snd_dg00x_stream_get_clock(struct snd_dg00x *dg00x,
|
||||
enum snd_dg00x_clock *clock);
|
||||
int snd_dg00x_stream_check_external_clock(struct snd_dg00x *dg00x,
|
||||
bool *detect);
|
||||
int snd_dg00x_stream_init_duplex(struct snd_dg00x *dg00x);
|
||||
int snd_dg00x_stream_start_duplex(struct snd_dg00x *dg00x, unsigned int rate);
|
||||
void snd_dg00x_stream_stop_duplex(struct snd_dg00x *dg00x);
|
||||
void snd_dg00x_stream_update_duplex(struct snd_dg00x *dg00x);
|
||||
void snd_dg00x_stream_destroy_duplex(struct snd_dg00x *dg00x);
|
||||
|
||||
void snd_dg00x_stream_lock_changed(struct snd_dg00x *dg00x);
|
||||
int snd_dg00x_stream_lock_try(struct snd_dg00x *dg00x);
|
||||
void snd_dg00x_stream_lock_release(struct snd_dg00x *dg00x);
|
||||
|
||||
void snd_dg00x_proc_init(struct snd_dg00x *dg00x);
|
||||
|
||||
int snd_dg00x_create_pcm_devices(struct snd_dg00x *dg00x);
|
||||
|
||||
int snd_dg00x_create_midi_devices(struct snd_dg00x *dg00x);
|
||||
|
||||
int snd_dg00x_create_hwdep_device(struct snd_dg00x *dg00x);
|
||||
#endif
|
|
@ -17,7 +17,7 @@
|
|||
#include <linux/delay.h>
|
||||
#include "fcp.h"
|
||||
#include "lib.h"
|
||||
#include "amdtp.h"
|
||||
#include "amdtp-stream.h"
|
||||
|
||||
#define CTS_AVC 0x00
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
snd-fireworks-objs := fireworks_transaction.o fireworks_command.o \
|
||||
fireworks_stream.o fireworks_proc.o fireworks_midi.o \
|
||||
fireworks_pcm.o fireworks_hwdep.o fireworks.o
|
||||
obj-m += snd-fireworks.o
|
||||
obj-$(CONFIG_SND_FIREWORKS) += snd-fireworks.o
|
||||
|
|
|
@ -138,12 +138,12 @@ get_hardware_info(struct snd_efw *efw)
|
|||
efw->midi_out_ports = hwinfo->midi_out_ports;
|
||||
efw->midi_in_ports = hwinfo->midi_in_ports;
|
||||
|
||||
if (hwinfo->amdtp_tx_pcm_channels > AMDTP_MAX_CHANNELS_FOR_PCM ||
|
||||
hwinfo->amdtp_tx_pcm_channels_2x > AMDTP_MAX_CHANNELS_FOR_PCM ||
|
||||
hwinfo->amdtp_tx_pcm_channels_4x > AMDTP_MAX_CHANNELS_FOR_PCM ||
|
||||
hwinfo->amdtp_rx_pcm_channels > AMDTP_MAX_CHANNELS_FOR_PCM ||
|
||||
hwinfo->amdtp_rx_pcm_channels_2x > AMDTP_MAX_CHANNELS_FOR_PCM ||
|
||||
hwinfo->amdtp_rx_pcm_channels_4x > AMDTP_MAX_CHANNELS_FOR_PCM) {
|
||||
if (hwinfo->amdtp_tx_pcm_channels > AM824_MAX_CHANNELS_FOR_PCM ||
|
||||
hwinfo->amdtp_tx_pcm_channels_2x > AM824_MAX_CHANNELS_FOR_PCM ||
|
||||
hwinfo->amdtp_tx_pcm_channels_4x > AM824_MAX_CHANNELS_FOR_PCM ||
|
||||
hwinfo->amdtp_rx_pcm_channels > AM824_MAX_CHANNELS_FOR_PCM ||
|
||||
hwinfo->amdtp_rx_pcm_channels_2x > AM824_MAX_CHANNELS_FOR_PCM ||
|
||||
hwinfo->amdtp_rx_pcm_channels_4x > AM824_MAX_CHANNELS_FOR_PCM) {
|
||||
err = -ENOSYS;
|
||||
goto end;
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
#include "../packets-buffer.h"
|
||||
#include "../iso-resources.h"
|
||||
#include "../amdtp.h"
|
||||
#include "../amdtp-am824.h"
|
||||
#include "../cmp.h"
|
||||
#include "../lib.h"
|
||||
|
||||
|
|
|
@ -257,7 +257,7 @@ int snd_efw_command_get_phys_meters(struct snd_efw *efw,
|
|||
struct snd_efw_phys_meters *meters,
|
||||
unsigned int len)
|
||||
{
|
||||
__be32 *buf = (__be32 *)meters;
|
||||
u32 *buf = (u32 *)meters;
|
||||
unsigned int i;
|
||||
int err;
|
||||
|
||||
|
|
|
@ -73,10 +73,10 @@ static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up)
|
|||
spin_lock_irqsave(&efw->lock, flags);
|
||||
|
||||
if (up)
|
||||
amdtp_stream_midi_trigger(&efw->tx_stream,
|
||||
amdtp_am824_midi_trigger(&efw->tx_stream,
|
||||
substrm->number, substrm);
|
||||
else
|
||||
amdtp_stream_midi_trigger(&efw->tx_stream,
|
||||
amdtp_am824_midi_trigger(&efw->tx_stream,
|
||||
substrm->number, NULL);
|
||||
|
||||
spin_unlock_irqrestore(&efw->lock, flags);
|
||||
|
@ -90,11 +90,11 @@ static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up)
|
|||
spin_lock_irqsave(&efw->lock, flags);
|
||||
|
||||
if (up)
|
||||
amdtp_stream_midi_trigger(&efw->rx_stream,
|
||||
substrm->number, substrm);
|
||||
amdtp_am824_midi_trigger(&efw->rx_stream,
|
||||
substrm->number, substrm);
|
||||
else
|
||||
amdtp_stream_midi_trigger(&efw->rx_stream,
|
||||
substrm->number, NULL);
|
||||
amdtp_am824_midi_trigger(&efw->rx_stream,
|
||||
substrm->number, NULL);
|
||||
|
||||
spin_unlock_irqrestore(&efw->lock, flags);
|
||||
}
|
||||
|
|
|
@ -159,11 +159,11 @@ pcm_init_hw_params(struct snd_efw *efw,
|
|||
SNDRV_PCM_INFO_MMAP_VALID;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
|
||||
runtime->hw.formats = AMDTP_IN_PCM_FORMAT_BITS;
|
||||
runtime->hw.formats = AM824_IN_PCM_FORMAT_BITS;
|
||||
s = &efw->tx_stream;
|
||||
pcm_channels = efw->pcm_capture_channels;
|
||||
} else {
|
||||
runtime->hw.formats = AMDTP_OUT_PCM_FORMAT_BITS;
|
||||
runtime->hw.formats = AM824_OUT_PCM_FORMAT_BITS;
|
||||
s = &efw->rx_stream;
|
||||
pcm_channels = efw->pcm_playback_channels;
|
||||
}
|
||||
|
@ -187,7 +187,7 @@ pcm_init_hw_params(struct snd_efw *efw,
|
|||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
err = amdtp_stream_add_pcm_hw_constraints(s, runtime);
|
||||
err = amdtp_am824_add_pcm_hw_constraints(s, runtime);
|
||||
end:
|
||||
return err;
|
||||
}
|
||||
|
@ -253,7 +253,8 @@ static int pcm_capture_hw_params(struct snd_pcm_substream *substream,
|
|||
|
||||
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN)
|
||||
atomic_inc(&efw->capture_substreams);
|
||||
amdtp_stream_set_pcm_format(&efw->tx_stream, params_format(hw_params));
|
||||
|
||||
amdtp_am824_set_pcm_format(&efw->tx_stream, params_format(hw_params));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -270,7 +271,8 @@ static int pcm_playback_hw_params(struct snd_pcm_substream *substream,
|
|||
|
||||
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN)
|
||||
atomic_inc(&efw->playback_substreams);
|
||||
amdtp_stream_set_pcm_format(&efw->rx_stream, params_format(hw_params));
|
||||
|
||||
amdtp_am824_set_pcm_format(&efw->rx_stream, params_format(hw_params));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ init_stream(struct snd_efw *efw, struct amdtp_stream *stream)
|
|||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
err = amdtp_stream_init(stream, efw->unit, s_dir, CIP_BLOCKING);
|
||||
err = amdtp_am824_init(stream, efw->unit, s_dir, CIP_BLOCKING);
|
||||
if (err < 0) {
|
||||
amdtp_stream_destroy(stream);
|
||||
cmp_connection_destroy(conn);
|
||||
|
@ -73,8 +73,10 @@ start_stream(struct snd_efw *efw, struct amdtp_stream *stream,
|
|||
midi_ports = efw->midi_in_ports;
|
||||
}
|
||||
|
||||
amdtp_stream_set_parameters(stream, sampling_rate,
|
||||
pcm_channels, midi_ports);
|
||||
err = amdtp_am824_set_parameters(stream, sampling_rate,
|
||||
pcm_channels, midi_ports, false);
|
||||
if (err < 0)
|
||||
goto end;
|
||||
|
||||
/* establish connection via CMP */
|
||||
err = cmp_connection_establish(conn,
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <linux/device.h>
|
||||
#include <linux/firewire.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include "lib.h"
|
||||
|
||||
#define ERROR_RETRY_DELAY_MS 20
|
||||
|
@ -66,6 +67,147 @@ int snd_fw_transaction(struct fw_unit *unit, int tcode,
|
|||
}
|
||||
EXPORT_SYMBOL(snd_fw_transaction);
|
||||
|
||||
static void async_midi_port_callback(struct fw_card *card, int rcode,
|
||||
void *data, size_t length,
|
||||
void *callback_data)
|
||||
{
|
||||
struct snd_fw_async_midi_port *port = callback_data;
|
||||
struct snd_rawmidi_substream *substream = ACCESS_ONCE(port->substream);
|
||||
|
||||
/* This port is closed. */
|
||||
if (substream == NULL)
|
||||
return;
|
||||
|
||||
if (rcode == RCODE_COMPLETE)
|
||||
snd_rawmidi_transmit_ack(substream, port->consume_bytes);
|
||||
else if (!rcode_is_permanent_error(rcode))
|
||||
/* To start next transaction immediately for recovery. */
|
||||
port->next_ktime = ktime_set(0, 0);
|
||||
else
|
||||
/* Don't continue processing. */
|
||||
port->error = true;
|
||||
|
||||
port->idling = true;
|
||||
|
||||
if (!snd_rawmidi_transmit_empty(substream))
|
||||
schedule_work(&port->work);
|
||||
}
|
||||
|
||||
static void midi_port_work(struct work_struct *work)
|
||||
{
|
||||
struct snd_fw_async_midi_port *port =
|
||||
container_of(work, struct snd_fw_async_midi_port, work);
|
||||
struct snd_rawmidi_substream *substream = ACCESS_ONCE(port->substream);
|
||||
int generation;
|
||||
int type;
|
||||
|
||||
/* Under transacting or error state. */
|
||||
if (!port->idling || port->error)
|
||||
return;
|
||||
|
||||
/* Nothing to do. */
|
||||
if (substream == NULL || snd_rawmidi_transmit_empty(substream))
|
||||
return;
|
||||
|
||||
/* Do it in next chance. */
|
||||
if (ktime_after(port->next_ktime, ktime_get())) {
|
||||
schedule_work(&port->work);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fill the buffer. The callee must use snd_rawmidi_transmit_peek().
|
||||
* Later, snd_rawmidi_transmit_ack() is called.
|
||||
*/
|
||||
memset(port->buf, 0, port->len);
|
||||
port->consume_bytes = port->fill(substream, port->buf);
|
||||
if (port->consume_bytes <= 0) {
|
||||
/* Do it in next chance, immediately. */
|
||||
if (port->consume_bytes == 0) {
|
||||
port->next_ktime = ktime_set(0, 0);
|
||||
schedule_work(&port->work);
|
||||
} else {
|
||||
/* Fatal error. */
|
||||
port->error = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* Calculate type of transaction. */
|
||||
if (port->len == 4)
|
||||
type = TCODE_WRITE_QUADLET_REQUEST;
|
||||
else
|
||||
type = TCODE_WRITE_BLOCK_REQUEST;
|
||||
|
||||
/* Set interval to next transaction. */
|
||||
port->next_ktime = ktime_add_ns(ktime_get(),
|
||||
port->consume_bytes * 8 * NSEC_PER_SEC / 31250);
|
||||
|
||||
/* Start this transaction. */
|
||||
port->idling = false;
|
||||
|
||||
/*
|
||||
* In Linux FireWire core, when generation is updated with memory
|
||||
* barrier, node id has already been updated. In this module, After
|
||||
* this smp_rmb(), load/store instructions to memory are completed.
|
||||
* Thus, both of generation and node id are available with recent
|
||||
* values. This is a light-serialization solution to handle bus reset
|
||||
* events on IEEE 1394 bus.
|
||||
*/
|
||||
generation = port->parent->generation;
|
||||
smp_rmb();
|
||||
|
||||
fw_send_request(port->parent->card, &port->transaction, type,
|
||||
port->parent->node_id, generation,
|
||||
port->parent->max_speed, port->addr,
|
||||
port->buf, port->len, async_midi_port_callback,
|
||||
port);
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_fw_async_midi_port_init - initialize asynchronous MIDI port structure
|
||||
* @port: the asynchronous MIDI port to initialize
|
||||
* @unit: the target of the asynchronous transaction
|
||||
* @addr: the address to which transactions are transferred
|
||||
* @len: the length of transaction
|
||||
* @fill: the callback function to fill given buffer, and returns the
|
||||
* number of consumed bytes for MIDI message.
|
||||
*
|
||||
*/
|
||||
int snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port,
|
||||
struct fw_unit *unit, u64 addr, unsigned int len,
|
||||
snd_fw_async_midi_port_fill fill)
|
||||
{
|
||||
port->len = DIV_ROUND_UP(len, 4) * 4;
|
||||
port->buf = kzalloc(port->len, GFP_KERNEL);
|
||||
if (port->buf == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
port->parent = fw_parent_device(unit);
|
||||
port->addr = addr;
|
||||
port->fill = fill;
|
||||
port->idling = true;
|
||||
port->next_ktime = ktime_set(0, 0);
|
||||
port->error = false;
|
||||
|
||||
INIT_WORK(&port->work, midi_port_work);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(snd_fw_async_midi_port_init);
|
||||
|
||||
/**
|
||||
* snd_fw_async_midi_port_destroy - free asynchronous MIDI port structure
|
||||
* @port: the asynchronous MIDI port structure
|
||||
*/
|
||||
void snd_fw_async_midi_port_destroy(struct snd_fw_async_midi_port *port)
|
||||
{
|
||||
snd_fw_async_midi_port_finish(port);
|
||||
cancel_work_sync(&port->work);
|
||||
kfree(port->buf);
|
||||
}
|
||||
EXPORT_SYMBOL(snd_fw_async_midi_port_destroy);
|
||||
|
||||
MODULE_DESCRIPTION("FireWire audio helper functions");
|
||||
MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
#include <linux/firewire-constants.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/sched.h>
|
||||
#include <sound/rawmidi.h>
|
||||
|
||||
struct fw_unit;
|
||||
|
||||
|
@ -20,4 +22,58 @@ static inline bool rcode_is_permanent_error(int rcode)
|
|||
return rcode == RCODE_TYPE_ERROR || rcode == RCODE_ADDRESS_ERROR;
|
||||
}
|
||||
|
||||
struct snd_fw_async_midi_port;
|
||||
typedef int (*snd_fw_async_midi_port_fill)(
|
||||
struct snd_rawmidi_substream *substream,
|
||||
u8 *buf);
|
||||
|
||||
struct snd_fw_async_midi_port {
|
||||
struct fw_device *parent;
|
||||
struct work_struct work;
|
||||
bool idling;
|
||||
ktime_t next_ktime;
|
||||
bool error;
|
||||
|
||||
u64 addr;
|
||||
struct fw_transaction transaction;
|
||||
|
||||
u8 *buf;
|
||||
unsigned int len;
|
||||
|
||||
struct snd_rawmidi_substream *substream;
|
||||
snd_fw_async_midi_port_fill fill;
|
||||
unsigned int consume_bytes;
|
||||
};
|
||||
|
||||
int snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port,
|
||||
struct fw_unit *unit, u64 addr, unsigned int len,
|
||||
snd_fw_async_midi_port_fill fill);
|
||||
void snd_fw_async_midi_port_destroy(struct snd_fw_async_midi_port *port);
|
||||
|
||||
/**
|
||||
* snd_fw_async_midi_port_run - run transactions for the async MIDI port
|
||||
* @port: the asynchronous MIDI port
|
||||
* @substream: the MIDI substream
|
||||
*/
|
||||
static inline void
|
||||
snd_fw_async_midi_port_run(struct snd_fw_async_midi_port *port,
|
||||
struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
if (!port->error) {
|
||||
port->substream = substream;
|
||||
schedule_work(&port->work);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_fw_async_midi_port_finish - finish the asynchronous MIDI port
|
||||
* @port: the asynchronous MIDI port
|
||||
*/
|
||||
static inline void
|
||||
snd_fw_async_midi_port_finish(struct snd_fw_async_midi_port *port)
|
||||
{
|
||||
port->substream = NULL;
|
||||
port->error = false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue