sound updates for 4.4-rc1

Here is the first batch of updates for sound system on 4.4-rc1.
 Again at this time, the update looks fairly calm; no big changes in
 either ALSA core or ASoC infrastructures, rather all small cleanups,
 in addition to the new stuff as usual.
 
 The biggest changes are about Firewire sound devices.  It gained lots
 of new device support, and MIDI functionality.  Also there are updates
 for a few still working-in-progress stuff (topology API and ASoC
 skylake), too.  But overall, this update should give no big surprise.
 
 Some highlight is below:
 
 Core:
  - A few more Kconfig items for tinification; it's marked as EXPERT,
    so normal user should't be bothered :)
  - Refactoring with a new PCM hw_constraint helper
  - Removal of unused transfer_ack_{begin,end} PCM callbacks
 
 Firewire:
  - Restructuring of code subtree, lots of refactoring
  - Support AMDTP variants
  - New driver for Digidesign 002/003 family
  - Adds support for TASCAM FireOne to ALSA OXFW driver
  - Add MIDI support to TASCAM and Digi00x devices
 
 HD-Audio:
  - Automated modalias generation for codec drivers, finally
  - Improvement on heuristics for setting mixer name
  - A few fixes for longstanding bugs on Creative CA0132 cards
  - Addition of audio rate callback with i915 communication
  - Fix suspend issue on recent Dell XPS
  - Intel Lewisburg controller support
 
 ASoC:
  - Updates to the topology userspace interface
  - Big updates to the Renesas support (rcar)
  - More updates for supporting Intel Sky Lake systems
  - New drivers for Asahi Kasei Microdevices AK4613, Allwinnner A10,
    Cirrus Logic WM8998, Dialog DA7219, Nuvoton NAU8825, Rockchip
    S/PDIF, and Atmel class D amplifier
 
 USB-Audio:
  - A fix for newer Roland MIDI devices
  - Quirks and workarounds for Zoom R16/24 device
 
 Misc:
  - A few fixes for some old Cirrus CS46xx PCI sound boards
  - Yet another fixes for some old ESS Maestro3 PCI sound boards
 -----BEGIN PGP SIGNATURE-----
 Version: GnuPG v2
 
 iQIcBAABCAAGBQJWPNI9AAoJEGwxgFQ9KSmkOGIP+wUX4eIWwV4BK3mTjdPgvB+i
 M7niITY+baN6WqoX8Impe6CyvdOed/pQyHqkT8NFthhmYrgVU9iJBykgF1+BOPt3
 ZtYb0qi2mOPbTL0mwFv6oDu8Nvh6hBdCz6ZepQQdjyG4QBdLs7Cea3o3ncJEgsqI
 H9LHcCjwSIi4wAcIFVCiD2rJnI/sqhyH2jm9ay8TWDPkBnUBC0Pz1aE+DIbb5x5G
 m53rVTjE2dU5MQJrG+rQyH5ngR51Qs6XLYLSnkMXBrZPSP5UQxM282pG19ILumFK
 b5uOKWC2DyeWkmDTglAQXaSSbI+3Sj+W+oo05z51Pz2b8YEvehl2XjJpeB0Nlez/
 q+i/c8LnUYV8MPlJdldC1jZO8MVRYEX5fEWm1Hwie+q1YozFkhxIfwKCdXnazpYE
 ga9E5t/Utg0Rclb2vlYuHv3A4RmY1CW7VazP6PwZjhrahPMxN6zU8aTe+OzBJxkT
 i8Ka+R7mMCVyAfauNuBcQtJ+cY+6JbOXsT/5BWTGW0qsa9V17uUOXriAYyNBLwFL
 zcBa8OXHtYoiPbWIgE5rJUmRqTXJaOlNYyuot+AT2K7bOW9KMXmEROaSXyVNoqR8
 AVZHVmByViI8TuQ98L7vakvyHSKoi4x1Aq/ODrS4Ya4+5T60PmVTyCxT7UUE0FNo
 V39cwMFp8TCMifTeRP/+
 =l9SO
 -----END PGP SIGNATURE-----

Merge tag 'sound-4.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound

Pull sound updates from Takashi Iwai:
 "Here is the first batch of updates for sound system on 4.4-rc1.

  Again at this time, the update looks fairly calm; no big changes in
  either ALSA core or ASoC infrastructures, rather all small cleanups,
  in addition to the new stuff as usual.

  The biggest changes are about Firewire sound devices.  It gained lots
  of new device support, and MIDI functionality.  Also there are updates
  for a few still working-in-progress stuff (topology API and ASoC
  skylake), too.  But overall, this update should give no big surprise.

  Some highlights are below:

  Core:
   - A few more Kconfig items for tinification; it's marked as EXPERT,
     so normal user should't be bothered :)
   - Refactoring with a new PCM hw_constraint helper
   - Removal of unused transfer_ack_{begin,end} PCM callbacks

  Firewire:
   - Restructuring of code subtree, lots of refactoring
   - Support AMDTP variants
   - New driver for Digidesign 002/003 family
   - Adds support for TASCAM FireOne to ALSA OXFW driver
   - Add MIDI support to TASCAM and Digi00x devices

  HD-Audio:
   - Automated modalias generation for codec drivers, finally
   - Improvement on heuristics for setting mixer name
   - A few fixes for longstanding bugs on Creative CA0132 cards
   - Addition of audio rate callback with i915 communication
   - Fix suspend issue on recent Dell XPS
   - Intel Lewisburg controller support

  ASoC:
   - Updates to the topology userspace interface
   - Big updates to the Renesas support (rcar)
   - More updates for supporting Intel Sky Lake systems
   - New drivers for Asahi Kasei Microdevices AK4613, Allwinnner A10,
     Cirrus Logic WM8998, Dialog DA7219, Nuvoton NAU8825, Rockchip
     S/PDIF, and Atmel class D amplifier

  USB-Audio:
   - A fix for newer Roland MIDI devices
   - Quirks and workarounds for Zoom R16/24 device

  Misc:
   - A few fixes for some old Cirrus CS46xx PCI sound boards
   - Yet another fixes for some old ESS Maestro3 PCI sound boards"

* tag 'sound-4.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound: (330 commits)
  ALSA: hda - Add Intel Lewisburg device IDs Audio
  ALSA: hda - Apply pin fixup for HP ProBook 6550b
  ALSA: hda - Fix lost 4k BDL boundary workaround
  ALSA: maestro3: Fix Allegro mute until master volume/mute is touched
  ALSA: maestro3: Enable docking support for Dell Latitude C810
  ALSA: firewire-digi00x: add another rawmidi character device for MIDI control ports
  ALSA: firewire-digi00x: add MIDI operations for MIDI control port
  ALSA: firewire-digi00x: rename identifiers of MIDI operation for physical ports
  ALSA: cs46xx: Fix suspend for all channels
  ALSA: cs46xx: Fix Duplicate front for CS4294 and CS4298 codecs
  ALSA: DocBook: Add soc-ops.c and soc-compress.c
  ALSA: hda - Add / fix kernel doc comments
  ALSA: Constify ratden/ratnum constraints
  ALSA: hda - Disable 64bit address for Creative HDA controllers
  ALSA: hda/realtek - Dell XPS one ALC3260 speaker no sound after resume back
  ALSA: hda/ca0132 - Convert leftover pr_info() and pr_err()
  ASoC: fsl: Use #ifdef instead of #if for CONFIG_PM_SLEEP
  ASoC: rt5645: Sort the order for register bit defines
  ASoC: dwc: add check for master/slave format
  ASoC: rt5645: Add the HWEQ for the speaker output
  ...
This commit is contained in:
Linus Torvalds 2015-11-06 11:04:07 -08:00
commit 0280d1a099
307 changed files with 21226 additions and 3298 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 = <&reg_audio>;
VDDMIC-supply = <&reg_audio>;
VDDIO-supply = <&reg_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>;
};
};

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -3403,6 +3403,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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

55
include/sound/da7219.h Normal file
View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -197,5 +197,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;
}

View file

@ -1254,6 +1254,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)
{

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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,
&reg, 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,
&reg, 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,
&reg, 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,
&reg, 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,
&reg, 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);
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,3 +1,3 @@
snd-oxfw-objs := oxfw-command.o oxfw-stream.o oxfw-control.o oxfw-pcm.o \
oxfw-proc.o oxfw-midi.o oxfw-hwdep.o oxfw.o
obj-m += snd-oxfw.o
obj-$(CONFIG_SND_OXFW) += snd-oxfw.o

Some files were not shown because too many files have changed in this diff Show more