提交 221656e7 编写于 作者: L Linus Torvalds

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

Pull sound updates from Takashi Iwai:
 "It was a relatively calm development cycle, and no scaring changes are
  seen in both core and driver sides. Here are some highlights:

  ASoC:
   - A new API for hooking up jacks more generically and easily

   - Card longname is set based on DMI for a unique UCM profile

   - Lots of Intel driver fixes: Atom, Broxton, Skylake and newer chips

   - New drivers for Cirrus CS35L35, DIO DIO2125, Everest ES7132,
     HiSilicon hi6210, Maxim MAX98927, MT2701 systems with WM8960,
     Nuvoton NAU8824, Odroid systems, ST STM32 SAI controllers and x86
     systems with DA7213

  HD-audio:
   - Many new quirks to support headset for various devices (mostly ASUS
     ones) as usual

   - Support for dual codecs on some Gigabyte mobos and Lenovo laptop

   - Improvement on PCM position reporting for Skylake and newer

  FireWire:
   - New drivers for MOTU and RME Fireface series

   - Updates for Digidesign Digi00x and TASCAM series

   - Support for tracepoints

  Others:
   - USB-audio: improved support for quirk_alias option

   - Cleanups, constification allover the places"

* tag 'sound-4.12-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound: (299 commits)
  ASoC: codec: wm8960: Relax bit clock computation when using PLL
  ASoC: codec: wm9860: avoid maybe-uninitialized warning
  ASoC: nau8824: leave Class D gain at chip default
  ASoC: nau8824: rename controls to match DAPM controls
  ASoC: Intel: Skylake: Return negative error code
  ASoC: Intel: Skylake: Fix unused variable warning
  ASoC: Intel: Skylake: fix uninitialized pointer use
  ASoC: sti: Fix error handling if of_clk_get() fails
  ASoC: cs4271: configure reset GPIO as output
  ASoC: dwc: Disallow building designware_pcm as a module
  ALSA: ali5451: fix spelling mistake in "ali_capture_preapre"
  ASoC: stm32: add SAI driver
  ASoC: stm32: add bindings for SAI
  ASoC: Intel: Skylake: Add loadable module support on KBL platform
  ASoC: Intel: Skylake: Modify load_lib_ipc arguments for a nowait version
  ASoC: Intel: Skylake: Register dsp_fw_ops for kabylake
  ASoC: Intel: Skylake: Modify arguments to reuse module transfer function
  ASoC: Intel: Skylake: Commonize library load
  ASoC: Intel: Skylake: Move sst common initialization to a helper function
  ASoC: nau8824: new driver
  ...
CS35L35 Boosted Speaker Amplifier
Required properties:
- compatible : "cirrus,cs35l35"
- reg : the I2C address of the device for I2C
- VA-supply, VP-supply : power supplies for the device,
as covered in
Documentation/devicetree/bindings/regulator/regulator.txt.
- interrupt-parent : Specifies the phandle of the interrupt controller to
which the IRQs from CS35L35 are delivered to.
- interrupts : IRQ line info CS35L35.
(See Documentation/devicetree/bindings/interrupt-controller/interrupts.txt
for further information relating to interrupt properties)
Optional properties:
- reset-gpios : gpio used to reset the amplifier
- cirrus,stereo-config : Boolean to determine if there are 2 AMPs for a
Stereo configuration
- cirrus,audio-channel : Set Location of Audio Signal on Serial Port
0 = Data Packet received on Left I2S Channel
1 = Data Packet received on Right I2S Channel
- cirrus,advisory-channel : Set Location of Advisory Signal on Serial Port
0 = Data Packet received on Left I2S Channel
1 = Data Packet received on Right I2S Channel
- cirrus,shared-boost : Boolean to enable ClassH tracking of Advisory Signal
if 2 Devices share Boost BST_CTL
- cirrus,external-boost : Boolean to specify the device is using an external
boost supply, note that sharing a boost from another cs35l35 would constitute
using an external supply for the slave device
- cirrus,sp-drv-strength : Value for setting the Serial Port drive strength
Table 3-10 of the datasheet lists drive-strength specifications
0 = 1x (Default)
1 = .5x
- cirrus,sp-drv-unused : Determines how unused slots should be driven on the
Serial Port.
0 - Hi-Z
2 - Drive 0's (Default)
3 - Drive 1's
- cirrus,bst-pdn-fet-on : Boolean to determine if the Boost PDN control
powers down with a rectification FET On or Off. If VSPK is supplied
externally then FET is off.
- cirrus,boost-ctl-millivolt : Boost Voltage Value. Configures the boost
converter's output voltage in mV. The range is from 2600mV to 9000mV with
increments of 100mV.
(Default) VP
- cirrus,boost-peak-milliamp : Boost-converter peak current limit in mA.
Configures the peak current by monitoring the current through the boost FET.
Range starts at 1680mA and goes to a maximum of 4480mA with increments of
110mA.
(Default) 2.46 Amps
- cirrus,amp-gain-zc : Boolean to determine if to use Amplifier gain-change
zero-cross
Optional H/G Algorithm sub-node:
The cs35l35 node can have a single "cirrus,classh-internal-algo" sub-node
that will disable automatic control of the internal H/G Algorithm.
It is strongly recommended that the Datasheet be referenced when adjusting
or using these Class H Algorithm controls over the internal Algorithm.
Serious damage can occur to the Device and surrounding components.
- cirrus,classh-internal-algo : Sub-node for the Internal Class H Algorithm
See Section 4.3 Internal Class H Algorithm in the Datasheet.
If not used, the device manages the ClassH Algorithm internally.
Optional properties for the "cirrus,classh-internal-algo" Sub-node
Section 7.29 Class H Control
- cirrus,classh-bst-overide : Boolean
- cirrus,classh-bst-max-limit
- cirrus,classh-mem-depth
Section 7.30 Class H Headroom Control
- cirrus,classh-headroom
Section 7.31 Class H Release Rate
- cirrus,classh-release-rate
Section 7.32 Class H Weak FET Drive Control
- cirrus,classh-wk-fet-disable
- cirrus,classh-wk-fet-delay
- cirrus,classh-wk-fet-thld
Section 7.34 Class H VP Control
- cirrus,classh-vpch-auto
- cirrus,classh-vpch-rate
- cirrus,classh-vpch-man
Optional Monitor Signal Format sub-node:
The cs35l35 node can have a single "cirrus,monitor-signal-format" sub-node
for adjusting the Depth, Location and Frame of the Monitoring Signals
for Algorithms.
See Sections 4.8.2 through 4.8.4 Serial-Port Control in the Datasheet
-cirrus,monitor-signal-format : Sub-node for the Monitor Signaling Formating
on the I2S Port. Each of the 3 8 bit values in the array contain the settings
for depth, location, and frame.
If not used, the defaults for the 6 monitor signals is used.
Sections 7.44 - 7.53 lists values for the depth, location, and frame
for each monitoring signal.
- cirrus,imon : 4 8 bit values to set the depth, location, frame and ADC
scale of the IMON monitor signal.
- cirrus,vmon : 3 8 bit values to set the depth, location, and frame
of the VMON monitor signal.
- cirrus,vpmon : 3 8 bit values to set the depth, location, and frame
of the VPMON monitor signal.
- cirrus,vbstmon : 3 8 bit values to set the depth, location, and frame
of the VBSTMON monitor signal
- cirrus,vpbrstat : 3 8 bit values to set the depth, location, and frame
of the VPBRSTAT monitor signal
- cirrus,zerofill : 3 8 bit values to set the depth, location, and frame\
of the ZEROFILL packet in the monitor signal
Example:
cs35l35: cs35l35@20 {
compatible = "cirrus,cs35l35";
reg = <0x20>;
VA-supply = <&dummy_vreg>;
VP-supply = <&dummy_vreg>;
reset-gpios = <&axi_gpio 54 0>;
interrupt-parent = <&gpio8>;
interrupts = <3 IRQ_TYPE_LEVEL_LOW>;
cirrus,boost-ctl-millivolt = <9000>;
cirrus,stereo-config;
cirrus,audio-channel = <0x00>;
cirrus,advisory-channel = <0x01>;
cirrus,shared-boost;
cirrus,classh-internal-algo {
cirrus,classh-bst-overide;
cirrus,classh-bst-max-limit = <0x01>;
cirrus,classh-mem-depth = <0x01>;
cirrus,classh-release-rate = <0x08>;
cirrus,classh-headroom-millivolt = <0x0B>;
cirrus,classh-wk-fet-disable = <0x01>;
cirrus,classh-wk-fet-delay = <0x04>;
cirrus,classh-wk-fet-thld = <0x01>;
cirrus,classh-vpch-auto = <0x01>;
cirrus,classh-vpch-rate = <0x02>;
cirrus,classh-vpch-man = <0x05>;
};
/* Depth, Location, Frame */
cirrus,monitor-signal-format {
cirrus,imon = /bits/ 8 <0x03 0x00 0x01>;
cirrus,vmon = /bits/ 8 <0x03 0x00 0x00>;
cirrus,vpmon = /bits/ 8 <0x03 0x04 0x00>;
cirrus,vbstmon = /bits/ 8 <0x03 0x04 0x01>;
cirrus,vpbrstat = /bits/ 8 <0x00 0x04 0x00>;
cirrus,zerofill = /bits/ 8 <0x00 0x00 0x00>;
};
};
DIO2125 Audio Driver
Required properties:
- compatible : "dioo,dio2125"
- enable-gpios : the gpio connected to the enable pin of the dio2125
Example:
amp: analog-amplifier {
compatible = "dioo,dio2125";
enable-gpios = <&gpio GPIOH_3 0>;
};
ES7134 i2s DA converter
Required properties:
- compatible : "everest,es7134" or "everest,es7144"
Example:
i2s_codec: external-codec {
compatible = "everest,es7134";
};
...@@ -20,24 +20,8 @@ Required properties: ...@@ -20,24 +20,8 @@ Required properties:
have. have.
- interrupt-parent: The phandle for the interrupt controller that - interrupt-parent: The phandle for the interrupt controller that
services interrupts for this device. services interrupts for this device.
- fsl,playback-dma: Phandle to a node for the DMA channel to use for
playback of audio. This is typically dictated by SOC
design. See the notes below.
- fsl,capture-dma: Phandle to a node for the DMA channel to use for
capture (recording) of audio. This is typically dictated
by SOC design. See the notes below.
- fsl,fifo-depth: The number of elements in the transmit and receive FIFOs. - fsl,fifo-depth: The number of elements in the transmit and receive FIFOs.
This number is the maximum allowed value for SFCSR[TFWM0]. This number is the maximum allowed value for SFCSR[TFWM0].
- fsl,ssi-asynchronous:
If specified, the SSI is to be programmed in asynchronous
mode. In this mode, pins SRCK, STCK, SRFS, and STFS must
all be connected to valid signals. In synchronous mode,
SRCK and SRFS are ignored. Asynchronous mode allows
playback and capture to use different sample sizes and
sample rates. Some drivers may require that SRCK and STCK
be connected together, and SRFS and STFS be connected
together. This would still allow different sample sizes,
but not different sample rates.
- clocks: "ipg" - Required clock for the SSI unit - clocks: "ipg" - Required clock for the SSI unit
"baud" - Required clock for SSI master mode. Otherwise this "baud" - Required clock for SSI master mode. Otherwise this
clock is not used clock is not used
...@@ -61,6 +45,24 @@ Optional properties: ...@@ -61,6 +45,24 @@ Optional properties:
- fsl,mode: The operating mode for the AC97 interface only. - fsl,mode: The operating mode for the AC97 interface only.
"ac97-slave" - AC97 mode, SSI is clock slave "ac97-slave" - AC97 mode, SSI is clock slave
"ac97-master" - AC97 mode, SSI is clock master "ac97-master" - AC97 mode, SSI is clock master
- fsl,ssi-asynchronous:
If specified, the SSI is to be programmed in asynchronous
mode. In this mode, pins SRCK, STCK, SRFS, and STFS must
all be connected to valid signals. In synchronous mode,
SRCK and SRFS are ignored. Asynchronous mode allows
playback and capture to use different sample sizes and
sample rates. Some drivers may require that SRCK and STCK
be connected together, and SRFS and STFS be connected
together. This would still allow different sample sizes,
but not different sample rates.
- fsl,playback-dma: Phandle to a node for the DMA channel to use for
playback of audio. This is typically dictated by SOC
design. See the notes below.
Only used on Power Architecture.
- fsl,capture-dma: Phandle to a node for the DMA channel to use for
capture (recording) of audio. This is typically dictated
by SOC design. See the notes below.
Only used on Power Architecture.
Child 'codec' node required properties: Child 'codec' node required properties:
- compatible: Compatible list, contains the name of the codec - compatible: Compatible list, contains the name of the codec
......
* Hisilicon 6210 i2s controller
Required properties:
- compatible: should be one of the following:
- "hisilicon,hi6210-i2s"
- reg: physical base address of the i2s controller unit and length of
memory mapped region.
- interrupts: should contain the i2s interrupt.
- clocks: a list of phandle + clock-specifier pairs, one for each entry
in clock-names.
- clock-names: should contain following:
- "dacodec"
- "i2s-base"
- dmas: DMA specifiers for tx dma. See the DMA client binding,
Documentation/devicetree/bindings/dma/dma.txt
- dma-names: should be "tx" and "rx"
- hisilicon,sysctrl-syscon: phandle to sysctrl syscon
- #sound-dai-cells: Should be set to 1 (for multi-dai)
- The dai cell indexes reference the following interfaces:
0: S2 interface
(Currently that is the only one available, but more may be
supported in the future)
Example for the hi6210 i2s controller:
i2s0: i2s@f7118000{
compatible = "hisilicon,hi6210-i2s";
reg = <0x0 0xf7118000 0x0 0x8000>; /* i2s unit */
interrupts = <GIC_SPI 123 IRQ_TYPE_LEVEL_HIGH>; /* 155 "DigACodec_intr"-32 */
clocks = <&sys_ctrl HI6220_DACODEC_PCLK>,
<&sys_ctrl HI6220_BBPPLL0_DIV>;
clock-names = "dacodec", "i2s-base";
dmas = <&dma0 15 &dma0 14>;
dma-names = "rx", "tx";
hisilicon,sysctrl-syscon = <&sys_ctrl>;
#sound-dai-cells = <1>;
};
Then when referencing the i2s controller:
sound-dai = <&i2s0 0>; /* index 0 => S2 interface */
max98925 audio CODEC
This device supports I2C.
Required properties:
- compatible : "maxim,max98925"
- vmon-slot-no : slot number used to send voltage information
- imon-slot-no : slot number used to send current information
- reg : the I2C address of the device for I2C
Example:
codec: max98925@1a {
compatible = "maxim,max98925";
vmon-slot-no = <0>;
imon-slot-no = <2>;
reg = <0x1a>;
};
max98926 audio CODEC
This device supports I2C.
Required properties:
- compatible : "maxim,max98926"
- vmon-slot-no : slot number used to send voltage information
or in inteleave mode this will be used as
interleave slot.
- imon-slot-no : slot number used to send current information
- interleave-mode : When using two MAX98926 in a system it is
possible to create ADC data that that will
overflow the frame size. Digital Audio Interleave
mode provides a means to output VMON and IMON data
from two devices on a single DOUT line when running
smaller frames sizes such as 32 BCLKS per LRCLK or
48 BCLKS per LRCLK.
- reg : the I2C address of the device for I2C
Example:
codec: max98926@1a {
compatible = "maxim,max98926";
vmon-slot-no = <0>;
imon-slot-no = <2>;
reg = <0x1a>;
};
Maxim Integrated MAX98925/MAX98926/MAX98927 Speaker Amplifier
This device supports I2C.
Required properties:
- compatible : should be one of the following
- "maxim,max98925"
- "maxim,max98926"
- "maxim,max98927"
- vmon-slot-no : slot number used to send voltage information
or in inteleave mode this will be used as
interleave slot.
MAX98925/MAX98926 slot range : 0 ~ 30, Default : 0
MAX98927 slot range : 0 ~ 15, Default : 0
- imon-slot-no : slot number used to send current information
MAX98925/MAX98926 slot range : 0 ~ 30, Default : 0
MAX98927 slot range : 0 ~ 15, Default : 0
- interleave-mode : When using two MAX9892X in a system it is
possible to create ADC data that that will
overflow the frame size. Digital Audio Interleave
mode provides a means to output VMON and IMON data
from two devices on a single DOUT line when running
smaller frames sizes such as 32 BCLKS per LRCLK or
48 BCLKS per LRCLK.
Range : 0 (off), 1 (on), Default : 0
- reg : the I2C address of the device for I2C
Example:
codec: max98927@3a {
compatible = "maxim,max98927";
vmon-slot-no = <0>;
imon-slot-no = <1>;
interleave-mode = <0>;
reg = <0x3a>;
};
MT2701 with WM8960 CODEC
Required properties:
- compatible: "mediatek,mt2701-wm8960-machine"
- mediatek,platform: the phandle of MT2701 ASoC platform
- audio-routing: a list of the connections between audio
- mediatek,audio-codec: the phandles of wm8960 codec
- pinctrl-names: Should contain only one value - "default"
- pinctrl-0: Should specify pin control groups used for this controller.
Example:
sound:sound {
compatible = "mediatek,mt2701-wm8960-machine";
mediatek,platform = <&afe>;
audio-routing =
"Headphone", "HP_L",
"Headphone", "HP_R",
"LINPUT1", "AMIC",
"RINPUT1", "AMIC";
mediatek,audio-codec = <&wm8960>;
pinctrl-names = "default";
pinctrl-0 = <&aud_pins_default>;
};
Nuvoton NAU8824 audio codec
This device supports I2C only.
Required properties:
- compatible : Must be "nuvoton,nau8824"
- reg : the I2C address of the device. This is either 0x1a (CSB=0) or 0x1b (CSB=1).
Optional properties:
- 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
- nuvoton,jack-eject-debounce: Jack ejection debounce time.
0 - 0 ms
1 - 1 ms
2 - 10 ms
Example:
headset: nau8824@1a {
compatible = "nuvoton,nau8824";
reg = <0x1a>;
interrupt-parent = <&gpio>;
interrupts = <TEGRA_GPIO(E, 6) IRQ_TYPE_LEVEL_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 = <0>;
nuvoton,sar-voltage = <6>;
nuvoton,sar-compare-time = <1>;
nuvoton,sar-sampling-time = <1>;
nuvoton,short-key-debounce = <0>;
nuvoton,jack-eject-debounce = <1>;
};
...@@ -9,6 +9,7 @@ Required properties: ...@@ -9,6 +9,7 @@ Required properties:
- "rockchip,rk3066-i2s": for rk3066 - "rockchip,rk3066-i2s": for rk3066
- "rockchip,rk3188-i2s", "rockchip,rk3066-i2s": for rk3188 - "rockchip,rk3188-i2s", "rockchip,rk3066-i2s": for rk3188
- "rockchip,rk3288-i2s", "rockchip,rk3066-i2s": for rk3288 - "rockchip,rk3288-i2s", "rockchip,rk3066-i2s": for rk3288
- "rockchip,rk3368-i2s", "rockchip,rk3066-i2s": for rk3368
- "rockchip,rk3399-i2s", "rockchip,rk3066-i2s": for rk3399 - "rockchip,rk3399-i2s", "rockchip,rk3066-i2s": for rk3399
- reg: physical base address of the controller and length of memory mapped - reg: physical base address of the controller and length of memory mapped
region. region.
......
Samsung Exynos Odroid XU3/XU4 audio complex with MAX98090 codec
Required properties:
- compatible - "samsung,odroidxu3-audio" - for Odroid XU3 board,
"samsung,odroidxu4-audio" - for Odroid XU4 board
- model - the user-visible name of this sound complex
- 'cpu' subnode with a 'sound-dai' property containing the phandle of the I2S
controller
- 'codec' subnode with a 'sound-dai' property containing list of phandles
to the CODEC nodes, first entry must be corresponding to the MAX98090
CODEC and the second entry must be the phandle of the HDMI IP block node
- clocks - should contain entries matching clock names in the clock-names
property
- clock-names - should contain following entries:
- "epll" - indicating the EPLL output clock
- "i2s_rclk" - indicating the RCLK (root) clock of the I2S0 controller
- samsung,audio-widgets - this property specifies off-codec audio elements
like headphones or speakers, for details see widgets.txt
- samsung,audio-routing - a list of the connections between audio
components; each entry is a pair of strings, the first being the
connection's sink, the second being the connection's source;
valid names for sources and sinks are the MAX98090's pins (as
documented in its binding), and the jacks on the board
For Odroid X2:
"Headphone Jack", "Mic Jack", "DMIC"
For Odroid U3, XU3:
"Headphone Jack", "Speakers"
For Odroid XU4:
no entries
Example:
sound {
compatible = "samsung,odroidxu3-audio";
samsung,cpu-dai = <&i2s0>;
samsung,codec-dai = <&max98090>;
model = "Odroid-XU3";
samsung,audio-routing =
"Headphone Jack", "HPL",
"Headphone Jack", "HPR",
"IN1", "Mic Jack",
"Mic Jack", "MICBIAS";
clocks = <&clock CLK_FOUT_EPLL>, <&i2s0 CLK_I2S_RCLK_SRC>;
clock-names = "epll", "sclk_i2s";
cpu {
sound-dai = <&i2s0 0>;
};
codec {
sound-dai = <&hdmi>, <&max98090>;
};
};
...@@ -26,6 +26,15 @@ Optional properties: ...@@ -26,6 +26,15 @@ Optional properties:
If this node is not mentioned or the value is unknown, then If this node is not mentioned or the value is unknown, then
the value is set to 1.25V. the value is set to 1.25V.
- lrclk-strength: the LRCLK pad strength. Possible values are:
0, 1, 2 and 3 as per the table below:
VDDIO 1.8V 2.5V 3.3V
0 = Disable
1 = 1.66 mA 2.87 mA 4.02 mA
2 = 3.33 mA 5.74 mA 8.03 mA
3 = 4.99 mA 8.61 mA 12.05 mA
Example: Example:
codec: sgtl5000@0a { codec: sgtl5000@0a {
......
STMicroelectronics STM32 Serial Audio Interface (SAI).
The SAI interface (Serial Audio Interface) offers a wide set of audio protocols
as I2S standards, LSB or MSB-justified, PCM/DSP, TDM, and AC'97.
The SAI contains two independent audio sub-blocks. Each sub-block has
its own clock generator and I/O lines controller.
Required properties:
- compatible: Should be "st,stm32f4-sai"
- reg: Base address and size of SAI common register set.
- clocks: Must contain phandle and clock specifier pairs for each entry
in clock-names.
- clock-names: Must contain "x8k" and "x11k"
"x8k": SAI parent clock for sampling rates multiple of 8kHz.
"x11k": SAI parent clock for sampling rates multiple of 11.025kHz.
- interrupts: cpu DAI interrupt line shared by SAI sub-blocks
Optional properties:
- resets: Reference to a reset controller asserting the SAI
SAI subnodes:
Two subnodes corresponding to SAI sub-block instances A et B can be defined.
Subnode can be omitted for unsused sub-block.
SAI subnodes required properties:
- compatible: Should be "st,stm32-sai-sub-a" or "st,stm32-sai-sub-b"
for SAI sub-block A or B respectively.
- reg: Base address and size of SAI sub-block register set.
- clocks: Must contain one phandle and clock specifier pair
for sai_ck which feeds the internal clock generator.
- clock-names: Must contain "sai_ck".
- dmas: see Documentation/devicetree/bindings/dma/stm32-dma.txt
- dma-names: identifier string for each DMA request line
"tx": if sai sub-block is configured as playback DAI
"rx": if sai sub-block is configured as capture DAI
- pinctrl-names: should contain only value "default"
- pinctrl-0: see Documentation/devicetree/bindings/pinctrl/pinctrl-stm32.txt
Example:
sound_card {
compatible = "audio-graph-card";
dais = <&sai1b_port>;
};
sai1: sai1@40015800 {
compatible = "st,stm32f4-sai";
#address-cells = <1>;
#size-cells = <1>;
ranges;
reg = <0x40015800 0x4>;
clocks = <&rcc 1 CLK_SAIQ_PDIV>, <&rcc 1 CLK_I2SQ_PDIV>;
clock-names = "x8k", "x11k";
interrupts = <87>;
sai1b: audio-controller@40015824 {
#sound-dai-cells = <0>;
compatible = "st,stm32-sai-sub-b";
reg = <0x40015824 0x1C>;
clocks = <&rcc 1 CLK_SAI2>;
clock-names = "sai_ck";
dmas = <&dma2 5 0 0x400 0x0>;
dma-names = "tx";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_sai1b>;
ports {
#address-cells = <1>;
#size-cells = <0>;
sai1b_port: port@0 {
reg = <0>;
cpu_endpoint: endpoint {
remote-endpoint = <&codec_endpoint>;
audio-graph-card,format = "i2s";
audio-graph-card,bitclock-master = <&codec_endpoint>;
audio-graph-card,frame-master = <&codec_endpoint>;
};
};
};
};
};
audio-codec {
codec_port: port {
codec_endpoint: endpoint {
remote-endpoint = <&cpu_endpoint>;
};
};
};
...@@ -5,7 +5,8 @@ The tas2552 serial control bus communicates through I2C protocols ...@@ -5,7 +5,8 @@ The tas2552 serial control bus communicates through I2C protocols
Required properties: Required properties:
- compatible - One of: - compatible - One of:
"ti,tas2552" - TAS2552 "ti,tas2552" - TAS2552
- reg - I2C slave address - reg - I2C slave address: it can be 0x40 if ADDR pin is 0
or 0x41 if ADDR pin is 1.
- supply-*: Required supply regulators are: - supply-*: Required supply regulators are:
"vbat" battery voltage "vbat" battery voltage
"iovdd" I/O Voltage "iovdd" I/O Voltage
...@@ -14,17 +15,20 @@ Required properties: ...@@ -14,17 +15,20 @@ Required properties:
Optional properties: Optional properties:
- enable-gpio - gpio pin to enable/disable the device - enable-gpio - gpio pin to enable/disable the device
tas2552 can receive it's reference clock via MCLK, BCLK, IVCLKIN pin or use the tas2552 can receive its reference clock via MCLK, BCLK, IVCLKIN pin or use the
internal 1.8MHz. This CLKIN is used by the PLL. In addition to PLL, the PDM internal 1.8MHz. This CLKIN is used by the PLL. In addition to PLL, the PDM
reference clock is also selectable: PLL, IVCLKIN, BCLK or MCLK. reference clock is also selectable: PLL, IVCLKIN, BCLK or MCLK.
For system integration the dt-bindings/sound/tas2552.h header file provides For system integration the dt-bindings/sound/tas2552.h header file provides
defined values to selct and configure the PLL and PDM reference clocks. defined values to select and configure the PLL and PDM reference clocks.
Example: Example:
tas2552: tas2552@41 { tas2552: tas2552@41 {
compatible = "ti,tas2552"; compatible = "ti,tas2552";
reg = <0x41>; reg = <0x41>;
vbat-supply = <&reg_vbat>;
iovdd-supply = <&reg_iovdd>;
avdd-supply = <&reg_avdd>;
enable-gpio = <&gpio4 2 GPIO_ACTIVE_HIGH>; enable-gpio = <&gpio4 2 GPIO_ACTIVE_HIGH>;
}; };
......
...@@ -28,6 +28,14 @@ Optional properties: ...@@ -28,6 +28,14 @@ Optional properties:
performed. If any entry has the value 0xffffffff, that GPIO's performed. If any entry has the value 0xffffffff, that GPIO's
configuration will not be modified. configuration will not be modified.
- AVDD-supply : Analog power supply regulator on the AVDD pin.
- CPVDD-supply : Charge pump supply regulator on the CPVDD pin.
- DBVDD-supply : Digital buffer supply regulator for the DBVDD pin.
- DCVDD-supply : Digital core supply regulator for the DCVDD pin.
Pins on the device (for linking into audio routes): Pins on the device (for linking into audio routes):
* IN1L * IN1L
...@@ -54,6 +62,11 @@ codec: wm8903@1a { ...@@ -54,6 +62,11 @@ codec: wm8903@1a {
reg = <0x1a>; reg = <0x1a>;
interrupts = < 347 >; interrupts = < 347 >;
AVDD-supply = <&fooreg_a>;
CPVDD-supply = <&fooreg_b>;
DBVDD-supply = <&fooreg_c>;
DCVDC-supply = <&fooreg_d>;
gpio-controller; gpio-controller;
#gpio-cells = <2>; #gpio-cells = <2>;
......
ZTE TDM DAI driver
Required properties:
- compatible : should be one of the following.
* zte,zx296718-tdm
- reg : physical base address of the controller and length of memory mapped
region.
- clocks : Pairs of phandle and specifier referencing the controller's clocks.
- clock-names: "wclk" for the wclk.
"pclk" for the pclk.
-#clock-cells: should be 1.
- zte,tdm-dma-sysctrl : Reference to the sysctrl controller controlling
the dma. includes:
phandle of sysctrl.
register offset in sysctrl for control dma.
mask of the register that be written to sysctrl.
Example:
tdm: tdm@1487000 {
compatible = "zte,zx296718-tdm";
reg = <0x01487000 0x1000>;
clocks = <&audiocrm AUDIO_TDM_WCLK>, <&audiocrm AUDIO_TDM_PCLK>;
clock-names = "wclk", "pclk";
#clock-cells = <1>;
pinctrl-names = "default";
pinctrl-0 = <&tdm_global_pin>;
zte,tdm-dma-sysctrl = <&sysctrl 0x10c 4>;
};
...@@ -494,6 +494,8 @@ add_hp_mic (bool) ...@@ -494,6 +494,8 @@ add_hp_mic (bool)
hp_mic_detect (bool) hp_mic_detect (bool)
enable/disable the hp/mic shared input for a single built-in mic enable/disable the hp/mic shared input for a single built-in mic
case; default true case; default true
vmaster (bool)
enable/disable the virtual Master control; default true
mixer_nid (int) mixer_nid (int)
specifies the widget NID of the analog-loopback mixer specifies the widget NID of the analog-loopback mixer
......
/*
* linux/sound/cs35l35.h -- Platform data for CS35l35
*
* Copyright (c) 2016 Cirrus Logic Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef __CS35L35_H
#define __CS35L35_H
struct classh_cfg {
/*
* Class H Algorithm Control Variables
* You can either have it done
* automatically or you can adjust
* these variables for tuning
*
* if you do not enable the internal algorithm
* you will get a set of mixer controls for
* Class H tuning
*
* Section 4.3 of the datasheet
*/
bool classh_bst_override;
bool classh_algo_enable;
int classh_bst_max_limit;
int classh_mem_depth;
int classh_release_rate;
int classh_headroom;
int classh_wk_fet_disable;
int classh_wk_fet_delay;
int classh_wk_fet_thld;
int classh_vpch_auto;
int classh_vpch_rate;
int classh_vpch_man;
};
struct monitor_cfg {
/*
* Signal Monitor Data
* highly configurable signal monitoring
* data positioning and different types of
* monitoring data.
*
* Section 4.8.2 - 4.8.4 of the datasheet
*/
bool is_present;
bool imon_specs;
bool vmon_specs;
bool vpmon_specs;
bool vbstmon_specs;
bool vpbrstat_specs;
bool zerofill_specs;
u8 imon_dpth;
u8 imon_loc;
u8 imon_frm;
u8 imon_scale;
u8 vmon_dpth;
u8 vmon_loc;
u8 vmon_frm;
u8 vpmon_dpth;
u8 vpmon_loc;
u8 vpmon_frm;
u8 vbstmon_dpth;
u8 vbstmon_loc;
u8 vbstmon_frm;
u8 vpbrstat_dpth;
u8 vpbrstat_loc;
u8 vpbrstat_frm;
u8 zerofill_dpth;
u8 zerofill_loc;
u8 zerofill_frm;
};
struct cs35l35_platform_data {
/* Stereo (2 Device) */
bool stereo;
/* serial port drive strength */
int sp_drv_str;
/* serial port drive in unused slots */
int sp_drv_unused;
/* Boost Power Down with FET */
bool bst_pdn_fet_on;
/* Boost Voltage : used if ClassH Algo Enabled */
int bst_vctl;
/* Boost Converter Peak Current CTRL */
int bst_ipk;
/* Amp Gain Zero Cross */
bool gain_zc;
/* Audio Input Location */
int aud_channel;
/* Advisory Input Location */
int adv_channel;
/* Shared Boost for stereo */
bool shared_bst;
/* Specifies this amp is using an external boost supply */
bool ext_bst;
/* ClassH Algorithm */
struct classh_cfg classh_algo;
/* Monitor Config */
struct monitor_cfg mon_cfg;
};
#endif /* __CS35L35_H */
...@@ -106,8 +106,26 @@ enum { SDI0, SDI1, SDI2, SDI3, SDO0, SDO1, SDO2, SDO3 }; ...@@ -106,8 +106,26 @@ enum { SDI0, SDI1, SDI2, SDI3, SDO0, SDO1, SDO2, SDO3 };
#define AZX_REG_HSW_EM4 0x100c #define AZX_REG_HSW_EM4 0x100c
#define AZX_REG_HSW_EM5 0x1010 #define AZX_REG_HSW_EM5 0x1010
/* Skylake/Broxton display HD-A controller Extended Mode registers */ /* Skylake/Broxton vendor-specific registers */
#define AZX_REG_SKL_EM4L 0x1040 #define AZX_REG_VS_EM1 0x1000
#define AZX_REG_VS_INRC 0x1004
#define AZX_REG_VS_OUTRC 0x1008
#define AZX_REG_VS_FIFOTRK 0x100C
#define AZX_REG_VS_FIFOTRK2 0x1010
#define AZX_REG_VS_EM2 0x1030
#define AZX_REG_VS_EM3L 0x1038
#define AZX_REG_VS_EM3U 0x103C
#define AZX_REG_VS_EM4L 0x1040
#define AZX_REG_VS_EM4U 0x1044
#define AZX_REG_VS_LTRC 0x1048
#define AZX_REG_VS_D0I3C 0x104A
#define AZX_REG_VS_PCE 0x104B
#define AZX_REG_VS_L2MAGC 0x1050
#define AZX_REG_VS_L2LAHPT 0x1054
#define AZX_REG_VS_SDXDPIB_XBASE 0x1084
#define AZX_REG_VS_SDXDPIB_XINTERVAL 0x20
#define AZX_REG_VS_SDXEFIFOS_XBASE 0x1094
#define AZX_REG_VS_SDXEFIFOS_XINTERVAL 0x20
/* PCI space */ /* PCI space */
#define AZX_PCIREG_TCSEL 0x44 #define AZX_PCIREG_TCSEL 0x44
...@@ -243,9 +261,11 @@ enum { SDI0, SDI1, SDI2, SDI3, SDO0, SDO1, SDO2, SDO3 }; ...@@ -243,9 +261,11 @@ enum { SDI0, SDI1, SDI2, SDI3, SDO0, SDO1, SDO2, SDO3 };
#define AZX_REG_ML_LOUTPAY 0x20 #define AZX_REG_ML_LOUTPAY 0x20
#define AZX_REG_ML_LINPAY 0x30 #define AZX_REG_ML_LINPAY 0x30
#define AZX_MLCTL_SPA (1<<16) #define ML_LCTL_SCF_MASK 0xF
#define AZX_MLCTL_CPA 23 #define AZX_MLCTL_SPA (0x1 << 16)
#define AZX_MLCTL_CPA (0x1 << 23)
#define AZX_MLCTL_SPA_SHIFT 16
#define AZX_MLCTL_CPA_SHIFT 23
/* registers for DMA Resume Capability Structure */ /* registers for DMA Resume Capability Structure */
#define AZX_DRSM_CAP_ID 0x5 #define AZX_DRSM_CAP_ID 0x5
......
...@@ -368,24 +368,32 @@ void snd_hdac_bus_free_stream_pages(struct hdac_bus *bus); ...@@ -368,24 +368,32 @@ void snd_hdac_bus_free_stream_pages(struct hdac_bus *bus);
/* /*
* macros for easy use * macros for easy use
*/ */
#define _snd_hdac_chip_write(type, chip, reg, value) \ #define _snd_hdac_chip_writeb(chip, reg, value) \
((chip)->io_ops->reg_write ## type(value, (chip)->remap_addr + (reg))) ((chip)->io_ops->reg_writeb(value, (chip)->remap_addr + (reg)))
#define _snd_hdac_chip_read(type, chip, reg) \ #define _snd_hdac_chip_readb(chip, reg) \
((chip)->io_ops->reg_read ## type((chip)->remap_addr + (reg))) ((chip)->io_ops->reg_readb((chip)->remap_addr + (reg)))
#define _snd_hdac_chip_writew(chip, reg, value) \
((chip)->io_ops->reg_writew(value, (chip)->remap_addr + (reg)))
#define _snd_hdac_chip_readw(chip, reg) \
((chip)->io_ops->reg_readw((chip)->remap_addr + (reg)))
#define _snd_hdac_chip_writel(chip, reg, value) \
((chip)->io_ops->reg_writel(value, (chip)->remap_addr + (reg)))
#define _snd_hdac_chip_readl(chip, reg) \
((chip)->io_ops->reg_readl((chip)->remap_addr + (reg)))
/* read/write a register, pass without AZX_REG_ prefix */ /* read/write a register, pass without AZX_REG_ prefix */
#define snd_hdac_chip_writel(chip, reg, value) \ #define snd_hdac_chip_writel(chip, reg, value) \
_snd_hdac_chip_write(l, chip, AZX_REG_ ## reg, value) _snd_hdac_chip_writel(chip, AZX_REG_ ## reg, value)
#define snd_hdac_chip_writew(chip, reg, value) \ #define snd_hdac_chip_writew(chip, reg, value) \
_snd_hdac_chip_write(w, chip, AZX_REG_ ## reg, value) _snd_hdac_chip_writew(chip, AZX_REG_ ## reg, value)
#define snd_hdac_chip_writeb(chip, reg, value) \ #define snd_hdac_chip_writeb(chip, reg, value) \
_snd_hdac_chip_write(b, chip, AZX_REG_ ## reg, value) _snd_hdac_chip_writeb(chip, AZX_REG_ ## reg, value)
#define snd_hdac_chip_readl(chip, reg) \ #define snd_hdac_chip_readl(chip, reg) \
_snd_hdac_chip_read(l, chip, AZX_REG_ ## reg) _snd_hdac_chip_readl(chip, AZX_REG_ ## reg)
#define snd_hdac_chip_readw(chip, reg) \ #define snd_hdac_chip_readw(chip, reg) \
_snd_hdac_chip_read(w, chip, AZX_REG_ ## reg) _snd_hdac_chip_readw(chip, AZX_REG_ ## reg)
#define snd_hdac_chip_readb(chip, reg) \ #define snd_hdac_chip_readb(chip, reg) \
_snd_hdac_chip_read(b, chip, AZX_REG_ ## reg) _snd_hdac_chip_readb(chip, AZX_REG_ ## reg)
/* update a register, pass without AZX_REG_ prefix */ /* update a register, pass without AZX_REG_ prefix */
#define snd_hdac_chip_updatel(chip, reg, mask, val) \ #define snd_hdac_chip_updatel(chip, reg, mask, val) \
......
...@@ -434,6 +434,8 @@ int snd_soc_codec_set_sysclk(struct snd_soc_codec *codec, int clk_id, ...@@ -434,6 +434,8 @@ int snd_soc_codec_set_sysclk(struct snd_soc_codec *codec, int clk_id,
int source, unsigned int freq, int dir); int source, unsigned int freq, int dir);
int snd_soc_codec_set_pll(struct snd_soc_codec *codec, int pll_id, int source, int snd_soc_codec_set_pll(struct snd_soc_codec *codec, int pll_id, int source,
unsigned int freq_in, unsigned int freq_out); unsigned int freq_in, unsigned int freq_out);
int snd_soc_codec_set_jack(struct snd_soc_codec *codec,
struct snd_soc_jack *jack, void *data);
int snd_soc_register_card(struct snd_soc_card *card); int snd_soc_register_card(struct snd_soc_card *card);
int snd_soc_unregister_card(struct snd_soc_card *card); int snd_soc_unregister_card(struct snd_soc_card *card);
...@@ -497,7 +499,15 @@ void snd_soc_runtime_deactivate(struct snd_soc_pcm_runtime *rtd, int stream); ...@@ -497,7 +499,15 @@ void snd_soc_runtime_deactivate(struct snd_soc_pcm_runtime *rtd, int stream);
int snd_soc_runtime_set_dai_fmt(struct snd_soc_pcm_runtime *rtd, int snd_soc_runtime_set_dai_fmt(struct snd_soc_pcm_runtime *rtd,
unsigned int dai_fmt); unsigned int dai_fmt);
#ifdef CONFIG_DMI
int snd_soc_set_dmi_name(struct snd_soc_card *card, const char *flavour); int snd_soc_set_dmi_name(struct snd_soc_card *card, const char *flavour);
#else
static inline int snd_soc_set_dmi_name(struct snd_soc_card *card,
const char *flavour)
{
return 0;
}
#endif
/* Utility functions to get clock rates from various things */ /* Utility functions to get clock rates from various things */
int snd_soc_calc_frame_size(int sample_size, int channels, int tdm_slots); int snd_soc_calc_frame_size(int sample_size, int channels, int tdm_slots);
...@@ -721,6 +731,7 @@ struct snd_soc_jack_gpio { ...@@ -721,6 +731,7 @@ struct snd_soc_jack_gpio {
/* private: */ /* private: */
struct snd_soc_jack *jack; struct snd_soc_jack *jack;
struct delayed_work work; struct delayed_work work;
struct notifier_block pm_notifier;
struct gpio_desc *desc; struct gpio_desc *desc;
void *data; void *data;
...@@ -812,7 +823,6 @@ struct snd_soc_component { ...@@ -812,7 +823,6 @@ struct snd_soc_component {
unsigned int ignore_pmdown_time:1; /* pmdown_time is ignored at stop */ unsigned int ignore_pmdown_time:1; /* pmdown_time is ignored at stop */
unsigned int registered_as_component:1; unsigned int registered_as_component:1;
unsigned int auxiliary:1; /* for auxiliary component of the card */
unsigned int suspended:1; /* is in suspend PM state */ unsigned int suspended:1; /* is in suspend PM state */
struct list_head list; struct list_head list;
...@@ -913,6 +923,8 @@ struct snd_soc_codec_driver { ...@@ -913,6 +923,8 @@ struct snd_soc_codec_driver {
int clk_id, int source, unsigned int freq, int dir); int clk_id, int source, unsigned int freq, int dir);
int (*set_pll)(struct snd_soc_codec *codec, int pll_id, int source, int (*set_pll)(struct snd_soc_codec *codec, int pll_id, int source,
unsigned int freq_in, unsigned int freq_out); unsigned int freq_in, unsigned int freq_out);
int (*set_jack)(struct snd_soc_codec *codec,
struct snd_soc_jack *jack, void *data);
/* codec IO */ /* codec IO */
struct regmap *(*get_regmap)(struct device *); struct regmap *(*get_regmap)(struct device *);
......
...@@ -107,9 +107,11 @@ enum { ...@@ -107,9 +107,11 @@ enum {
SNDRV_HWDEP_IFACE_FW_DIGI00X, /* Digidesign Digi 002/003 family */ SNDRV_HWDEP_IFACE_FW_DIGI00X, /* Digidesign Digi 002/003 family */
SNDRV_HWDEP_IFACE_FW_TASCAM, /* TASCAM FireWire series */ SNDRV_HWDEP_IFACE_FW_TASCAM, /* TASCAM FireWire series */
SNDRV_HWDEP_IFACE_LINE6, /* Line6 USB processors */ SNDRV_HWDEP_IFACE_LINE6, /* Line6 USB processors */
SNDRV_HWDEP_IFACE_FW_MOTU, /* MOTU FireWire series */
SNDRV_HWDEP_IFACE_FW_FIREFACE, /* RME Fireface series */
/* Don't forget to change the following: */ /* Don't forget to change the following: */
SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_LINE6 SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_FIREFACE
}; };
struct snd_hwdep_info { struct snd_hwdep_info {
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#define SNDRV_FIREWIRE_EVENT_DICE_NOTIFICATION 0xd1ce004e #define SNDRV_FIREWIRE_EVENT_DICE_NOTIFICATION 0xd1ce004e
#define SNDRV_FIREWIRE_EVENT_EFW_RESPONSE 0x4e617475 #define SNDRV_FIREWIRE_EVENT_EFW_RESPONSE 0x4e617475
#define SNDRV_FIREWIRE_EVENT_DIGI00X_MESSAGE 0x746e736c #define SNDRV_FIREWIRE_EVENT_DIGI00X_MESSAGE 0x746e736c
#define SNDRV_FIREWIRE_EVENT_MOTU_NOTIFICATION 0x64776479
struct snd_firewire_event_common { struct snd_firewire_event_common {
unsigned int type; /* SNDRV_FIREWIRE_EVENT_xxx */ unsigned int type; /* SNDRV_FIREWIRE_EVENT_xxx */
...@@ -46,12 +47,18 @@ struct snd_firewire_event_digi00x_message { ...@@ -46,12 +47,18 @@ struct snd_firewire_event_digi00x_message {
__u32 message; /* Digi00x-specific message */ __u32 message; /* Digi00x-specific message */
}; };
struct snd_firewire_event_motu_notification {
unsigned int type;
__u32 message; /* MOTU-specific bits. */
};
union snd_firewire_event { union snd_firewire_event {
struct snd_firewire_event_common common; struct snd_firewire_event_common common;
struct snd_firewire_event_lock_status lock_status; struct snd_firewire_event_lock_status lock_status;
struct snd_firewire_event_dice_notification dice_notification; struct snd_firewire_event_dice_notification dice_notification;
struct snd_firewire_event_efw_response efw_response; struct snd_firewire_event_efw_response efw_response;
struct snd_firewire_event_digi00x_message digi00x_message; struct snd_firewire_event_digi00x_message digi00x_message;
struct snd_firewire_event_motu_notification motu_notification;
}; };
...@@ -65,7 +72,8 @@ union snd_firewire_event { ...@@ -65,7 +72,8 @@ union snd_firewire_event {
#define SNDRV_FIREWIRE_TYPE_OXFW 4 #define SNDRV_FIREWIRE_TYPE_OXFW 4
#define SNDRV_FIREWIRE_TYPE_DIGI00X 5 #define SNDRV_FIREWIRE_TYPE_DIGI00X 5
#define SNDRV_FIREWIRE_TYPE_TASCAM 6 #define SNDRV_FIREWIRE_TYPE_TASCAM 6
/* RME, MOTU, ... */ #define SNDRV_FIREWIRE_TYPE_MOTU 7
#define SNDRV_FIREWIRE_TYPE_FIREFACE 8
struct snd_firewire_get_info { struct snd_firewire_get_info {
unsigned int type; /* SNDRV_FIREWIRE_TYPE_xxx */ unsigned int type; /* SNDRV_FIREWIRE_TYPE_xxx */
......
...@@ -1277,6 +1277,7 @@ static void snd_timer_user_tinterrupt(struct snd_timer_instance *timeri, ...@@ -1277,6 +1277,7 @@ static void snd_timer_user_tinterrupt(struct snd_timer_instance *timeri,
struct timespec tstamp; struct timespec tstamp;
int prev, append = 0; int prev, append = 0;
memset(&r1, 0, sizeof(r1));
memset(&tstamp, 0, sizeof(tstamp)); memset(&tstamp, 0, sizeof(tstamp));
spin_lock(&tu->qlock); spin_lock(&tu->qlock);
if ((tu->filter & ((1 << SNDRV_TIMER_EVENT_RESOLUTION) | if ((tu->filter & ((1 << SNDRV_TIMER_EVENT_RESOLUTION) |
...@@ -1292,7 +1293,6 @@ static void snd_timer_user_tinterrupt(struct snd_timer_instance *timeri, ...@@ -1292,7 +1293,6 @@ static void snd_timer_user_tinterrupt(struct snd_timer_instance *timeri,
} }
if ((tu->filter & (1 << SNDRV_TIMER_EVENT_RESOLUTION)) && if ((tu->filter & (1 << SNDRV_TIMER_EVENT_RESOLUTION)) &&
tu->last_resolution != resolution) { tu->last_resolution != resolution) {
memset(&r1, 0, sizeof(r1));
r1.event = SNDRV_TIMER_EVENT_RESOLUTION; r1.event = SNDRV_TIMER_EVENT_RESOLUTION;
r1.tstamp = tstamp; r1.tstamp = tstamp;
r1.val = resolution; r1.val = resolution;
...@@ -1430,18 +1430,13 @@ static int snd_timer_user_next_device(struct snd_timer_id __user *_tid) ...@@ -1430,18 +1430,13 @@ static int snd_timer_user_next_device(struct snd_timer_id __user *_tid)
if (id.card < 0) { if (id.card < 0) {
id.card = 0; id.card = 0;
} else { } else {
if (id.card < 0) { if (id.device < 0) {
id.card = 0; id.device = 0;
} else { } else {
if (id.device < 0) { if (id.subdevice < 0)
id.device = 0; id.subdevice = 0;
} else { else
if (id.subdevice < 0) { id.subdevice++;
id.subdevice = 0;
} else {
id.subdevice++;
}
}
} }
} }
list_for_each(p, &snd_timer_list) { list_for_each(p, &snd_timer_list) {
......
...@@ -795,10 +795,8 @@ struct vx_core *snd_vx_create(struct snd_card *card, struct snd_vx_hardware *hw, ...@@ -795,10 +795,8 @@ struct vx_core *snd_vx_create(struct snd_card *card, struct snd_vx_hardware *hw,
return NULL; return NULL;
chip = kzalloc(sizeof(*chip) + extra_size, GFP_KERNEL); chip = kzalloc(sizeof(*chip) + extra_size, GFP_KERNEL);
if (! chip) { if (! chip)
snd_printk(KERN_ERR "vx_core: no memory\n");
return NULL; return NULL;
}
mutex_init(&chip->lock); mutex_init(&chip->lock);
chip->irq = -1; chip->irq = -1;
chip->hw = hw; chip->hw = hw;
......
...@@ -140,4 +140,24 @@ config SND_FIREWIRE_TASCAM ...@@ -140,4 +140,24 @@ config SND_FIREWIRE_TASCAM
To compile this driver as a module, choose M here: the module To compile this driver as a module, choose M here: the module
will be called snd-firewire-tascam. will be called snd-firewire-tascam.
config SND_FIREWIRE_MOTU
tristate "Mark of the unicorn FireWire series support"
select SND_FIREWIRE_LIB
select SND_HWDEP
help
Say Y here to enable support for FireWire devices which MOTU produced:
* 828mk2
* 828mk3
To compile this driver as a module, choose M here: the module
will be called snd-firewire-motu.
config SND_FIREFACE
tristate "RME Fireface series support"
select SND_FIREWIRE_LIB
select SND_HWDEP
help
Say Y here to include support for RME fireface series.
* Fireface 400
endif # SND_FIREWIRE endif # SND_FIREWIRE
...@@ -13,3 +13,5 @@ obj-$(CONFIG_SND_FIREWORKS) += fireworks/ ...@@ -13,3 +13,5 @@ obj-$(CONFIG_SND_FIREWORKS) += fireworks/
obj-$(CONFIG_SND_BEBOB) += bebob/ obj-$(CONFIG_SND_BEBOB) += bebob/
obj-$(CONFIG_SND_FIREWIRE_DIGI00X) += digi00x/ obj-$(CONFIG_SND_FIREWIRE_DIGI00X) += digi00x/
obj-$(CONFIG_SND_FIREWIRE_TASCAM) += tascam/ obj-$(CONFIG_SND_FIREWIRE_TASCAM) += tascam/
obj-$(CONFIG_SND_FIREWIRE_MOTU) += motu/
obj-$(CONFIG_SND_FIREFACE) += fireface/
...@@ -14,8 +14,8 @@ ...@@ -14,8 +14,8 @@
#include <linux/tracepoint.h> #include <linux/tracepoint.h>
TRACE_EVENT(in_packet, TRACE_EVENT(in_packet,
TP_PROTO(const struct amdtp_stream *s, u32 cycles, u32 cip_header[2], unsigned int payload_quadlets, unsigned int index), TP_PROTO(const struct amdtp_stream *s, u32 cycles, u32 cip_header[2], unsigned int payload_length, unsigned int index),
TP_ARGS(s, cycles, cip_header, payload_quadlets, index), TP_ARGS(s, cycles, cip_header, payload_length, index),
TP_STRUCT__entry( TP_STRUCT__entry(
__field(unsigned int, second) __field(unsigned int, second)
__field(unsigned int, cycle) __field(unsigned int, cycle)
...@@ -37,7 +37,7 @@ TRACE_EVENT(in_packet, ...@@ -37,7 +37,7 @@ TRACE_EVENT(in_packet,
__entry->dest = fw_parent_device(s->unit)->card->node_id; __entry->dest = fw_parent_device(s->unit)->card->node_id;
__entry->cip_header0 = cip_header[0]; __entry->cip_header0 = cip_header[0];
__entry->cip_header1 = cip_header[1]; __entry->cip_header1 = cip_header[1];
__entry->payload_quadlets = payload_quadlets; __entry->payload_quadlets = payload_length / 4;
__entry->packet_index = s->packet_index; __entry->packet_index = s->packet_index;
__entry->irq = !!in_interrupt(); __entry->irq = !!in_interrupt();
__entry->index = index; __entry->index = index;
...@@ -101,6 +101,94 @@ TRACE_EVENT(out_packet, ...@@ -101,6 +101,94 @@ TRACE_EVENT(out_packet,
__entry->index) __entry->index)
); );
TRACE_EVENT(in_packet_without_header,
TP_PROTO(const struct amdtp_stream *s, u32 cycles, unsigned int payload_quadlets, unsigned int data_blocks, unsigned int index),
TP_ARGS(s, cycles, payload_quadlets, data_blocks, index),
TP_STRUCT__entry(
__field(unsigned int, second)
__field(unsigned int, cycle)
__field(int, channel)
__field(int, src)
__field(int, dest)
__field(unsigned int, payload_quadlets)
__field(unsigned int, data_blocks)
__field(unsigned int, data_block_counter)
__field(unsigned int, packet_index)
__field(unsigned int, irq)
__field(unsigned int, index)
),
TP_fast_assign(
__entry->second = cycles / CYCLES_PER_SECOND;
__entry->cycle = cycles % CYCLES_PER_SECOND;
__entry->channel = s->context->channel;
__entry->src = fw_parent_device(s->unit)->node_id;
__entry->dest = fw_parent_device(s->unit)->card->node_id;
__entry->payload_quadlets = payload_quadlets;
__entry->data_blocks = data_blocks,
__entry->data_block_counter = s->data_block_counter,
__entry->packet_index = s->packet_index;
__entry->irq = !!in_interrupt();
__entry->index = index;
),
TP_printk(
"%02u %04u %04x %04x %02d %03u %3u %3u %02u %01u %02u",
__entry->second,
__entry->cycle,
__entry->src,
__entry->dest,
__entry->channel,
__entry->payload_quadlets,
__entry->data_blocks,
__entry->data_block_counter,
__entry->packet_index,
__entry->irq,
__entry->index)
);
TRACE_EVENT(out_packet_without_header,
TP_PROTO(const struct amdtp_stream *s, u32 cycles, unsigned int payload_length, unsigned int data_blocks, unsigned int index),
TP_ARGS(s, cycles, payload_length, data_blocks, index),
TP_STRUCT__entry(
__field(unsigned int, second)
__field(unsigned int, cycle)
__field(int, channel)
__field(int, src)
__field(int, dest)
__field(unsigned int, payload_quadlets)
__field(unsigned int, data_blocks)
__field(unsigned int, data_block_counter)
__field(unsigned int, packet_index)
__field(unsigned int, irq)
__field(unsigned int, index)
),
TP_fast_assign(
__entry->second = cycles / CYCLES_PER_SECOND;
__entry->cycle = cycles % CYCLES_PER_SECOND;
__entry->channel = s->context->channel;
__entry->src = fw_parent_device(s->unit)->card->node_id;
__entry->dest = fw_parent_device(s->unit)->node_id;
__entry->payload_quadlets = payload_length / 4;
__entry->data_blocks = data_blocks,
__entry->data_blocks = s->data_block_counter,
__entry->packet_index = s->packet_index;
__entry->irq = !!in_interrupt();
__entry->index = index;
),
TP_printk(
"%02u %04u %04x %04x %02d %03u %02u %03u %02u %01u %02u",
__entry->second,
__entry->cycle,
__entry->src,
__entry->dest,
__entry->channel,
__entry->payload_quadlets,
__entry->data_blocks,
__entry->data_block_counter,
__entry->packet_index,
__entry->irq,
__entry->index)
);
#endif #endif
#undef TRACE_INCLUDE_PATH #undef TRACE_INCLUDE_PATH
......
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
/* isochronous header parameters */ /* isochronous header parameters */
#define ISO_DATA_LENGTH_SHIFT 16 #define ISO_DATA_LENGTH_SHIFT 16
#define TAG_NO_CIP_HEADER 0
#define TAG_CIP 1 #define TAG_CIP 1
/* common isochronous packet header parameters */ /* common isochronous packet header parameters */
...@@ -37,6 +38,8 @@ ...@@ -37,6 +38,8 @@
#define CIP_SID_MASK 0x3f000000 #define CIP_SID_MASK 0x3f000000
#define CIP_DBS_MASK 0x00ff0000 #define CIP_DBS_MASK 0x00ff0000
#define CIP_DBS_SHIFT 16 #define CIP_DBS_SHIFT 16
#define CIP_SPH_MASK 0x00000400
#define CIP_SPH_SHIFT 10
#define CIP_DBC_MASK 0x000000ff #define CIP_DBC_MASK 0x000000ff
#define CIP_FMT_SHIFT 24 #define CIP_FMT_SHIFT 24
#define CIP_FMT_MASK 0x3f000000 #define CIP_FMT_MASK 0x3f000000
...@@ -232,11 +235,15 @@ EXPORT_SYMBOL(amdtp_stream_set_parameters); ...@@ -232,11 +235,15 @@ EXPORT_SYMBOL(amdtp_stream_set_parameters);
unsigned int amdtp_stream_get_max_payload(struct amdtp_stream *s) unsigned int amdtp_stream_get_max_payload(struct amdtp_stream *s)
{ {
unsigned int multiplier = 1; unsigned int multiplier = 1;
unsigned int header_size = 0;
if (s->flags & CIP_JUMBO_PAYLOAD) if (s->flags & CIP_JUMBO_PAYLOAD)
multiplier = 5; multiplier = 5;
if (!(s->flags & CIP_NO_HEADER))
header_size = 8;
return 8 + s->syt_interval * s->data_block_quadlets * 4 * multiplier; return header_size +
s->syt_interval * s->data_block_quadlets * 4 * multiplier;
} }
EXPORT_SYMBOL(amdtp_stream_get_max_payload); EXPORT_SYMBOL(amdtp_stream_get_max_payload);
...@@ -378,7 +385,7 @@ static int queue_packet(struct amdtp_stream *s, unsigned int header_length, ...@@ -378,7 +385,7 @@ static int queue_packet(struct amdtp_stream *s, unsigned int header_length,
goto end; goto end;
p.interrupt = IS_ALIGNED(s->packet_index + 1, INTERRUPT_INTERVAL); p.interrupt = IS_ALIGNED(s->packet_index + 1, INTERRUPT_INTERVAL);
p.tag = TAG_CIP; p.tag = s->tag;
p.header_length = header_length; p.header_length = header_length;
if (payload_length > 0) if (payload_length > 0)
p.payload_length = payload_length; p.payload_length = payload_length;
...@@ -405,17 +412,16 @@ static inline int queue_out_packet(struct amdtp_stream *s, ...@@ -405,17 +412,16 @@ static inline int queue_out_packet(struct amdtp_stream *s,
static inline int queue_in_packet(struct amdtp_stream *s) static inline int queue_in_packet(struct amdtp_stream *s)
{ {
return queue_packet(s, IN_PACKET_HEADER_SIZE, return queue_packet(s, IN_PACKET_HEADER_SIZE, s->max_payload_length);
amdtp_stream_get_max_payload(s));
} }
static int handle_out_packet(struct amdtp_stream *s, unsigned int cycle, static int handle_out_packet(struct amdtp_stream *s,
unsigned int payload_length, unsigned int cycle,
unsigned int index) unsigned int index)
{ {
__be32 *buffer; __be32 *buffer;
unsigned int syt; unsigned int syt;
unsigned int data_blocks; unsigned int data_blocks;
unsigned int payload_length;
unsigned int pcm_frames; unsigned int pcm_frames;
struct snd_pcm_substream *pcm; struct snd_pcm_substream *pcm;
...@@ -424,15 +430,22 @@ static int handle_out_packet(struct amdtp_stream *s, unsigned int cycle, ...@@ -424,15 +430,22 @@ static int handle_out_packet(struct amdtp_stream *s, unsigned int cycle,
data_blocks = calculate_data_blocks(s, syt); data_blocks = calculate_data_blocks(s, syt);
pcm_frames = s->process_data_blocks(s, buffer + 2, data_blocks, &syt); pcm_frames = s->process_data_blocks(s, buffer + 2, data_blocks, &syt);
if (s->flags & CIP_DBC_IS_END_EVENT)
s->data_block_counter =
(s->data_block_counter + data_blocks) & 0xff;
buffer[0] = cpu_to_be32(ACCESS_ONCE(s->source_node_id_field) | buffer[0] = cpu_to_be32(ACCESS_ONCE(s->source_node_id_field) |
(s->data_block_quadlets << CIP_DBS_SHIFT) | (s->data_block_quadlets << CIP_DBS_SHIFT) |
((s->sph << CIP_SPH_SHIFT) & CIP_SPH_MASK) |
s->data_block_counter); s->data_block_counter);
buffer[1] = cpu_to_be32(CIP_EOH | buffer[1] = cpu_to_be32(CIP_EOH |
((s->fmt << CIP_FMT_SHIFT) & CIP_FMT_MASK) | ((s->fmt << CIP_FMT_SHIFT) & CIP_FMT_MASK) |
((s->fdf << CIP_FDF_SHIFT) & CIP_FDF_MASK) | ((s->fdf << CIP_FDF_SHIFT) & CIP_FDF_MASK) |
(syt & CIP_SYT_MASK)); (syt & CIP_SYT_MASK));
s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff; if (!(s->flags & CIP_DBC_IS_END_EVENT))
s->data_block_counter =
(s->data_block_counter + data_blocks) & 0xff;
payload_length = 8 + data_blocks * 4 * s->data_block_quadlets; payload_length = 8 + data_blocks * 4 * s->data_block_quadlets;
trace_out_packet(s, cycle, buffer, payload_length, index); trace_out_packet(s, cycle, buffer, payload_length, index);
...@@ -448,13 +461,45 @@ static int handle_out_packet(struct amdtp_stream *s, unsigned int cycle, ...@@ -448,13 +461,45 @@ static int handle_out_packet(struct amdtp_stream *s, unsigned int cycle,
return 0; return 0;
} }
static int handle_out_packet_without_header(struct amdtp_stream *s,
unsigned int payload_length, unsigned int cycle,
unsigned int index)
{
__be32 *buffer;
unsigned int syt;
unsigned int data_blocks;
unsigned int pcm_frames;
struct snd_pcm_substream *pcm;
buffer = s->buffer.packets[s->packet_index].buffer;
syt = calculate_syt(s, cycle);
data_blocks = calculate_data_blocks(s, syt);
pcm_frames = s->process_data_blocks(s, buffer, data_blocks, &syt);
s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff;
payload_length = data_blocks * 4 * s->data_block_quadlets;
trace_out_packet_without_header(s, cycle, payload_length, data_blocks,
index);
if (queue_out_packet(s, payload_length) < 0)
return -EIO;
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;
}
static int handle_in_packet(struct amdtp_stream *s, static int handle_in_packet(struct amdtp_stream *s,
unsigned int payload_quadlets, unsigned int cycle, unsigned int payload_length, unsigned int cycle,
unsigned int index) unsigned int index)
{ {
__be32 *buffer; __be32 *buffer;
u32 cip_header[2]; u32 cip_header[2];
unsigned int fmt, fdf, syt; unsigned int sph, fmt, fdf, syt;
unsigned int data_block_quadlets, data_block_counter, dbc_interval; unsigned int data_block_quadlets, data_block_counter, dbc_interval;
unsigned int data_blocks; unsigned int data_blocks;
struct snd_pcm_substream *pcm; struct snd_pcm_substream *pcm;
...@@ -465,14 +510,15 @@ static int handle_in_packet(struct amdtp_stream *s, ...@@ -465,14 +510,15 @@ static int handle_in_packet(struct amdtp_stream *s,
cip_header[0] = be32_to_cpu(buffer[0]); cip_header[0] = be32_to_cpu(buffer[0]);
cip_header[1] = be32_to_cpu(buffer[1]); cip_header[1] = be32_to_cpu(buffer[1]);
trace_in_packet(s, cycle, cip_header, payload_quadlets, index); trace_in_packet(s, cycle, cip_header, payload_length, index);
/* /*
* This module supports 'Two-quadlet CIP header with SYT field'. * This module supports 'Two-quadlet CIP header with SYT field'.
* For convenience, also check FMT field is AM824 or not. * For convenience, also check FMT field is AM824 or not.
*/ */
if (((cip_header[0] & CIP_EOH_MASK) == CIP_EOH) || if ((((cip_header[0] & CIP_EOH_MASK) == CIP_EOH) ||
((cip_header[1] & CIP_EOH_MASK) != CIP_EOH)) { ((cip_header[1] & CIP_EOH_MASK) != CIP_EOH)) &&
(!(s->flags & CIP_HEADER_WITHOUT_EOH))) {
dev_info_ratelimited(&s->unit->device, dev_info_ratelimited(&s->unit->device,
"Invalid CIP header for AMDTP: %08X:%08X\n", "Invalid CIP header for AMDTP: %08X:%08X\n",
cip_header[0], cip_header[1]); cip_header[0], cip_header[1]);
...@@ -482,8 +528,9 @@ static int handle_in_packet(struct amdtp_stream *s, ...@@ -482,8 +528,9 @@ static int handle_in_packet(struct amdtp_stream *s,
} }
/* Check valid protocol or not. */ /* Check valid protocol or not. */
sph = (cip_header[0] & CIP_SPH_MASK) >> CIP_SPH_SHIFT;
fmt = (cip_header[1] & CIP_FMT_MASK) >> CIP_FMT_SHIFT; fmt = (cip_header[1] & CIP_FMT_MASK) >> CIP_FMT_SHIFT;
if (fmt != s->fmt) { if (sph != s->sph || fmt != s->fmt) {
dev_info_ratelimited(&s->unit->device, dev_info_ratelimited(&s->unit->device,
"Detect unexpected protocol: %08x %08x\n", "Detect unexpected protocol: %08x %08x\n",
cip_header[0], cip_header[1]); cip_header[0], cip_header[1]);
...@@ -494,7 +541,7 @@ static int handle_in_packet(struct amdtp_stream *s, ...@@ -494,7 +541,7 @@ static int handle_in_packet(struct amdtp_stream *s,
/* Calculate data blocks */ /* Calculate data blocks */
fdf = (cip_header[1] & CIP_FDF_MASK) >> CIP_FDF_SHIFT; fdf = (cip_header[1] & CIP_FDF_MASK) >> CIP_FDF_SHIFT;
if (payload_quadlets < 3 || if (payload_length < 12 ||
(fmt == CIP_FMT_AM && fdf == AMDTP_FDF_NO_DATA)) { (fmt == CIP_FMT_AM && fdf == AMDTP_FDF_NO_DATA)) {
data_blocks = 0; data_blocks = 0;
} else { } else {
...@@ -510,7 +557,8 @@ static int handle_in_packet(struct amdtp_stream *s, ...@@ -510,7 +557,8 @@ static int handle_in_packet(struct amdtp_stream *s,
if (s->flags & CIP_WRONG_DBS) if (s->flags & CIP_WRONG_DBS)
data_block_quadlets = s->data_block_quadlets; data_block_quadlets = s->data_block_quadlets;
data_blocks = (payload_quadlets - 2) / data_block_quadlets; data_blocks = (payload_length / 4 - 2) /
data_block_quadlets;
} }
/* Check data block counter continuity */ /* Check data block counter continuity */
...@@ -561,6 +609,34 @@ static int handle_in_packet(struct amdtp_stream *s, ...@@ -561,6 +609,34 @@ static int handle_in_packet(struct amdtp_stream *s,
return 0; return 0;
} }
static int handle_in_packet_without_header(struct amdtp_stream *s,
unsigned int payload_quadlets, unsigned int cycle,
unsigned int index)
{
__be32 *buffer;
unsigned int data_blocks;
struct snd_pcm_substream *pcm;
unsigned int pcm_frames;
buffer = s->buffer.packets[s->packet_index].buffer;
data_blocks = payload_quadlets / s->data_block_quadlets;
trace_in_packet_without_header(s, cycle, payload_quadlets, data_blocks,
index);
pcm_frames = s->process_data_blocks(s, buffer, data_blocks, NULL);
s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff;
if (queue_in_packet(s) < 0)
return -EIO;
pcm = ACCESS_ONCE(s->pcm);
if (pcm && pcm_frames > 0)
update_pcm_pointers(s, pcm, pcm_frames);
return 0;
}
/* /*
* In CYCLE_TIMER register of IEEE 1394, 7 bits are used to represent second. On * In CYCLE_TIMER register of IEEE 1394, 7 bits are used to represent second. On
* the other hand, in DMA descriptors of 1394 OHCI, 3 bits are used to represent * the other hand, in DMA descriptors of 1394 OHCI, 3 bits are used to represent
...@@ -604,7 +680,7 @@ static void out_stream_callback(struct fw_iso_context *context, u32 tstamp, ...@@ -604,7 +680,7 @@ static void out_stream_callback(struct fw_iso_context *context, u32 tstamp,
for (i = 0; i < packets; ++i) { for (i = 0; i < packets; ++i) {
cycle = increment_cycle_count(cycle, 1); cycle = increment_cycle_count(cycle, 1);
if (handle_out_packet(s, cycle, i) < 0) { if (s->handle_packet(s, 0, cycle, i) < 0) {
s->packet_index = -1; s->packet_index = -1;
amdtp_stream_pcm_abort(s); amdtp_stream_pcm_abort(s);
return; return;
...@@ -620,7 +696,7 @@ static void in_stream_callback(struct fw_iso_context *context, u32 tstamp, ...@@ -620,7 +696,7 @@ static void in_stream_callback(struct fw_iso_context *context, u32 tstamp,
{ {
struct amdtp_stream *s = private_data; struct amdtp_stream *s = private_data;
unsigned int i, packets; unsigned int i, packets;
unsigned int payload_quadlets, max_payload_quadlets; unsigned int payload_length, max_payload_length;
__be32 *headers = header; __be32 *headers = header;
u32 cycle; u32 cycle;
...@@ -636,22 +712,22 @@ static void in_stream_callback(struct fw_iso_context *context, u32 tstamp, ...@@ -636,22 +712,22 @@ static void in_stream_callback(struct fw_iso_context *context, u32 tstamp,
cycle = decrement_cycle_count(cycle, packets); cycle = decrement_cycle_count(cycle, packets);
/* For buffer-over-run prevention. */ /* For buffer-over-run prevention. */
max_payload_quadlets = amdtp_stream_get_max_payload(s) / 4; max_payload_length = s->max_payload_length;
for (i = 0; i < packets; i++) { for (i = 0; i < packets; i++) {
cycle = increment_cycle_count(cycle, 1); cycle = increment_cycle_count(cycle, 1);
/* The number of quadlets in this packet */ /* The number of bytes in this packet */
payload_quadlets = payload_length =
(be32_to_cpu(headers[i]) >> ISO_DATA_LENGTH_SHIFT) / 4; (be32_to_cpu(headers[i]) >> ISO_DATA_LENGTH_SHIFT);
if (payload_quadlets > max_payload_quadlets) { if (payload_length > max_payload_length) {
dev_err(&s->unit->device, dev_err(&s->unit->device,
"Detect jumbo payload: %02x %02x\n", "Detect jumbo payload: %04x %04x\n",
payload_quadlets, max_payload_quadlets); payload_length, max_payload_length);
break; break;
} }
if (handle_in_packet(s, payload_quadlets, cycle, i) < 0) if (s->handle_packet(s, payload_length, cycle, i) < 0)
break; break;
} }
...@@ -671,6 +747,10 @@ static void amdtp_stream_first_callback(struct fw_iso_context *context, ...@@ -671,6 +747,10 @@ static void amdtp_stream_first_callback(struct fw_iso_context *context,
void *header, void *private_data) void *header, void *private_data)
{ {
struct amdtp_stream *s = private_data; struct amdtp_stream *s = private_data;
u32 cycle;
unsigned int packets;
s->max_payload_length = amdtp_stream_get_max_payload(s);
/* /*
* For in-stream, first packet has come. * For in-stream, first packet has come.
...@@ -679,10 +759,27 @@ static void amdtp_stream_first_callback(struct fw_iso_context *context, ...@@ -679,10 +759,27 @@ static void amdtp_stream_first_callback(struct fw_iso_context *context,
s->callbacked = true; s->callbacked = true;
wake_up(&s->callback_wait); wake_up(&s->callback_wait);
if (s->direction == AMDTP_IN_STREAM) cycle = compute_cycle_count(tstamp);
if (s->direction == AMDTP_IN_STREAM) {
packets = header_length / IN_PACKET_HEADER_SIZE;
cycle = decrement_cycle_count(cycle, packets);
context->callback.sc = in_stream_callback; context->callback.sc = in_stream_callback;
else if (s->flags & CIP_NO_HEADER)
s->handle_packet = handle_in_packet_without_header;
else
s->handle_packet = handle_in_packet;
} else {
packets = header_length / 4;
cycle = increment_cycle_count(cycle, QUEUE_LENGTH - packets);
context->callback.sc = out_stream_callback; context->callback.sc = out_stream_callback;
if (s->flags & CIP_NO_HEADER)
s->handle_packet = handle_out_packet_without_header;
else
s->handle_packet = handle_out_packet;
}
s->start_cycle = cycle;
context->callback.sc(context, tstamp, header_length, header, s); context->callback.sc(context, tstamp, header_length, header, s);
} }
...@@ -759,6 +856,11 @@ int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed) ...@@ -759,6 +856,11 @@ int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed)
amdtp_stream_update(s); amdtp_stream_update(s);
if (s->flags & CIP_NO_HEADER)
s->tag = TAG_NO_CIP_HEADER;
else
s->tag = TAG_CIP;
s->packet_index = 0; s->packet_index = 0;
do { do {
if (s->direction == AMDTP_IN_STREAM) if (s->direction == AMDTP_IN_STREAM)
...@@ -771,7 +873,7 @@ int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed) ...@@ -771,7 +873,7 @@ int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed)
/* NOTE: TAG1 matches CIP. This just affects in stream. */ /* NOTE: TAG1 matches CIP. This just affects in stream. */
tag = FW_ISO_CONTEXT_MATCH_TAG1; tag = FW_ISO_CONTEXT_MATCH_TAG1;
if (s->flags & CIP_EMPTY_WITH_TAG0) if ((s->flags & CIP_EMPTY_WITH_TAG0) || (s->flags & CIP_NO_HEADER))
tag |= FW_ISO_CONTEXT_MATCH_TAG0; tag |= FW_ISO_CONTEXT_MATCH_TAG0;
s->callbacked = false; s->callbacked = false;
......
...@@ -18,8 +18,8 @@ ...@@ -18,8 +18,8 @@
* SYT_INTERVAL samples, with these two types alternating so that * SYT_INTERVAL samples, with these two types alternating so that
* the overall sample rate comes out right. * the overall sample rate comes out right.
* @CIP_EMPTY_WITH_TAG0: Only for in-stream. Empty in-packets have TAG0. * @CIP_EMPTY_WITH_TAG0: Only for in-stream. Empty in-packets have TAG0.
* @CIP_DBC_IS_END_EVENT: Only for in-stream. The value of dbc in an in-packet * @CIP_DBC_IS_END_EVENT: The value of dbc in an packet corresponds to the end
* corresponds to the end of event in the packet. Out of IEC 61883. * of event in the packet. Out of IEC 61883.
* @CIP_WRONG_DBS: Only for in-stream. The value of dbs is wrong in in-packets. * @CIP_WRONG_DBS: Only for in-stream. The value of dbs is wrong in in-packets.
* The value of data_block_quadlets is used instead of reported value. * The value of data_block_quadlets is used instead of reported value.
* @CIP_SKIP_DBC_ZERO_CHECK: Only for in-stream. Packets with zero in dbc is * @CIP_SKIP_DBC_ZERO_CHECK: Only for in-stream. Packets with zero in dbc is
...@@ -29,6 +29,9 @@ ...@@ -29,6 +29,9 @@
* @CIP_JUMBO_PAYLOAD: Only for in-stream. The number of data blocks in an * @CIP_JUMBO_PAYLOAD: Only for in-stream. The number of data blocks in an
* packet is larger than IEC 61883-6 defines. Current implementation * packet is larger than IEC 61883-6 defines. Current implementation
* allows 5 times as large as IEC 61883-6 defines. * allows 5 times as large as IEC 61883-6 defines.
* @CIP_HEADER_WITHOUT_EOH: Only for in-stream. CIP Header doesn't include
* valid EOH.
* @CIP_NO_HEADERS: a lack of headers in packets
*/ */
enum cip_flags { enum cip_flags {
CIP_NONBLOCKING = 0x00, CIP_NONBLOCKING = 0x00,
...@@ -39,6 +42,8 @@ enum cip_flags { ...@@ -39,6 +42,8 @@ enum cip_flags {
CIP_SKIP_DBC_ZERO_CHECK = 0x10, CIP_SKIP_DBC_ZERO_CHECK = 0x10,
CIP_EMPTY_HAS_WRONG_DBC = 0x20, CIP_EMPTY_HAS_WRONG_DBC = 0x20,
CIP_JUMBO_PAYLOAD = 0x40, CIP_JUMBO_PAYLOAD = 0x40,
CIP_HEADER_WITHOUT_EOH = 0x80,
CIP_NO_HEADER = 0x100,
}; };
/** /**
...@@ -101,11 +106,17 @@ struct amdtp_stream { ...@@ -101,11 +106,17 @@ struct amdtp_stream {
struct fw_iso_context *context; struct fw_iso_context *context;
struct iso_packets_buffer buffer; struct iso_packets_buffer buffer;
int packet_index; int packet_index;
int tag;
int (*handle_packet)(struct amdtp_stream *s,
unsigned int payload_quadlets, unsigned int cycle,
unsigned int index);
unsigned int max_payload_length;
/* For CIP headers. */ /* For CIP headers. */
unsigned int source_node_id_field; unsigned int source_node_id_field;
unsigned int data_block_quadlets; unsigned int data_block_quadlets;
unsigned int data_block_counter; unsigned int data_block_counter;
unsigned int sph;
unsigned int fmt; unsigned int fmt;
unsigned int fdf; unsigned int fdf;
/* quirk: fixed interval of dbc between previos/current packets. */ /* quirk: fixed interval of dbc between previos/current packets. */
...@@ -130,6 +141,7 @@ struct amdtp_stream { ...@@ -130,6 +141,7 @@ struct amdtp_stream {
/* To wait for first packet. */ /* To wait for first packet. */
bool callbacked; bool callbacked;
wait_queue_head_t callback_wait; wait_queue_head_t callback_wait;
u32 start_cycle;
/* For backends to process data blocks. */ /* For backends to process data blocks. */
void *protocol; void *protocol;
......
...@@ -31,13 +31,15 @@ int avc_audio_set_selector(struct fw_unit *unit, unsigned int subunit_id, ...@@ -31,13 +31,15 @@ int avc_audio_set_selector(struct fw_unit *unit, unsigned int subunit_id,
err = fcp_avc_transaction(unit, buf, 12, buf, 12, err = fcp_avc_transaction(unit, buf, 12, buf, 12,
BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
BIT(6) | BIT(7) | BIT(8)); BIT(6) | BIT(7) | BIT(8));
if (err > 0 && err < 9) if (err < 0)
;
else if (err < 9)
err = -EIO; err = -EIO;
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
err = -ENOSYS; err = -ENOSYS;
else if (buf[0] == 0x0a) /* REJECTED */ else if (buf[0] == 0x0a) /* REJECTED */
err = -EINVAL; err = -EINVAL;
else if (err > 0) else
err = 0; err = 0;
kfree(buf); kfree(buf);
...@@ -67,7 +69,9 @@ int avc_audio_get_selector(struct fw_unit *unit, unsigned int subunit_id, ...@@ -67,7 +69,9 @@ int avc_audio_get_selector(struct fw_unit *unit, unsigned int subunit_id,
err = fcp_avc_transaction(unit, buf, 12, buf, 12, err = fcp_avc_transaction(unit, buf, 12, buf, 12,
BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
BIT(6) | BIT(8)); BIT(6) | BIT(8));
if (err > 0 && err < 9) if (err < 0)
;
else if (err < 9)
err = -EIO; err = -EIO;
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
err = -ENOSYS; err = -ENOSYS;
...@@ -120,7 +124,9 @@ int avc_bridgeco_get_plug_type(struct fw_unit *unit, ...@@ -120,7 +124,9 @@ int avc_bridgeco_get_plug_type(struct fw_unit *unit,
err = fcp_avc_transaction(unit, buf, 12, buf, 12, err = fcp_avc_transaction(unit, buf, 12, buf, 12,
BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
BIT(6) | BIT(7) | BIT(9)); BIT(6) | BIT(7) | BIT(9));
if ((err >= 0) && (err < 8)) if (err < 0)
;
else if (err < 11)
err = -EIO; err = -EIO;
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
err = -ENOSYS; err = -ENOSYS;
...@@ -150,7 +156,9 @@ int avc_bridgeco_get_plug_ch_pos(struct fw_unit *unit, ...@@ -150,7 +156,9 @@ int avc_bridgeco_get_plug_ch_pos(struct fw_unit *unit,
err = fcp_avc_transaction(unit, buf, 12, buf, 256, err = fcp_avc_transaction(unit, buf, 12, buf, 256,
BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(1) | BIT(2) | BIT(3) | BIT(4) |
BIT(5) | BIT(6) | BIT(7) | BIT(9)); BIT(5) | BIT(6) | BIT(7) | BIT(9));
if ((err >= 0) && (err < 8)) if (err < 0)
;
else if (err < 11)
err = -EIO; err = -EIO;
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
err = -ENOSYS; err = -ENOSYS;
...@@ -187,7 +195,9 @@ int avc_bridgeco_get_plug_section_type(struct fw_unit *unit, ...@@ -187,7 +195,9 @@ int avc_bridgeco_get_plug_section_type(struct fw_unit *unit,
err = fcp_avc_transaction(unit, buf, 12, buf, 12, err = fcp_avc_transaction(unit, buf, 12, buf, 12,
BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
BIT(6) | BIT(7) | BIT(9) | BIT(10)); BIT(6) | BIT(7) | BIT(9) | BIT(10));
if ((err >= 0) && (err < 8)) if (err < 0)
;
else if (err < 12)
err = -EIO; err = -EIO;
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
err = -ENOSYS; err = -ENOSYS;
...@@ -221,7 +231,9 @@ int avc_bridgeco_get_plug_input(struct fw_unit *unit, ...@@ -221,7 +231,9 @@ int avc_bridgeco_get_plug_input(struct fw_unit *unit,
err = fcp_avc_transaction(unit, buf, 16, buf, 16, err = fcp_avc_transaction(unit, buf, 16, buf, 16,
BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
BIT(6) | BIT(7)); BIT(6) | BIT(7));
if ((err >= 0) && (err < 8)) if (err < 0)
;
else if (err < 16)
err = -EIO; err = -EIO;
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
err = -ENOSYS; err = -ENOSYS;
...@@ -260,7 +272,9 @@ int avc_bridgeco_get_plug_strm_fmt(struct fw_unit *unit, ...@@ -260,7 +272,9 @@ int avc_bridgeco_get_plug_strm_fmt(struct fw_unit *unit,
err = fcp_avc_transaction(unit, buf, 12, buf, *len, err = fcp_avc_transaction(unit, buf, 12, buf, *len,
BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
BIT(6) | BIT(7) | BIT(10)); BIT(6) | BIT(7) | BIT(10));
if ((err >= 0) && (err < 12)) if (err < 0)
;
else if (err < 12)
err = -EIO; err = -EIO;
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
err = -ENOSYS; err = -ENOSYS;
......
...@@ -28,6 +28,9 @@ ...@@ -28,6 +28,9 @@
*/ */
#define MAX_MIDI_RX_BLOCKS 8 #define MAX_MIDI_RX_BLOCKS 8
/* 3 = MAX(DOT_MIDI_IN_PORTS, DOT_MIDI_OUT_PORTS) + 1. */
#define MAX_MIDI_PORTS 3
/* /*
* The double-oh-three algorithm was discovered by Robin Gareus and Damien * The double-oh-three algorithm was discovered by Robin Gareus and Damien
* Zammit in 2012, with reverse-engineering for Digi 003 Rack. * Zammit in 2012, with reverse-engineering for Digi 003 Rack.
...@@ -42,10 +45,8 @@ struct amdtp_dot { ...@@ -42,10 +45,8 @@ struct amdtp_dot {
unsigned int pcm_channels; unsigned int pcm_channels;
struct dot_state state; struct dot_state state;
unsigned int midi_ports; struct snd_rawmidi_substream *midi[MAX_MIDI_PORTS];
/* 2 = MAX(DOT_MIDI_IN_PORTS, DOT_MIDI_OUT_PORTS) */ int midi_fifo_used[MAX_MIDI_PORTS];
struct snd_rawmidi_substream *midi[2];
int midi_fifo_used[2];
int midi_fifo_limit; int midi_fifo_limit;
void (*transfer_samples)(struct amdtp_stream *s, void (*transfer_samples)(struct amdtp_stream *s,
...@@ -124,8 +125,8 @@ int amdtp_dot_set_parameters(struct amdtp_stream *s, unsigned int rate, ...@@ -124,8 +125,8 @@ int amdtp_dot_set_parameters(struct amdtp_stream *s, unsigned int rate,
return -EBUSY; return -EBUSY;
/* /*
* A first data channel is for MIDI conformant data channel, the rest is * A first data channel is for MIDI messages, the rest is Multi Bit
* Multi Bit Linear Audio data channel. * Linear Audio data channel.
*/ */
err = amdtp_stream_set_parameters(s, rate, pcm_channels + 1); err = amdtp_stream_set_parameters(s, rate, pcm_channels + 1);
if (err < 0) if (err < 0)
...@@ -135,11 +136,6 @@ int amdtp_dot_set_parameters(struct amdtp_stream *s, unsigned int rate, ...@@ -135,11 +136,6 @@ int amdtp_dot_set_parameters(struct amdtp_stream *s, unsigned int rate,
p->pcm_channels = pcm_channels; 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 * 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 * assume two bytes, i.e., one byte can be received over the bus while
...@@ -281,13 +277,25 @@ static void write_midi_messages(struct amdtp_stream *s, __be32 *buffer, ...@@ -281,13 +277,25 @@ static void write_midi_messages(struct amdtp_stream *s, __be32 *buffer,
b = (u8 *)&buffer[0]; b = (u8 *)&buffer[0];
len = 0; len = 0;
if (port < p->midi_ports && if (port < MAX_MIDI_PORTS &&
midi_ratelimit_per_packet(s, port) && midi_ratelimit_per_packet(s, port) &&
p->midi[port] != NULL) p->midi[port] != NULL)
len = snd_rawmidi_transmit(p->midi[port], b + 1, 2); len = snd_rawmidi_transmit(p->midi[port], b + 1, 2);
if (len > 0) { if (len > 0) {
b[3] = (0x10 << port) | len; /*
* Upper 4 bits of LSB represent port number.
* - 0000b: physical MIDI port 1.
* - 0010b: physical MIDI port 2.
* - 1110b: console MIDI port.
*/
if (port == 2)
b[3] = 0xe0;
else if (port == 1)
b[3] = 0x20;
else
b[3] = 0x00;
b[3] |= len;
midi_use_bytes(s, port, len); midi_use_bytes(s, port, len);
} else { } else {
b[1] = 0; b[1] = 0;
...@@ -309,11 +317,22 @@ static void read_midi_messages(struct amdtp_stream *s, __be32 *buffer, ...@@ -309,11 +317,22 @@ static void read_midi_messages(struct amdtp_stream *s, __be32 *buffer,
for (f = 0; f < data_blocks; f++) { for (f = 0; f < data_blocks; f++) {
b = (u8 *)&buffer[0]; b = (u8 *)&buffer[0];
port = b[3] >> 4;
len = b[3] & 0x0f;
if (port < p->midi_ports && p->midi[port] && len > 0) len = b[3] & 0x0f;
snd_rawmidi_receive(p->midi[port], b + 1, len); if (len > 0) {
/*
* Upper 4 bits of LSB represent port number.
* - 0000b: physical MIDI port 1. Use port 0.
* - 1110b: console MIDI port. Use port 2.
*/
if (b[3] >> 4 > 0)
port = 2;
else
port = 0;
if (port < MAX_MIDI_PORTS && p->midi[port])
snd_rawmidi_receive(p->midi[port], b + 1, len);
}
buffer += s->data_block_quadlets; buffer += s->data_block_quadlets;
} }
...@@ -364,7 +383,7 @@ void amdtp_dot_midi_trigger(struct amdtp_stream *s, unsigned int port, ...@@ -364,7 +383,7 @@ void amdtp_dot_midi_trigger(struct amdtp_stream *s, unsigned int port,
{ {
struct amdtp_dot *p = s->protocol; struct amdtp_dot *p = s->protocol;
if (port < p->midi_ports) if (port < MAX_MIDI_PORTS)
ACCESS_ONCE(p->midi[port]) = midi; ACCESS_ONCE(p->midi[port]) = midi;
} }
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
#include "digi00x.h" #include "digi00x.h"
static int midi_phys_open(struct snd_rawmidi_substream *substream) static int midi_open(struct snd_rawmidi_substream *substream)
{ {
struct snd_dg00x *dg00x = substream->rmidi->private_data; struct snd_dg00x *dg00x = substream->rmidi->private_data;
int err; int err;
...@@ -27,7 +27,7 @@ static int midi_phys_open(struct snd_rawmidi_substream *substream) ...@@ -27,7 +27,7 @@ static int midi_phys_open(struct snd_rawmidi_substream *substream)
return err; return err;
} }
static int midi_phys_close(struct snd_rawmidi_substream *substream) static int midi_close(struct snd_rawmidi_substream *substream)
{ {
struct snd_dg00x *dg00x = substream->rmidi->private_data; struct snd_dg00x *dg00x = substream->rmidi->private_data;
...@@ -40,180 +40,130 @@ static int midi_phys_close(struct snd_rawmidi_substream *substream) ...@@ -40,180 +40,130 @@ static int midi_phys_close(struct snd_rawmidi_substream *substream)
return 0; return 0;
} }
static void midi_phys_capture_trigger(struct snd_rawmidi_substream *substream, static void midi_capture_trigger(struct snd_rawmidi_substream *substream,
int up) int up)
{ {
struct snd_dg00x *dg00x = substream->rmidi->private_data; struct snd_dg00x *dg00x = substream->rmidi->private_data;
unsigned int port;
unsigned long flags; unsigned long flags;
spin_lock_irqsave(&dg00x->lock, flags); if (substream->rmidi->device == 0)
port = substream->number;
if (up)
amdtp_dot_midi_trigger(&dg00x->tx_stream, substream->number,
substream);
else else
amdtp_dot_midi_trigger(&dg00x->tx_stream, substream->number, port = 2;
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); spin_lock_irqsave(&dg00x->lock, flags);
if (up) if (up)
amdtp_dot_midi_trigger(&dg00x->rx_stream, substream->number, amdtp_dot_midi_trigger(&dg00x->tx_stream, port, substream);
substream);
else else
amdtp_dot_midi_trigger(&dg00x->rx_stream, substream->number, amdtp_dot_midi_trigger(&dg00x->tx_stream, port, NULL);
NULL);
spin_unlock_irqrestore(&dg00x->lock, flags); spin_unlock_irqrestore(&dg00x->lock, flags);
} }
static int midi_ctl_open(struct snd_rawmidi_substream *substream) static void midi_playback_trigger(struct snd_rawmidi_substream *substream,
{ int up)
/* 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; struct snd_dg00x *dg00x = substream->rmidi->private_data;
unsigned int port;
unsigned long flags; unsigned long flags;
spin_lock_irqsave(&dg00x->lock, flags); if (substream->rmidi->device == 0)
port = substream->number;
if (up)
dg00x->in_control = substream;
else else
dg00x->in_control = NULL; port = 2;
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); spin_lock_irqsave(&dg00x->lock, flags);
if (up) if (up)
snd_fw_async_midi_port_run(&dg00x->out_control, substream); amdtp_dot_midi_trigger(&dg00x->rx_stream, port, substream);
else
amdtp_dot_midi_trigger(&dg00x->rx_stream, port, NULL);
spin_unlock_irqrestore(&dg00x->lock, flags); spin_unlock_irqrestore(&dg00x->lock, flags);
} }
static void set_midi_substream_names(struct snd_dg00x *dg00x, static void set_substream_names(struct snd_dg00x *dg00x,
struct snd_rawmidi_str *str, struct snd_rawmidi *rmidi, bool is_console)
bool is_ctl)
{ {
struct snd_rawmidi_substream *subs; struct snd_rawmidi_substream *subs;
struct snd_rawmidi_str *str;
list_for_each_entry(subs, &str->substreams, list) { int i;
if (!is_ctl)
snprintf(subs->name, sizeof(subs->name), for (i = 0; i < 2; ++i) {
"%s MIDI %d", str = &rmidi->streams[i];
dg00x->card->shortname, subs->number + 1);
else list_for_each_entry(subs, &str->substreams, list) {
/* This port is for asynchronous transaction. */ if (!is_console) {
snprintf(subs->name, sizeof(subs->name), snprintf(subs->name, sizeof(subs->name),
"%s control", "%s MIDI %d",
dg00x->card->shortname); dg00x->card->shortname,
subs->number + 1);
} else {
snprintf(subs->name, sizeof(subs->name),
"%s control",
dg00x->card->shortname);
}
}
} }
} }
int snd_dg00x_create_midi_devices(struct snd_dg00x *dg00x) static int add_substream_pair(struct snd_dg00x *dg00x, unsigned int out_ports,
unsigned int in_ports, bool is_console)
{ {
static const struct snd_rawmidi_ops phys_capture_ops = { static const struct snd_rawmidi_ops capture_ops = {
.open = midi_phys_open, .open = midi_open,
.close = midi_phys_close, .close = midi_close,
.trigger = midi_phys_capture_trigger, .trigger = midi_capture_trigger,
};
static const struct snd_rawmidi_ops phys_playback_ops = {
.open = midi_phys_open,
.close = midi_phys_close,
.trigger = midi_phys_playback_trigger,
}; };
static const struct snd_rawmidi_ops ctl_capture_ops = { static const struct snd_rawmidi_ops playback_ops = {
.open = midi_ctl_open, .open = midi_open,
.close = midi_ctl_capture_close, .close = midi_close,
.trigger = midi_ctl_capture_trigger, .trigger = midi_playback_trigger,
}; };
static const struct snd_rawmidi_ops ctl_playback_ops = { const char *label;
.open = midi_ctl_open, struct snd_rawmidi *rmidi;
.close = midi_ctl_playback_close,
.trigger = midi_ctl_playback_trigger,
};
struct snd_rawmidi *rmidi[2];
struct snd_rawmidi_str *str;
unsigned int i;
int err; int err;
/* Add physical midi ports. */ /* Add physical midi ports. */
err = snd_rawmidi_new(dg00x->card, dg00x->card->driver, 0, err = snd_rawmidi_new(dg00x->card, dg00x->card->driver, is_console,
DOT_MIDI_OUT_PORTS, DOT_MIDI_IN_PORTS, &rmidi[0]); out_ports, in_ports, &rmidi);
if (err < 0) if (err < 0)
return err; return err;
rmidi->private_data = dg00x;
snprintf(rmidi[0]->name, sizeof(rmidi[0]->name), if (!is_console)
"%s MIDI", dg00x->card->shortname); label = "%s control";
else
snd_rawmidi_set_ops(rmidi[0], SNDRV_RAWMIDI_STREAM_INPUT, label = "%s MIDI";
&phys_capture_ops); snprintf(rmidi->name, sizeof(rmidi->name), label,
snd_rawmidi_set_ops(rmidi[0], SNDRV_RAWMIDI_STREAM_OUTPUT, dg00x->card->shortname);
&phys_playback_ops);
/* Add a pair of control midi ports. */ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &playback_ops);
err = snd_rawmidi_new(dg00x->card, dg00x->card->driver, 1, snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &capture_ops);
1, 1, &rmidi[1]);
if (err < 0)
return err;
snprintf(rmidi[1]->name, sizeof(rmidi[1]->name), rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT |
"%s control", dg00x->card->shortname); SNDRV_RAWMIDI_INFO_OUTPUT |
SNDRV_RAWMIDI_INFO_DUPLEX;
snd_rawmidi_set_ops(rmidi[1], SNDRV_RAWMIDI_STREAM_INPUT, set_substream_names(dg00x, rmidi, is_console);
&ctl_capture_ops);
snd_rawmidi_set_ops(rmidi[1], SNDRV_RAWMIDI_STREAM_OUTPUT,
&ctl_playback_ops);
for (i = 0; i < ARRAY_SIZE(rmidi); i++) { return 0;
rmidi[i]->private_data = dg00x; }
rmidi[i]->info_flags |= SNDRV_RAWMIDI_INFO_INPUT; int snd_dg00x_create_midi_devices(struct snd_dg00x *dg00x)
str = &rmidi[i]->streams[SNDRV_RAWMIDI_STREAM_INPUT]; {
set_midi_substream_names(dg00x, str, i); int err;
rmidi[i]->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; /* Add physical midi ports. */
str = &rmidi[i]->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]; err = add_substream_pair(dg00x, DOT_MIDI_OUT_PORTS, DOT_MIDI_IN_PORTS,
set_midi_substream_names(dg00x, str, i); false);
if (err < 0)
return err;
rmidi[i]->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; if (dg00x->is_console)
} err = add_substream_pair(dg00x, 1, 1, true);
return 0; return err;
} }
...@@ -9,40 +9,6 @@ ...@@ -9,40 +9,6 @@
#include <sound/asound.h> #include <sound/asound.h>
#include "digi00x.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, static void handle_unknown_message(struct snd_dg00x *dg00x,
unsigned long long offset, __be32 *buf) unsigned long long offset, __be32 *buf)
{ {
...@@ -63,39 +29,36 @@ static void handle_message(struct fw_card *card, struct fw_request *request, ...@@ -63,39 +29,36 @@ static void handle_message(struct fw_card *card, struct fw_request *request,
struct snd_dg00x *dg00x = callback_data; struct snd_dg00x *dg00x = callback_data;
__be32 *buf = (__be32 *)data; __be32 *buf = (__be32 *)data;
fw_send_response(card, request, RCODE_COMPLETE);
if (offset == dg00x->async_handler.offset) if (offset == dg00x->async_handler.offset)
handle_unknown_message(dg00x, offset, buf); 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) int snd_dg00x_transaction_reregister(struct snd_dg00x *dg00x)
{ {
struct fw_device *device = fw_parent_device(dg00x->unit); struct fw_device *device = fw_parent_device(dg00x->unit);
__be32 data[2]; __be32 data[2];
int err;
/* Unknown. 4bytes. */ /* Unknown. 4bytes. */
data[0] = cpu_to_be32((device->card->node_id << 16) | data[0] = cpu_to_be32((device->card->node_id << 16) |
(dg00x->async_handler.offset >> 32)); (dg00x->async_handler.offset >> 32));
data[1] = cpu_to_be32(dg00x->async_handler.offset); 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, return snd_fw_transaction(dg00x->unit, TCODE_WRITE_BLOCK_REQUEST,
DG00X_ADDR_BASE + DG00X_OFFSET_MIDI_CTL_ADDR, DG00X_ADDR_BASE + DG00X_OFFSET_MESSAGE_ADDR,
&data, sizeof(data), 0); &data, sizeof(data), 0);
} }
void snd_dg00x_transaction_unregister(struct snd_dg00x *dg00x)
{
if (dg00x->async_handler.callback_data == NULL)
return;
fw_core_remove_address_handler(&dg00x->async_handler);
dg00x->async_handler.callback_data = NULL;
}
int snd_dg00x_transaction_register(struct snd_dg00x *dg00x) int snd_dg00x_transaction_register(struct snd_dg00x *dg00x)
{ {
static const struct fw_address_region resp_register_region = { static const struct fw_address_region resp_register_region = {
...@@ -104,7 +67,7 @@ int snd_dg00x_transaction_register(struct snd_dg00x *dg00x) ...@@ -104,7 +67,7 @@ int snd_dg00x_transaction_register(struct snd_dg00x *dg00x)
}; };
int err; int err;
dg00x->async_handler.length = 12; dg00x->async_handler.length = 4;
dg00x->async_handler.address_callback = handle_message; dg00x->async_handler.address_callback = handle_message;
dg00x->async_handler.callback_data = dg00x; dg00x->async_handler.callback_data = dg00x;
...@@ -115,28 +78,7 @@ int snd_dg00x_transaction_register(struct snd_dg00x *dg00x) ...@@ -115,28 +78,7 @@ int snd_dg00x_transaction_register(struct snd_dg00x *dg00x)
err = snd_dg00x_transaction_reregister(dg00x); err = snd_dg00x_transaction_reregister(dg00x);
if (err < 0) if (err < 0)
goto error; snd_dg00x_transaction_unregister(dg00x);
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; return err;
error:
fw_core_remove_address_handler(&dg00x->async_handler);
dg00x->async_handler.callback_data = NULL;
return err;
}
void snd_dg00x_transaction_unregister(struct snd_dg00x *dg00x)
{
if (dg00x->async_handler.callback_data == NULL)
return;
snd_fw_async_midi_port_destroy(&dg00x->out_control);
fw_core_remove_address_handler(&dg00x->async_handler);
dg00x->async_handler.callback_data = NULL;
} }
...@@ -13,7 +13,8 @@ MODULE_AUTHOR("Takashi Sakamoto <o-takashi@sakamocchi.jp>"); ...@@ -13,7 +13,8 @@ MODULE_AUTHOR("Takashi Sakamoto <o-takashi@sakamocchi.jp>");
MODULE_LICENSE("GPL v2"); MODULE_LICENSE("GPL v2");
#define VENDOR_DIGIDESIGN 0x00a07e #define VENDOR_DIGIDESIGN 0x00a07e
#define MODEL_DIGI00X 0x000002 #define MODEL_CONSOLE 0x000001
#define MODEL_RACK 0x000002
static int name_card(struct snd_dg00x *dg00x) static int name_card(struct snd_dg00x *dg00x)
{ {
...@@ -129,6 +130,8 @@ static int snd_dg00x_probe(struct fw_unit *unit, ...@@ -129,6 +130,8 @@ static int snd_dg00x_probe(struct fw_unit *unit,
spin_lock_init(&dg00x->lock); spin_lock_init(&dg00x->lock);
init_waitqueue_head(&dg00x->hwdep_wait); init_waitqueue_head(&dg00x->hwdep_wait);
dg00x->is_console = entry->model_id == MODEL_CONSOLE;
/* Allocate and register this sound card later. */ /* Allocate and register this sound card later. */
INIT_DEFERRABLE_WORK(&dg00x->dwork, do_registration); INIT_DEFERRABLE_WORK(&dg00x->dwork, do_registration);
snd_fw_schedule_registration(unit, &dg00x->dwork); snd_fw_schedule_registration(unit, &dg00x->dwork);
...@@ -183,7 +186,13 @@ static const struct ieee1394_device_id snd_dg00x_id_table[] = { ...@@ -183,7 +186,13 @@ static const struct ieee1394_device_id snd_dg00x_id_table[] = {
.match_flags = IEEE1394_MATCH_VENDOR_ID | .match_flags = IEEE1394_MATCH_VENDOR_ID |
IEEE1394_MATCH_MODEL_ID, IEEE1394_MATCH_MODEL_ID,
.vendor_id = VENDOR_DIGIDESIGN, .vendor_id = VENDOR_DIGIDESIGN,
.model_id = MODEL_DIGI00X, .model_id = MODEL_CONSOLE,
},
{
.match_flags = IEEE1394_MATCH_VENDOR_ID |
IEEE1394_MATCH_MODEL_ID,
.vendor_id = VENDOR_DIGIDESIGN,
.model_id = MODEL_RACK,
}, },
{} {}
}; };
......
...@@ -58,16 +58,15 @@ struct snd_dg00x { ...@@ -58,16 +58,15 @@ struct snd_dg00x {
struct fw_address_handler async_handler; struct fw_address_handler async_handler;
u32 msg; u32 msg;
/* For asynchronous MIDI controls. */ /* Console models have additional MIDI ports for control surface. */
struct snd_rawmidi_substream *in_control; bool is_console;
struct snd_fw_async_midi_port out_control;
}; };
#define DG00X_ADDR_BASE 0xffffe0000000ull #define DG00X_ADDR_BASE 0xffffe0000000ull
#define DG00X_OFFSET_STREAMING_STATE 0x0000 #define DG00X_OFFSET_STREAMING_STATE 0x0000
#define DG00X_OFFSET_STREAMING_SET 0x0004 #define DG00X_OFFSET_STREAMING_SET 0x0004
#define DG00X_OFFSET_MIDI_CTL_ADDR 0x0008 /* unknown but address in host space 0x0008 */
/* For LSB of the address 0x000c */ /* For LSB of the address 0x000c */
/* unknown 0x0010 */ /* unknown 0x0010 */
#define DG00X_OFFSET_MESSAGE_ADDR 0x0014 #define DG00X_OFFSET_MESSAGE_ADDR 0x0014
......
...@@ -63,7 +63,9 @@ int avc_general_set_sig_fmt(struct fw_unit *unit, unsigned int rate, ...@@ -63,7 +63,9 @@ int avc_general_set_sig_fmt(struct fw_unit *unit, unsigned int rate,
/* do transaction and check buf[1-5] are the same against command */ /* do transaction and check buf[1-5] are the same against command */
err = fcp_avc_transaction(unit, buf, 8, buf, 8, err = fcp_avc_transaction(unit, buf, 8, buf, 8,
BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5)); BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5));
if (err >= 0 && err < 8) if (err < 0)
;
else if (err < 8)
err = -EIO; err = -EIO;
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
err = -ENOSYS; err = -ENOSYS;
...@@ -106,7 +108,9 @@ int avc_general_get_sig_fmt(struct fw_unit *unit, unsigned int *rate, ...@@ -106,7 +108,9 @@ int avc_general_get_sig_fmt(struct fw_unit *unit, unsigned int *rate,
/* do transaction and check buf[1-4] are the same against command */ /* do transaction and check buf[1-4] are the same against command */
err = fcp_avc_transaction(unit, buf, 8, buf, 8, err = fcp_avc_transaction(unit, buf, 8, buf, 8,
BIT(1) | BIT(2) | BIT(3) | BIT(4)); BIT(1) | BIT(2) | BIT(3) | BIT(4));
if (err >= 0 && err < 8) if (err < 0)
;
else if (err < 8)
err = -EIO; err = -EIO;
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
err = -ENOSYS; err = -ENOSYS;
...@@ -154,7 +158,9 @@ int avc_general_get_plug_info(struct fw_unit *unit, unsigned int subunit_type, ...@@ -154,7 +158,9 @@ int avc_general_get_plug_info(struct fw_unit *unit, unsigned int subunit_type,
buf[3] = 0xff & subfunction; buf[3] = 0xff & subfunction;
err = fcp_avc_transaction(unit, buf, 8, buf, 8, BIT(1) | BIT(2)); err = fcp_avc_transaction(unit, buf, 8, buf, 8, BIT(1) | BIT(2));
if (err >= 0 && err < 8) if (err < 0)
;
else if (err < 8)
err = -EIO; err = -EIO;
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
err = -ENOSYS; err = -ENOSYS;
......
snd-fireface-objs := ff.o ff-transaction.o ff-midi.o ff-proc.o amdtp-ff.o \
ff-stream.o ff-pcm.o ff-hwdep.o ff-protocol-ff400.o
obj-$(CONFIG_SND_FIREFACE) += snd-fireface.o
/*
* amdtp-ff.c - a part of driver for RME Fireface series
*
* Copyright (c) 2015-2017 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include <sound/pcm.h>
#include "ff.h"
struct amdtp_ff {
unsigned int pcm_channels;
};
int amdtp_ff_set_parameters(struct amdtp_stream *s, unsigned int rate,
unsigned int pcm_channels)
{
struct amdtp_ff *p = s->protocol;
unsigned int data_channels;
if (amdtp_stream_running(s))
return -EBUSY;
p->pcm_channels = pcm_channels;
data_channels = pcm_channels;
return amdtp_stream_set_parameters(s, rate, data_channels);
}
static void write_pcm_s32(struct amdtp_stream *s,
struct snd_pcm_substream *pcm,
__le32 *buffer, unsigned int frames)
{
struct amdtp_ff *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[c] = cpu_to_le32(*src);
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,
__le32 *buffer, unsigned int frames)
{
struct amdtp_ff *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 = le32_to_cpu(buffer[c]) & 0xffffff00;
dst++;
}
buffer += s->data_block_quadlets;
if (--remaining_frames == 0)
dst = (void *)runtime->dma_area;
}
}
static void write_pcm_silence(struct amdtp_stream *s,
__le32 *buffer, unsigned int frames)
{
struct amdtp_ff *p = s->protocol;
unsigned int i, c, channels = p->pcm_channels;
for (i = 0; i < frames; ++i) {
for (c = 0; c < channels; ++c)
buffer[c] = cpu_to_le32(0x00000000);
buffer += s->data_block_quadlets;
}
}
int amdtp_ff_add_pcm_hw_constraints(struct amdtp_stream *s,
struct snd_pcm_runtime *runtime)
{
int err;
err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
if (err < 0)
return err;
return amdtp_stream_add_pcm_hw_constraints(s, runtime);
}
static unsigned int process_rx_data_blocks(struct amdtp_stream *s,
__be32 *buffer,
unsigned int data_blocks,
unsigned int *syt)
{
struct snd_pcm_substream *pcm = ACCESS_ONCE(s->pcm);
unsigned int pcm_frames;
if (pcm) {
write_pcm_s32(s, pcm, (__le32 *)buffer, data_blocks);
pcm_frames = data_blocks;
} else {
write_pcm_silence(s, (__le32 *)buffer, data_blocks);
pcm_frames = 0;
}
return pcm_frames;
}
static unsigned int process_tx_data_blocks(struct amdtp_stream *s,
__be32 *buffer,
unsigned int data_blocks,
unsigned int *syt)
{
struct snd_pcm_substream *pcm = ACCESS_ONCE(s->pcm);
unsigned int pcm_frames;
if (pcm) {
read_pcm_s32(s, pcm, (__le32 *)buffer, data_blocks);
pcm_frames = data_blocks;
} else {
pcm_frames = 0;
}
return pcm_frames;
}
int amdtp_ff_init(struct amdtp_stream *s, struct fw_unit *unit,
enum amdtp_stream_direction dir)
{
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, CIP_NO_HEADER, 0,
process_data_blocks, sizeof(struct amdtp_ff));
}
/*
* ff-hwdep.c - a part of driver for RME Fireface series
*
* Copyright (c) 2015-2017 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
/*
* This codes give three functionality.
*
* 1.get firewire node information
* 2.get notification about starting/stopping stream
* 3.lock/unlock stream
*/
#include "ff.h"
static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
loff_t *offset)
{
struct snd_ff *ff = hwdep->private_data;
DEFINE_WAIT(wait);
union snd_firewire_event event;
spin_lock_irq(&ff->lock);
while (!ff->dev_lock_changed) {
prepare_to_wait(&ff->hwdep_wait, &wait, TASK_INTERRUPTIBLE);
spin_unlock_irq(&ff->lock);
schedule();
finish_wait(&ff->hwdep_wait, &wait);
if (signal_pending(current))
return -ERESTARTSYS;
spin_lock_irq(&ff->lock);
}
memset(&event, 0, sizeof(event));
if (ff->dev_lock_changed) {
event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS;
event.lock_status.status = (ff->dev_lock_count > 0);
ff->dev_lock_changed = false;
count = min_t(long, count, sizeof(event.lock_status));
}
spin_unlock_irq(&ff->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_ff *ff = hwdep->private_data;
unsigned int events;
poll_wait(file, &ff->hwdep_wait, wait);
spin_lock_irq(&ff->lock);
if (ff->dev_lock_changed)
events = POLLIN | POLLRDNORM;
else
events = 0;
spin_unlock_irq(&ff->lock);
return events;
}
static int hwdep_get_info(struct snd_ff *ff, void __user *arg)
{
struct fw_device *dev = fw_parent_device(ff->unit);
struct snd_firewire_get_info info;
memset(&info, 0, sizeof(info));
info.type = SNDRV_FIREWIRE_TYPE_FIREFACE;
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_ff *ff)
{
int err;
spin_lock_irq(&ff->lock);
if (ff->dev_lock_count == 0) {
ff->dev_lock_count = -1;
err = 0;
} else {
err = -EBUSY;
}
spin_unlock_irq(&ff->lock);
return err;
}
static int hwdep_unlock(struct snd_ff *ff)
{
int err;
spin_lock_irq(&ff->lock);
if (ff->dev_lock_count == -1) {
ff->dev_lock_count = 0;
err = 0;
} else {
err = -EBADFD;
}
spin_unlock_irq(&ff->lock);
return err;
}
static int hwdep_release(struct snd_hwdep *hwdep, struct file *file)
{
struct snd_ff *ff = hwdep->private_data;
spin_lock_irq(&ff->lock);
if (ff->dev_lock_count == -1)
ff->dev_lock_count = 0;
spin_unlock_irq(&ff->lock);
return 0;
}
static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file,
unsigned int cmd, unsigned long arg)
{
struct snd_ff *ff = hwdep->private_data;
switch (cmd) {
case SNDRV_FIREWIRE_IOCTL_GET_INFO:
return hwdep_get_info(ff, (void __user *)arg);
case SNDRV_FIREWIRE_IOCTL_LOCK:
return hwdep_lock(ff);
case SNDRV_FIREWIRE_IOCTL_UNLOCK:
return hwdep_unlock(ff);
default:
return -ENOIOCTLCMD;
}
}
#ifdef CONFIG_COMPAT
static int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file,
unsigned int cmd, unsigned long arg)
{
return hwdep_ioctl(hwdep, file, cmd,
(unsigned long)compat_ptr(arg));
}
#else
#define hwdep_compat_ioctl NULL
#endif
int snd_ff_create_hwdep_devices(struct snd_ff *ff)
{
static const struct snd_hwdep_ops hwdep_ops = {
.read = hwdep_read,
.release = hwdep_release,
.poll = hwdep_poll,
.ioctl = hwdep_ioctl,
.ioctl_compat = hwdep_compat_ioctl,
};
struct snd_hwdep *hwdep;
int err;
err = snd_hwdep_new(ff->card, ff->card->driver, 0, &hwdep);
if (err < 0)
return err;
strcpy(hwdep->name, ff->card->driver);
hwdep->iface = SNDRV_HWDEP_IFACE_FW_FIREFACE;
hwdep->ops = hwdep_ops;
hwdep->private_data = ff;
hwdep->exclusive = true;
return 0;
}
/*
* ff-midi.c - a part of driver for RME Fireface series
*
* Copyright (c) 2015-2017 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include "ff.h"
static int midi_capture_open(struct snd_rawmidi_substream *substream)
{
/* Do nothing. */
return 0;
}
static int midi_playback_open(struct snd_rawmidi_substream *substream)
{
struct snd_ff *ff = substream->rmidi->private_data;
/* Initialize internal status. */
ff->running_status[substream->number] = 0;
ff->rx_midi_error[substream->number] = false;
ACCESS_ONCE(ff->rx_midi_substreams[substream->number]) = substream;
return 0;
}
static int midi_capture_close(struct snd_rawmidi_substream *substream)
{
/* Do nothing. */
return 0;
}
static int midi_playback_close(struct snd_rawmidi_substream *substream)
{
struct snd_ff *ff = substream->rmidi->private_data;
cancel_work_sync(&ff->rx_midi_work[substream->number]);
ACCESS_ONCE(ff->rx_midi_substreams[substream->number]) = NULL;
return 0;
}
static void midi_capture_trigger(struct snd_rawmidi_substream *substream,
int up)
{
struct snd_ff *ff = substream->rmidi->private_data;
unsigned long flags;
spin_lock_irqsave(&ff->lock, flags);
if (up)
ACCESS_ONCE(ff->tx_midi_substreams[substream->number]) =
substream;
else
ACCESS_ONCE(ff->tx_midi_substreams[substream->number]) = NULL;
spin_unlock_irqrestore(&ff->lock, flags);
}
static void midi_playback_trigger(struct snd_rawmidi_substream *substream,
int up)
{
struct snd_ff *ff = substream->rmidi->private_data;
unsigned long flags;
spin_lock_irqsave(&ff->lock, flags);
if (up || !ff->rx_midi_error[substream->number])
schedule_work(&ff->rx_midi_work[substream->number]);
spin_unlock_irqrestore(&ff->lock, flags);
}
static struct snd_rawmidi_ops midi_capture_ops = {
.open = midi_capture_open,
.close = midi_capture_close,
.trigger = midi_capture_trigger,
};
static struct snd_rawmidi_ops midi_playback_ops = {
.open = midi_playback_open,
.close = midi_playback_close,
.trigger = midi_playback_trigger,
};
static void set_midi_substream_names(struct snd_rawmidi_str *stream,
const char *const name)
{
struct snd_rawmidi_substream *substream;
list_for_each_entry(substream, &stream->substreams, list) {
snprintf(substream->name, sizeof(substream->name),
"%s MIDI %d", name, substream->number + 1);
}
}
int snd_ff_create_midi_devices(struct snd_ff *ff)
{
struct snd_rawmidi *rmidi;
struct snd_rawmidi_str *stream;
int err;
err = snd_rawmidi_new(ff->card, ff->card->driver, 0,
ff->spec->midi_out_ports, ff->spec->midi_in_ports,
&rmidi);
if (err < 0)
return err;
snprintf(rmidi->name, sizeof(rmidi->name),
"%s MIDI", ff->card->shortname);
rmidi->private_data = ff;
rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
&midi_capture_ops);
stream = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT];
set_midi_substream_names(stream, ff->card->shortname);
rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
&midi_playback_ops);
stream = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT];
set_midi_substream_names(stream, ff->card->shortname);
rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX;
return 0;
}
/*
* ff-pcm.c - a part of driver for RME Fireface series
*
* Copyright (c) 2015-2017 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include "ff.h"
static inline unsigned int get_multiplier_mode_with_index(unsigned int index)
{
return ((int)index - 1) / 2;
}
static int hw_rule_rate(struct snd_pcm_hw_params *params,
struct snd_pcm_hw_rule *rule)
{
const unsigned int *pcm_channels = rule->private;
struct snd_interval *r =
hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
const struct snd_interval *c =
hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS);
struct snd_interval t = {
.min = UINT_MAX, .max = 0, .integer = 1
};
unsigned int i, mode;
for (i = 0; i < ARRAY_SIZE(amdtp_rate_table); i++) {
mode = get_multiplier_mode_with_index(i);
if (!snd_interval_test(c, pcm_channels[mode]))
continue;
t.min = min(t.min, amdtp_rate_table[i]);
t.max = max(t.max, amdtp_rate_table[i]);
}
return snd_interval_refine(r, &t);
}
static int hw_rule_channels(struct snd_pcm_hw_params *params,
struct snd_pcm_hw_rule *rule)
{
const unsigned int *pcm_channels = rule->private;
struct snd_interval *c =
hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
const struct snd_interval *r =
hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE);
struct snd_interval t = {
.min = UINT_MAX, .max = 0, .integer = 1
};
unsigned int i, mode;
for (i = 0; i < ARRAY_SIZE(amdtp_rate_table); i++) {
mode = get_multiplier_mode_with_index(i);
if (!snd_interval_test(r, amdtp_rate_table[i]))
continue;
t.min = min(t.min, pcm_channels[mode]);
t.max = max(t.max, pcm_channels[mode]);
}
return snd_interval_refine(c, &t);
}
static void limit_channels_and_rates(struct snd_pcm_hardware *hw,
const unsigned int *pcm_channels)
{
unsigned int mode;
unsigned int rate, channels;
int i;
hw->channels_min = UINT_MAX;
hw->channels_max = 0;
hw->rate_min = UINT_MAX;
hw->rate_max = 0;
for (i = 0; i < ARRAY_SIZE(amdtp_rate_table); i++) {
mode = get_multiplier_mode_with_index(i);
channels = pcm_channels[mode];
if (pcm_channels[mode] == 0)
continue;
hw->channels_min = min(hw->channels_min, channels);
hw->channels_max = max(hw->channels_max, channels);
rate = amdtp_rate_table[i];
hw->rates |= snd_pcm_rate_to_rate_bit(rate);
hw->rate_min = min(hw->rate_min, rate);
hw->rate_max = max(hw->rate_max, rate);
}
}
static void limit_period_and_buffer(struct snd_pcm_hardware *hw)
{
hw->periods_min = 2; /* SNDRV_PCM_INFO_BATCH */
hw->periods_max = UINT_MAX;
hw->period_bytes_min = 4 * hw->channels_max; /* bytes for a frame */
/* Just to prevent from allocating much pages. */
hw->period_bytes_max = hw->period_bytes_min * 2048;
hw->buffer_bytes_max = hw->period_bytes_max * hw->periods_min;
}
static int pcm_init_hw_params(struct snd_ff *ff,
struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct amdtp_stream *s;
const unsigned int *pcm_channels;
int err;
runtime->hw.info = SNDRV_PCM_INFO_BATCH |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_JOINT_DUPLEX |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID;
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
runtime->hw.formats = SNDRV_PCM_FMTBIT_S32;
s = &ff->tx_stream;
pcm_channels = ff->spec->pcm_capture_channels;
} else {
runtime->hw.formats = SNDRV_PCM_FMTBIT_S32;
s = &ff->rx_stream;
pcm_channels = ff->spec->pcm_playback_channels;
}
/* limit rates */
limit_channels_and_rates(&runtime->hw, pcm_channels);
limit_period_and_buffer(&runtime->hw);
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
hw_rule_channels, (void *)pcm_channels,
SNDRV_PCM_HW_PARAM_RATE, -1);
if (err < 0)
return err;
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
hw_rule_rate, (void *)pcm_channels,
SNDRV_PCM_HW_PARAM_CHANNELS, -1);
if (err < 0)
return err;
return amdtp_ff_add_pcm_hw_constraints(s, runtime);
}
static int pcm_open(struct snd_pcm_substream *substream)
{
struct snd_ff *ff = substream->private_data;
unsigned int rate;
enum snd_ff_clock_src src;
int i, err;
err = snd_ff_stream_lock_try(ff);
if (err < 0)
return err;
err = pcm_init_hw_params(ff, substream);
if (err < 0) {
snd_ff_stream_lock_release(ff);
return err;
}
err = ff->spec->protocol->get_clock(ff, &rate, &src);
if (err < 0) {
snd_ff_stream_lock_release(ff);
return err;
}
if (src != SND_FF_CLOCK_SRC_INTERNAL) {
for (i = 0; i < CIP_SFC_COUNT; ++i) {
if (amdtp_rate_table[i] == rate)
break;
}
/*
* The unit is configured at sampling frequency which packet
* streaming engine can't support.
*/
if (i >= CIP_SFC_COUNT) {
snd_ff_stream_lock_release(ff);
return -EIO;
}
substream->runtime->hw.rate_min = rate;
substream->runtime->hw.rate_max = rate;
} else {
if (amdtp_stream_pcm_running(&ff->rx_stream) ||
amdtp_stream_pcm_running(&ff->tx_stream)) {
rate = amdtp_rate_table[ff->rx_stream.sfc];
substream->runtime->hw.rate_min = rate;
substream->runtime->hw.rate_max = rate;
}
}
snd_pcm_set_sync(substream);
return 0;
}
static int pcm_close(struct snd_pcm_substream *substream)
{
struct snd_ff *ff = substream->private_data;
snd_ff_stream_lock_release(ff);
return 0;
}
static int pcm_capture_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
struct snd_ff *ff = 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(&ff->mutex);
ff->substreams_counter++;
mutex_unlock(&ff->mutex);
}
return 0;
}
static int pcm_playback_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
struct snd_ff *ff = 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(&ff->mutex);
ff->substreams_counter++;
mutex_unlock(&ff->mutex);
}
return 0;
}
static int pcm_capture_hw_free(struct snd_pcm_substream *substream)
{
struct snd_ff *ff = substream->private_data;
mutex_lock(&ff->mutex);
if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
ff->substreams_counter--;
snd_ff_stream_stop_duplex(ff);
mutex_unlock(&ff->mutex);
return snd_pcm_lib_free_vmalloc_buffer(substream);
}
static int pcm_playback_hw_free(struct snd_pcm_substream *substream)
{
struct snd_ff *ff = substream->private_data;
mutex_lock(&ff->mutex);
if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
ff->substreams_counter--;
snd_ff_stream_stop_duplex(ff);
mutex_unlock(&ff->mutex);
return snd_pcm_lib_free_vmalloc_buffer(substream);
}
static int pcm_capture_prepare(struct snd_pcm_substream *substream)
{
struct snd_ff *ff = substream->private_data;
struct snd_pcm_runtime *runtime = substream->runtime;
int err;
mutex_lock(&ff->mutex);
err = snd_ff_stream_start_duplex(ff, runtime->rate);
if (err >= 0)
amdtp_stream_pcm_prepare(&ff->tx_stream);
mutex_unlock(&ff->mutex);
return err;
}
static int pcm_playback_prepare(struct snd_pcm_substream *substream)
{
struct snd_ff *ff = substream->private_data;
struct snd_pcm_runtime *runtime = substream->runtime;
int err;
mutex_lock(&ff->mutex);
err = snd_ff_stream_start_duplex(ff, runtime->rate);
if (err >= 0)
amdtp_stream_pcm_prepare(&ff->rx_stream);
mutex_unlock(&ff->mutex);
return err;
}
static int pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_ff *ff = substream->private_data;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
amdtp_stream_pcm_trigger(&ff->tx_stream, substream);
break;
case SNDRV_PCM_TRIGGER_STOP:
amdtp_stream_pcm_trigger(&ff->tx_stream, NULL);
break;
default:
return -EINVAL;
}
return 0;
}
static int pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_ff *ff = substream->private_data;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
amdtp_stream_pcm_trigger(&ff->rx_stream, substream);
break;
case SNDRV_PCM_TRIGGER_STOP:
amdtp_stream_pcm_trigger(&ff->rx_stream, NULL);
break;
default:
return -EINVAL;
}
return 0;
}
static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstrm)
{
struct snd_ff *ff = sbstrm->private_data;
return amdtp_stream_pcm_pointer(&ff->tx_stream);
}
static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstrm)
{
struct snd_ff *ff = sbstrm->private_data;
return amdtp_stream_pcm_pointer(&ff->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_ff_create_pcm_devices(struct snd_ff *ff)
{
struct snd_pcm *pcm;
int err;
err = snd_pcm_new(ff->card, ff->card->driver, 0, 1, 1, &pcm);
if (err < 0)
return err;
pcm->private_data = ff;
snprintf(pcm->name, sizeof(pcm->name),
"%s PCM", ff->card->shortname);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_playback_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pcm_capture_ops);
return 0;
}
/*
* ff-proc.c - a part of driver for RME Fireface series
*
* Copyright (c) 2015-2017 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include "./ff.h"
static void proc_dump_clock_config(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_ff *ff = entry->private_data;
ff->spec->protocol->dump_clock_config(ff, buffer);
}
static void proc_dump_sync_status(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_ff *ff = entry->private_data;
ff->spec->protocol->dump_sync_status(ff, buffer);
}
static void add_node(struct snd_ff *ff, struct snd_info_entry *root,
const char *name,
void (*op)(struct snd_info_entry *e,
struct snd_info_buffer *b))
{
struct snd_info_entry *entry;
entry = snd_info_create_card_entry(ff->card, name, root);
if (entry == NULL)
return;
snd_info_set_text_ops(entry, ff, op);
if (snd_info_register(entry) < 0)
snd_info_free_entry(entry);
}
void snd_ff_proc_init(struct snd_ff *ff)
{
struct snd_info_entry *root;
/*
* All nodes are automatically removed at snd_card_disconnect(),
* by following to link list.
*/
root = snd_info_create_card_entry(ff->card, "firewire",
ff->card->proc_root);
if (root == NULL)
return;
root->mode = S_IFDIR | S_IRUGO | S_IXUGO;
if (snd_info_register(root) < 0) {
snd_info_free_entry(root);
return;
}
add_node(ff, root, "clock-config", proc_dump_clock_config);
add_node(ff, root, "sync-status", proc_dump_sync_status);
}
/*
* ff-protocol-ff400.c - a part of driver for RME Fireface series
*
* Copyright (c) 2015-2017 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include <linux/delay.h>
#include "ff.h"
#define FF400_STF 0x000080100500ull
#define FF400_RX_PACKET_FORMAT 0x000080100504ull
#define FF400_ISOC_COMM_START 0x000080100508ull
#define FF400_TX_PACKET_FORMAT 0x00008010050cull
#define FF400_ISOC_COMM_STOP 0x000080100510ull
#define FF400_SYNC_STATUS 0x0000801c0000ull
#define FF400_FETCH_PCM_FRAMES 0x0000801c0000ull /* For block request. */
#define FF400_CLOCK_CONFIG 0x0000801c0004ull
#define FF400_MIDI_HIGH_ADDR 0x0000801003f4ull
#define FF400_MIDI_RX_PORT_0 0x000080180000ull
#define FF400_MIDI_RX_PORT_1 0x000080190000ull
static int ff400_get_clock(struct snd_ff *ff, unsigned int *rate,
enum snd_ff_clock_src *src)
{
__le32 reg;
u32 data;
int err;
err = snd_fw_transaction(ff->unit, TCODE_READ_QUADLET_REQUEST,
FF400_SYNC_STATUS, &reg, sizeof(reg), 0);
if (err < 0)
return err;
data = le32_to_cpu(reg);
/* Calculate sampling rate. */
switch ((data >> 1) & 0x03) {
case 0x01:
*rate = 32000;
break;
case 0x00:
*rate = 44100;
break;
case 0x03:
*rate = 48000;
break;
case 0x02:
default:
return -EIO;
}
if (data & 0x08)
*rate *= 2;
else if (data & 0x10)
*rate *= 4;
/* Calculate source of clock. */
if (data & 0x01) {
*src = SND_FF_CLOCK_SRC_INTERNAL;
} else {
/* TODO: 0x00, 0x01, 0x02, 0x06, 0x07? */
switch ((data >> 10) & 0x07) {
case 0x03:
*src = SND_FF_CLOCK_SRC_SPDIF;
break;
case 0x04:
*src = SND_FF_CLOCK_SRC_WORD;
break;
case 0x05:
*src = SND_FF_CLOCK_SRC_LTC;
break;
case 0x00:
default:
*src = SND_FF_CLOCK_SRC_ADAT;
break;
}
}
return 0;
}
static int ff400_begin_session(struct snd_ff *ff, unsigned int rate)
{
__le32 reg;
int i, err;
/* Check whether the given value is supported or not. */
for (i = 0; i < CIP_SFC_COUNT; i++) {
if (amdtp_rate_table[i] == rate)
break;
}
if (i == CIP_SFC_COUNT)
return -EINVAL;
/* Set the number of data blocks transferred in a second. */
reg = cpu_to_le32(rate);
err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
FF400_STF, &reg, sizeof(reg), 0);
if (err < 0)
return err;
msleep(100);
/*
* Set isochronous channel and the number of quadlets of received
* packets.
*/
reg = cpu_to_le32(((ff->rx_stream.data_block_quadlets << 3) << 8) |
ff->rx_resources.channel);
err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
FF400_RX_PACKET_FORMAT, &reg, sizeof(reg), 0);
if (err < 0)
return err;
/*
* Set isochronous channel and the number of quadlets of transmitted
* packet.
*/
/* TODO: investigate the purpose of this 0x80. */
reg = cpu_to_le32((0x80 << 24) |
(ff->tx_resources.channel << 5) |
(ff->tx_stream.data_block_quadlets));
err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
FF400_TX_PACKET_FORMAT, &reg, sizeof(reg), 0);
if (err < 0)
return err;
/* Allow to transmit packets. */
reg = cpu_to_le32(0x00000001);
return snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
FF400_ISOC_COMM_START, &reg, sizeof(reg), 0);
}
static void ff400_finish_session(struct snd_ff *ff)
{
__le32 reg;
reg = cpu_to_le32(0x80000000);
snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
FF400_ISOC_COMM_STOP, &reg, sizeof(reg), 0);
}
static int ff400_switch_fetching_mode(struct snd_ff *ff, bool enable)
{
__le32 *reg;
int i;
reg = kzalloc(sizeof(__le32) * 18, GFP_KERNEL);
if (reg == NULL)
return -ENOMEM;
if (enable) {
/*
* Each quadlet is corresponding to data channels in a data
* blocks in reverse order. Precisely, quadlets for available
* data channels should be enabled. Here, I take second best
* to fetch PCM frames from all of data channels regardless of
* stf.
*/
for (i = 0; i < 18; ++i)
reg[i] = cpu_to_le32(0x00000001);
}
return snd_fw_transaction(ff->unit, TCODE_WRITE_BLOCK_REQUEST,
FF400_FETCH_PCM_FRAMES, reg,
sizeof(__le32) * 18, 0);
}
static void ff400_dump_sync_status(struct snd_ff *ff,
struct snd_info_buffer *buffer)
{
__le32 reg;
u32 data;
int err;
err = snd_fw_transaction(ff->unit, TCODE_READ_QUADLET_REQUEST,
FF400_SYNC_STATUS, &reg, sizeof(reg), 0);
if (err < 0)
return;
data = le32_to_cpu(reg);
snd_iprintf(buffer, "External source detection:\n");
snd_iprintf(buffer, "Word Clock:");
if ((data >> 24) & 0x20) {
if ((data >> 24) & 0x40)
snd_iprintf(buffer, "sync\n");
else
snd_iprintf(buffer, "lock\n");
} else {
snd_iprintf(buffer, "none\n");
}
snd_iprintf(buffer, "S/PDIF:");
if ((data >> 16) & 0x10) {
if ((data >> 16) & 0x04)
snd_iprintf(buffer, "sync\n");
else
snd_iprintf(buffer, "lock\n");
} else {
snd_iprintf(buffer, "none\n");
}
snd_iprintf(buffer, "ADAT:");
if ((data >> 8) & 0x04) {
if ((data >> 8) & 0x10)
snd_iprintf(buffer, "sync\n");
else
snd_iprintf(buffer, "lock\n");
} else {
snd_iprintf(buffer, "none\n");
}
snd_iprintf(buffer, "\nUsed external source:\n");
if (((data >> 22) & 0x07) == 0x07) {
snd_iprintf(buffer, "None\n");
} else {
switch ((data >> 22) & 0x07) {
case 0x00:
snd_iprintf(buffer, "ADAT:");
break;
case 0x03:
snd_iprintf(buffer, "S/PDIF:");
break;
case 0x04:
snd_iprintf(buffer, "Word:");
break;
case 0x07:
snd_iprintf(buffer, "Nothing:");
break;
case 0x01:
case 0x02:
case 0x05:
case 0x06:
default:
snd_iprintf(buffer, "unknown:");
break;
}
if ((data >> 25) & 0x07) {
switch ((data >> 25) & 0x07) {
case 0x01:
snd_iprintf(buffer, "32000\n");
break;
case 0x02:
snd_iprintf(buffer, "44100\n");
break;
case 0x03:
snd_iprintf(buffer, "48000\n");
break;
case 0x04:
snd_iprintf(buffer, "64000\n");
break;
case 0x05:
snd_iprintf(buffer, "88200\n");
break;
case 0x06:
snd_iprintf(buffer, "96000\n");
break;
case 0x07:
snd_iprintf(buffer, "128000\n");
break;
case 0x08:
snd_iprintf(buffer, "176400\n");
break;
case 0x09:
snd_iprintf(buffer, "192000\n");
break;
case 0x00:
snd_iprintf(buffer, "unknown\n");
break;
}
}
}
snd_iprintf(buffer, "Multiplied:");
snd_iprintf(buffer, "%d\n", (data & 0x3ff) * 250);
}
static void ff400_dump_clock_config(struct snd_ff *ff,
struct snd_info_buffer *buffer)
{
__le32 reg;
u32 data;
unsigned int rate;
const char *src;
int err;
err = snd_fw_transaction(ff->unit, TCODE_READ_BLOCK_REQUEST,
FF400_CLOCK_CONFIG, &reg, sizeof(reg), 0);
if (err < 0)
return;
data = le32_to_cpu(reg);
snd_iprintf(buffer, "Output S/PDIF format: %s (Emphasis: %s)\n",
(data & 0x20) ? "Professional" : "Consumer",
(data & 0x40) ? "on" : "off");
snd_iprintf(buffer, "Optical output interface format: %s\n",
((data >> 8) & 0x01) ? "S/PDIF" : "ADAT");
snd_iprintf(buffer, "Word output single speed: %s\n",
((data >> 8) & 0x20) ? "on" : "off");
snd_iprintf(buffer, "S/PDIF input interface: %s\n",
((data >> 8) & 0x02) ? "Optical" : "Coaxial");
switch ((data >> 1) & 0x03) {
case 0x01:
rate = 32000;
break;
case 0x00:
rate = 44100;
break;
case 0x03:
rate = 48000;
break;
case 0x02:
default:
return;
}
if (data & 0x08)
rate *= 2;
else if (data & 0x10)
rate *= 4;
snd_iprintf(buffer, "Sampling rate: %d\n", rate);
if (data & 0x01) {
src = "Internal";
} else {
switch ((data >> 10) & 0x07) {
case 0x00:
src = "ADAT";
break;
case 0x03:
src = "S/PDIF";
break;
case 0x04:
src = "Word";
break;
case 0x05:
src = "LTC";
break;
default:
return;
}
}
snd_iprintf(buffer, "Sync to clock source: %s\n", src);
}
struct snd_ff_protocol snd_ff_protocol_ff400 = {
.get_clock = ff400_get_clock,
.begin_session = ff400_begin_session,
.finish_session = ff400_finish_session,
.switch_fetching_mode = ff400_switch_fetching_mode,
.dump_sync_status = ff400_dump_sync_status,
.dump_clock_config = ff400_dump_clock_config,
.midi_high_addr_reg = FF400_MIDI_HIGH_ADDR,
.midi_rx_port_0_reg = FF400_MIDI_RX_PORT_0,
.midi_rx_port_1_reg = FF400_MIDI_RX_PORT_1,
};
/*
* ff-stream.c - a part of driver for RME Fireface series
*
* Copyright (c) 2015-2017 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include "ff.h"
#define CALLBACK_TIMEOUT_MS 200
static int get_rate_mode(unsigned int rate, unsigned int *mode)
{
int i;
for (i = 0; i < CIP_SFC_COUNT; i++) {
if (amdtp_rate_table[i] == rate)
break;
}
if (i == CIP_SFC_COUNT)
return -EINVAL;
*mode = ((int)i - 1) / 2;
return 0;
}
/*
* Fireface 400 manages isochronous channel number in 3 bit field. Therefore,
* we can allocate between 0 and 7 channel.
*/
static int keep_resources(struct snd_ff *ff, unsigned int rate)
{
int mode;
int err;
err = get_rate_mode(rate, &mode);
if (err < 0)
return err;
/* Keep resources for in-stream. */
err = amdtp_ff_set_parameters(&ff->tx_stream, rate,
ff->spec->pcm_capture_channels[mode]);
if (err < 0)
return err;
ff->tx_resources.channels_mask = 0x00000000000000ffuLL;
err = fw_iso_resources_allocate(&ff->tx_resources,
amdtp_stream_get_max_payload(&ff->tx_stream),
fw_parent_device(ff->unit)->max_speed);
if (err < 0)
return err;
/* Keep resources for out-stream. */
err = amdtp_ff_set_parameters(&ff->rx_stream, rate,
ff->spec->pcm_playback_channels[mode]);
if (err < 0)
return err;
ff->rx_resources.channels_mask = 0x00000000000000ffuLL;
err = fw_iso_resources_allocate(&ff->rx_resources,
amdtp_stream_get_max_payload(&ff->rx_stream),
fw_parent_device(ff->unit)->max_speed);
if (err < 0)
fw_iso_resources_free(&ff->tx_resources);
return err;
}
static void release_resources(struct snd_ff *ff)
{
fw_iso_resources_free(&ff->tx_resources);
fw_iso_resources_free(&ff->rx_resources);
}
static inline void finish_session(struct snd_ff *ff)
{
ff->spec->protocol->finish_session(ff);
ff->spec->protocol->switch_fetching_mode(ff, false);
}
static int init_stream(struct snd_ff *ff, enum amdtp_stream_direction dir)
{
int err;
struct fw_iso_resources *resources;
struct amdtp_stream *stream;
if (dir == AMDTP_IN_STREAM) {
resources = &ff->tx_resources;
stream = &ff->tx_stream;
} else {
resources = &ff->rx_resources;
stream = &ff->rx_stream;
}
err = fw_iso_resources_init(resources, ff->unit);
if (err < 0)
return err;
err = amdtp_ff_init(stream, ff->unit, dir);
if (err < 0)
fw_iso_resources_destroy(resources);
return err;
}
static void destroy_stream(struct snd_ff *ff, enum amdtp_stream_direction dir)
{
if (dir == AMDTP_IN_STREAM) {
amdtp_stream_destroy(&ff->tx_stream);
fw_iso_resources_destroy(&ff->tx_resources);
} else {
amdtp_stream_destroy(&ff->rx_stream);
fw_iso_resources_destroy(&ff->rx_resources);
}
}
int snd_ff_stream_init_duplex(struct snd_ff *ff)
{
int err;
err = init_stream(ff, AMDTP_OUT_STREAM);
if (err < 0)
goto end;
err = init_stream(ff, AMDTP_IN_STREAM);
if (err < 0)
destroy_stream(ff, AMDTP_OUT_STREAM);
end:
return err;
}
/*
* This function should be called before starting streams or after stopping
* streams.
*/
void snd_ff_stream_destroy_duplex(struct snd_ff *ff)
{
destroy_stream(ff, AMDTP_IN_STREAM);
destroy_stream(ff, AMDTP_OUT_STREAM);
}
int snd_ff_stream_start_duplex(struct snd_ff *ff, unsigned int rate)
{
unsigned int curr_rate;
enum snd_ff_clock_src src;
int err;
if (ff->substreams_counter == 0)
return 0;
err = ff->spec->protocol->get_clock(ff, &curr_rate, &src);
if (err < 0)
return err;
if (curr_rate != rate ||
amdtp_streaming_error(&ff->tx_stream) ||
amdtp_streaming_error(&ff->rx_stream)) {
finish_session(ff);
amdtp_stream_stop(&ff->tx_stream);
amdtp_stream_stop(&ff->rx_stream);
release_resources(ff);
}
/*
* Regardless of current source of clock signal, drivers transfer some
* packets. Then, the device transfers packets.
*/
if (!amdtp_stream_running(&ff->rx_stream)) {
err = keep_resources(ff, rate);
if (err < 0)
goto error;
err = ff->spec->protocol->begin_session(ff, rate);
if (err < 0)
goto error;
err = amdtp_stream_start(&ff->rx_stream,
ff->rx_resources.channel,
fw_parent_device(ff->unit)->max_speed);
if (err < 0)
goto error;
if (!amdtp_stream_wait_callback(&ff->rx_stream,
CALLBACK_TIMEOUT_MS)) {
err = -ETIMEDOUT;
goto error;
}
err = ff->spec->protocol->switch_fetching_mode(ff, true);
if (err < 0)
goto error;
}
if (!amdtp_stream_running(&ff->tx_stream)) {
err = amdtp_stream_start(&ff->tx_stream,
ff->tx_resources.channel,
fw_parent_device(ff->unit)->max_speed);
if (err < 0)
goto error;
if (!amdtp_stream_wait_callback(&ff->tx_stream,
CALLBACK_TIMEOUT_MS)) {
err = -ETIMEDOUT;
goto error;
}
}
return 0;
error:
amdtp_stream_stop(&ff->tx_stream);
amdtp_stream_stop(&ff->rx_stream);
finish_session(ff);
release_resources(ff);
return err;
}
void snd_ff_stream_stop_duplex(struct snd_ff *ff)
{
if (ff->substreams_counter > 0)
return;
amdtp_stream_stop(&ff->tx_stream);
amdtp_stream_stop(&ff->rx_stream);
finish_session(ff);
release_resources(ff);
}
void snd_ff_stream_update_duplex(struct snd_ff *ff)
{
/* The device discontinue to transfer packets. */
amdtp_stream_pcm_abort(&ff->tx_stream);
amdtp_stream_stop(&ff->tx_stream);
amdtp_stream_pcm_abort(&ff->rx_stream);
amdtp_stream_stop(&ff->rx_stream);
fw_iso_resources_update(&ff->tx_resources);
fw_iso_resources_update(&ff->rx_resources);
}
void snd_ff_stream_lock_changed(struct snd_ff *ff)
{
ff->dev_lock_changed = true;
wake_up(&ff->hwdep_wait);
}
int snd_ff_stream_lock_try(struct snd_ff *ff)
{
int err;
spin_lock_irq(&ff->lock);
/* user land lock this */
if (ff->dev_lock_count < 0) {
err = -EBUSY;
goto end;
}
/* this is the first time */
if (ff->dev_lock_count++ == 0)
snd_ff_stream_lock_changed(ff);
err = 0;
end:
spin_unlock_irq(&ff->lock);
return err;
}
void snd_ff_stream_lock_release(struct snd_ff *ff)
{
spin_lock_irq(&ff->lock);
if (WARN_ON(ff->dev_lock_count <= 0))
goto end;
if (--ff->dev_lock_count == 0)
snd_ff_stream_lock_changed(ff);
end:
spin_unlock_irq(&ff->lock);
}
/*
* ff-transaction.c - a part of driver for RME Fireface series
*
* Copyright (c) 2015-2017 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include "ff.h"
static void finish_transmit_midi_msg(struct snd_ff *ff, unsigned int port,
int rcode)
{
struct snd_rawmidi_substream *substream =
ACCESS_ONCE(ff->rx_midi_substreams[port]);
if (rcode_is_permanent_error(rcode)) {
ff->rx_midi_error[port] = true;
return;
}
if (rcode != RCODE_COMPLETE) {
/* Transfer the message again, immediately. */
ff->next_ktime[port] = 0;
schedule_work(&ff->rx_midi_work[port]);
return;
}
snd_rawmidi_transmit_ack(substream, ff->rx_bytes[port]);
ff->rx_bytes[port] = 0;
if (!snd_rawmidi_transmit_empty(substream))
schedule_work(&ff->rx_midi_work[port]);
}
static void finish_transmit_midi0_msg(struct fw_card *card, int rcode,
void *data, size_t length,
void *callback_data)
{
struct snd_ff *ff =
container_of(callback_data, struct snd_ff, transactions[0]);
finish_transmit_midi_msg(ff, 0, rcode);
}
static void finish_transmit_midi1_msg(struct fw_card *card, int rcode,
void *data, size_t length,
void *callback_data)
{
struct snd_ff *ff =
container_of(callback_data, struct snd_ff, transactions[1]);
finish_transmit_midi_msg(ff, 1, rcode);
}
static inline void fill_midi_buf(struct snd_ff *ff, unsigned int port,
unsigned int index, u8 byte)
{
ff->msg_buf[port][index] = cpu_to_le32(byte);
}
static void transmit_midi_msg(struct snd_ff *ff, unsigned int port)
{
struct snd_rawmidi_substream *substream =
ACCESS_ONCE(ff->rx_midi_substreams[port]);
u8 *buf = (u8 *)ff->msg_buf[port];
int i, len;
struct fw_device *fw_dev = fw_parent_device(ff->unit);
unsigned long long addr;
int generation;
fw_transaction_callback_t callback;
if (substream == NULL || snd_rawmidi_transmit_empty(substream))
return;
if (ff->rx_bytes[port] > 0 || ff->rx_midi_error[port])
return;
/* Do it in next chance. */
if (ktime_after(ff->next_ktime[port], ktime_get())) {
schedule_work(&ff->rx_midi_work[port]);
return;
}
len = snd_rawmidi_transmit_peek(substream, buf,
SND_FF_MAXIMIM_MIDI_QUADS);
if (len <= 0)
return;
for (i = len - 1; i >= 0; i--)
fill_midi_buf(ff, port, i, buf[i]);
if (port == 0) {
addr = ff->spec->protocol->midi_rx_port_0_reg;
callback = finish_transmit_midi0_msg;
} else {
addr = ff->spec->protocol->midi_rx_port_1_reg;
callback = finish_transmit_midi1_msg;
}
/* Set interval to next transaction. */
ff->next_ktime[port] = ktime_add_ns(ktime_get(),
len * 8 * NSEC_PER_SEC / 31250);
ff->rx_bytes[port] = len;
/*
* 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 = fw_dev->generation;
smp_rmb();
fw_send_request(fw_dev->card, &ff->transactions[port],
TCODE_WRITE_BLOCK_REQUEST,
fw_dev->node_id, generation, fw_dev->max_speed,
addr, &ff->msg_buf[port], len * 4,
callback, &ff->transactions[port]);
}
static void transmit_midi0_msg(struct work_struct *work)
{
struct snd_ff *ff = container_of(work, struct snd_ff, rx_midi_work[0]);
transmit_midi_msg(ff, 0);
}
static void transmit_midi1_msg(struct work_struct *work)
{
struct snd_ff *ff = container_of(work, struct snd_ff, rx_midi_work[1]);
transmit_midi_msg(ff, 1);
}
static void handle_midi_msg(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_ff *ff = callback_data;
__le32 *buf = data;
u32 quad;
u8 byte;
unsigned int index;
struct snd_rawmidi_substream *substream;
int i;
fw_send_response(card, request, RCODE_COMPLETE);
for (i = 0; i < length / 4; i++) {
quad = le32_to_cpu(buf[i]);
/* Message in first port. */
/*
* This value may represent the index of this unit when the same
* units are on the same IEEE 1394 bus. This driver doesn't use
* it.
*/
index = (quad >> 8) & 0xff;
if (index > 0) {
substream = ACCESS_ONCE(ff->tx_midi_substreams[0]);
if (substream != NULL) {
byte = quad & 0xff;
snd_rawmidi_receive(substream, &byte, 1);
}
}
/* Message in second port. */
index = (quad >> 24) & 0xff;
if (index > 0) {
substream = ACCESS_ONCE(ff->tx_midi_substreams[1]);
if (substream != NULL) {
byte = (quad >> 16) & 0xff;
snd_rawmidi_receive(substream, &byte, 1);
}
}
}
}
static int allocate_own_address(struct snd_ff *ff, int i)
{
struct fw_address_region midi_msg_region;
int err;
ff->async_handler.length = SND_FF_MAXIMIM_MIDI_QUADS * 4;
ff->async_handler.address_callback = handle_midi_msg;
ff->async_handler.callback_data = ff;
midi_msg_region.start = 0x000100000000ull * i;
midi_msg_region.end = midi_msg_region.start + ff->async_handler.length;
err = fw_core_add_address_handler(&ff->async_handler, &midi_msg_region);
if (err >= 0) {
/* Controllers are allowed to register this region. */
if (ff->async_handler.offset & 0x0000ffffffff) {
fw_core_remove_address_handler(&ff->async_handler);
err = -EAGAIN;
}
}
return err;
}
/*
* The configuration to start asynchronous transactions for MIDI messages is in
* 0x'0000'8010'051c. This register includes the other options, thus this driver
* doesn't touch it and leaves the decision to userspace. The userspace MUST add
* 0x04000000 to write transactions to the register to receive any MIDI
* messages.
*
* Here, I just describe MIDI-related offsets of the register, in little-endian
* order.
*
* Controllers are allowed to register higher 4 bytes of address to receive
* the transactions. The register is 0x'0000'8010'03f4. On the other hand, the
* controllers are not allowed to register lower 4 bytes of the address. They
* are forced to select from 4 options by writing corresponding bits to
* 0x'0000'8010'051c.
*
* The 3rd-6th bits in MSB of this register are used to indicate lower 4 bytes
* of address to which the device transferrs the transactions.
* - 6th: 0x'....'....'0000'0180
* - 5th: 0x'....'....'0000'0100
* - 4th: 0x'....'....'0000'0080
* - 3rd: 0x'....'....'0000'0000
*
* This driver configure 0x'....'....'0000'0000 for units to receive MIDI
* messages. 3rd bit of the register should be configured, however this driver
* deligates this task to user space applications due to a restriction that
* this register is write-only and the other bits have own effects.
*
* The 1st and 2nd bits in LSB of this register are used to cancel transferring
* asynchronous transactions. These two bits have the same effect.
* - 1st/2nd: cancel transferring
*/
int snd_ff_transaction_reregister(struct snd_ff *ff)
{
struct fw_card *fw_card = fw_parent_device(ff->unit)->card;
u32 addr;
__le32 reg;
/*
* Controllers are allowed to register its node ID and upper 2 byte of
* local address to listen asynchronous transactions.
*/
addr = (fw_card->node_id << 16) | (ff->async_handler.offset >> 32);
reg = cpu_to_le32(addr);
return snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
ff->spec->protocol->midi_high_addr_reg,
&reg, sizeof(reg), 0);
}
int snd_ff_transaction_register(struct snd_ff *ff)
{
int i, err;
/*
* Allocate in Memory Space of IEC 13213, but lower 4 byte in LSB should
* be zero due to device specification.
*/
for (i = 0; i < 0xffff; i++) {
err = allocate_own_address(ff, i);
if (err != -EBUSY && err != -EAGAIN)
break;
}
if (err < 0)
return err;
err = snd_ff_transaction_reregister(ff);
if (err < 0)
return err;
INIT_WORK(&ff->rx_midi_work[0], transmit_midi0_msg);
INIT_WORK(&ff->rx_midi_work[1], transmit_midi1_msg);
return 0;
}
void snd_ff_transaction_unregister(struct snd_ff *ff)
{
__le32 reg;
if (ff->async_handler.callback_data == NULL)
return;
ff->async_handler.callback_data = NULL;
/* Release higher 4 bytes of address. */
reg = cpu_to_le32(0x00000000);
snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
ff->spec->protocol->midi_high_addr_reg,
&reg, sizeof(reg), 0);
fw_core_remove_address_handler(&ff->async_handler);
}
/*
* ff.c - a part of driver for RME Fireface series
*
* Copyright (c) 2015-2017 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include "ff.h"
#define OUI_RME 0x000a35
MODULE_DESCRIPTION("RME Fireface series Driver");
MODULE_AUTHOR("Takashi Sakamoto <o-takashi@sakamocchi.jp>");
MODULE_LICENSE("GPL v2");
static void name_card(struct snd_ff *ff)
{
struct fw_device *fw_dev = fw_parent_device(ff->unit);
strcpy(ff->card->driver, "Fireface");
strcpy(ff->card->shortname, ff->spec->name);
strcpy(ff->card->mixername, ff->spec->name);
snprintf(ff->card->longname, sizeof(ff->card->longname),
"RME %s, GUID %08x%08x at %s, S%d", ff->spec->name,
fw_dev->config_rom[3], fw_dev->config_rom[4],
dev_name(&ff->unit->device), 100 << fw_dev->max_speed);
}
static void ff_free(struct snd_ff *ff)
{
snd_ff_stream_destroy_duplex(ff);
snd_ff_transaction_unregister(ff);
fw_unit_put(ff->unit);
mutex_destroy(&ff->mutex);
kfree(ff);
}
static void ff_card_free(struct snd_card *card)
{
ff_free(card->private_data);
}
static void do_registration(struct work_struct *work)
{
struct snd_ff *ff = container_of(work, struct snd_ff, dwork.work);
int err;
if (ff->registered)
return;
err = snd_card_new(&ff->unit->device, -1, NULL, THIS_MODULE, 0,
&ff->card);
if (err < 0)
return;
err = snd_ff_transaction_register(ff);
if (err < 0)
goto error;
name_card(ff);
err = snd_ff_stream_init_duplex(ff);
if (err < 0)
goto error;
snd_ff_proc_init(ff);
err = snd_ff_create_midi_devices(ff);
if (err < 0)
goto error;
err = snd_ff_create_pcm_devices(ff);
if (err < 0)
goto error;
err = snd_ff_create_hwdep_devices(ff);
if (err < 0)
goto error;
err = snd_card_register(ff->card);
if (err < 0)
goto error;
ff->card->private_free = ff_card_free;
ff->card->private_data = ff;
ff->registered = true;
return;
error:
snd_ff_transaction_unregister(ff);
snd_ff_stream_destroy_duplex(ff);
snd_card_free(ff->card);
dev_info(&ff->unit->device,
"Sound card registration failed: %d\n", err);
}
static int snd_ff_probe(struct fw_unit *unit,
const struct ieee1394_device_id *entry)
{
struct snd_ff *ff;
ff = kzalloc(sizeof(struct snd_ff), GFP_KERNEL);
if (ff == NULL)
return -ENOMEM;
/* initialize myself */
ff->unit = fw_unit_get(unit);
dev_set_drvdata(&unit->device, ff);
mutex_init(&ff->mutex);
spin_lock_init(&ff->lock);
init_waitqueue_head(&ff->hwdep_wait);
ff->spec = (const struct snd_ff_spec *)entry->driver_data;
/* Register this sound card later. */
INIT_DEFERRABLE_WORK(&ff->dwork, do_registration);
snd_fw_schedule_registration(unit, &ff->dwork);
return 0;
}
static void snd_ff_update(struct fw_unit *unit)
{
struct snd_ff *ff = dev_get_drvdata(&unit->device);
/* Postpone a workqueue for deferred registration. */
if (!ff->registered)
snd_fw_schedule_registration(unit, &ff->dwork);
snd_ff_transaction_reregister(ff);
if (ff->registered)
snd_ff_stream_update_duplex(ff);
}
static void snd_ff_remove(struct fw_unit *unit)
{
struct snd_ff *ff = dev_get_drvdata(&unit->device);
/*
* Confirm to stop the work for registration before the sound card is
* going to be released. The work is not scheduled again because bus
* reset handler is not called anymore.
*/
cancel_work_sync(&ff->dwork.work);
if (ff->registered) {
/* No need to wait for releasing card object in this context. */
snd_card_free_when_closed(ff->card);
} else {
/* Don't forget this case. */
ff_free(ff);
}
}
static struct snd_ff_spec spec_ff400 = {
.name = "Fireface400",
.pcm_capture_channels = {18, 14, 10},
.pcm_playback_channels = {18, 14, 10},
.midi_in_ports = 2,
.midi_out_ports = 2,
.protocol = &snd_ff_protocol_ff400,
};
static const struct ieee1394_device_id snd_ff_id_table[] = {
/* Fireface 400 */
{
.match_flags = IEEE1394_MATCH_VENDOR_ID |
IEEE1394_MATCH_SPECIFIER_ID |
IEEE1394_MATCH_VERSION |
IEEE1394_MATCH_MODEL_ID,
.vendor_id = OUI_RME,
.specifier_id = 0x000a35,
.version = 0x000002,
.model_id = 0x101800,
.driver_data = (kernel_ulong_t)&spec_ff400,
},
{}
};
MODULE_DEVICE_TABLE(ieee1394, snd_ff_id_table);
static struct fw_driver ff_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "snd-fireface",
.bus = &fw_bus_type,
},
.probe = snd_ff_probe,
.update = snd_ff_update,
.remove = snd_ff_remove,
.id_table = snd_ff_id_table,
};
static int __init snd_ff_init(void)
{
return driver_register(&ff_driver.driver);
}
static void __exit snd_ff_exit(void)
{
driver_unregister(&ff_driver.driver);
}
module_init(snd_ff_init);
module_exit(snd_ff_exit);
/*
* ff.h - a part of driver for RME Fireface series
*
* Copyright (c) 2015-2017 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#ifndef SOUND_FIREFACE_H_INCLUDED
#define SOUND_FIREFACE_H_INCLUDED
#include <linux/device.h>
#include <linux/firewire.h>
#include <linux/firewire-constants.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/compat.h>
#include <linux/sched/signal.h>
#include <sound/core.h>
#include <sound/info.h>
#include <sound/rawmidi.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/hwdep.h>
#include <sound/firewire.h>
#include "../lib.h"
#include "../amdtp-stream.h"
#include "../iso-resources.h"
#define SND_FF_STREAM_MODES 3
#define SND_FF_MAXIMIM_MIDI_QUADS 9
#define SND_FF_IN_MIDI_PORTS 2
#define SND_FF_OUT_MIDI_PORTS 2
struct snd_ff_protocol;
struct snd_ff_spec {
const char *const name;
const unsigned int pcm_capture_channels[SND_FF_STREAM_MODES];
const unsigned int pcm_playback_channels[SND_FF_STREAM_MODES];
unsigned int midi_in_ports;
unsigned int midi_out_ports;
struct snd_ff_protocol *protocol;
};
struct snd_ff {
struct snd_card *card;
struct fw_unit *unit;
struct mutex mutex;
spinlock_t lock;
bool registered;
struct delayed_work dwork;
const struct snd_ff_spec *spec;
/* To handle MIDI tx. */
struct snd_rawmidi_substream *tx_midi_substreams[SND_FF_IN_MIDI_PORTS];
struct fw_address_handler async_handler;
/* TO handle MIDI rx. */
struct snd_rawmidi_substream *rx_midi_substreams[SND_FF_OUT_MIDI_PORTS];
u8 running_status[SND_FF_OUT_MIDI_PORTS];
__le32 msg_buf[SND_FF_OUT_MIDI_PORTS][SND_FF_MAXIMIM_MIDI_QUADS];
struct work_struct rx_midi_work[SND_FF_OUT_MIDI_PORTS];
struct fw_transaction transactions[SND_FF_OUT_MIDI_PORTS];
ktime_t next_ktime[SND_FF_OUT_MIDI_PORTS];
bool rx_midi_error[SND_FF_OUT_MIDI_PORTS];
unsigned int rx_bytes[SND_FF_OUT_MIDI_PORTS];
unsigned int substreams_counter;
struct amdtp_stream tx_stream;
struct amdtp_stream rx_stream;
struct fw_iso_resources tx_resources;
struct fw_iso_resources rx_resources;
int dev_lock_count;
bool dev_lock_changed;
wait_queue_head_t hwdep_wait;
};
enum snd_ff_clock_src {
SND_FF_CLOCK_SRC_INTERNAL,
SND_FF_CLOCK_SRC_SPDIF,
SND_FF_CLOCK_SRC_ADAT,
SND_FF_CLOCK_SRC_WORD,
SND_FF_CLOCK_SRC_LTC,
/* TODO: perhaps ADAT2 and TCO exists. */
};
struct snd_ff_protocol {
int (*get_clock)(struct snd_ff *ff, unsigned int *rate,
enum snd_ff_clock_src *src);
int (*begin_session)(struct snd_ff *ff, unsigned int rate);
void (*finish_session)(struct snd_ff *ff);
int (*switch_fetching_mode)(struct snd_ff *ff, bool enable);
void (*dump_sync_status)(struct snd_ff *ff,
struct snd_info_buffer *buffer);
void (*dump_clock_config)(struct snd_ff *ff,
struct snd_info_buffer *buffer);
u64 midi_high_addr_reg;
u64 midi_rx_port_0_reg;
u64 midi_rx_port_1_reg;
};
extern struct snd_ff_protocol snd_ff_protocol_ff400;
int snd_ff_transaction_register(struct snd_ff *ff);
int snd_ff_transaction_reregister(struct snd_ff *ff);
void snd_ff_transaction_unregister(struct snd_ff *ff);
int amdtp_ff_set_parameters(struct amdtp_stream *s, unsigned int rate,
unsigned int pcm_channels);
int amdtp_ff_add_pcm_hw_constraints(struct amdtp_stream *s,
struct snd_pcm_runtime *runtime);
int amdtp_ff_init(struct amdtp_stream *s, struct fw_unit *unit,
enum amdtp_stream_direction dir);
int snd_ff_stream_init_duplex(struct snd_ff *ff);
void snd_ff_stream_destroy_duplex(struct snd_ff *ff);
int snd_ff_stream_start_duplex(struct snd_ff *ff, unsigned int rate);
void snd_ff_stream_stop_duplex(struct snd_ff *ff);
void snd_ff_stream_update_duplex(struct snd_ff *ff);
void snd_ff_stream_lock_changed(struct snd_ff *ff);
int snd_ff_stream_lock_try(struct snd_ff *ff);
void snd_ff_stream_lock_release(struct snd_ff *ff);
void snd_ff_proc_init(struct snd_ff *ff);
int snd_ff_create_midi_devices(struct snd_ff *ff);
int snd_ff_create_pcm_devices(struct snd_ff *ff);
int snd_ff_create_hwdep_devices(struct snd_ff *ff);
#endif
...@@ -99,147 +99,6 @@ void snd_fw_schedule_registration(struct fw_unit *unit, ...@@ -99,147 +99,6 @@ void snd_fw_schedule_registration(struct fw_unit *unit,
} }
EXPORT_SYMBOL(snd_fw_schedule_registration); EXPORT_SYMBOL(snd_fw_schedule_registration);
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 = 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 = 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 = 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_DESCRIPTION("FireWire audio helper functions");
MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>"); MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
MODULE_LICENSE("GPL v2"); MODULE_LICENSE("GPL v2");
...@@ -25,58 +25,4 @@ static inline bool rcode_is_permanent_error(int rcode) ...@@ -25,58 +25,4 @@ static inline bool rcode_is_permanent_error(int rcode)
void snd_fw_schedule_registration(struct fw_unit *unit, void snd_fw_schedule_registration(struct fw_unit *unit,
struct delayed_work *dwork); struct delayed_work *dwork);
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;
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 #endif
CFLAGS_amdtp-motu.o := -I$(src)
snd-firewire-motu-objs := motu.o amdtp-motu.o motu-transaction.o motu-stream.o \
motu-proc.o motu-pcm.o motu-midi.o motu-hwdep.o \
motu-protocol-v2.o motu-protocol-v3.o
obj-$(CONFIG_SND_FIREWIRE_MOTU) += snd-firewire-motu.o
/*
* amdtp-motu-trace.h - tracepoint definitions to dump a part of packet data
*
* Copyright (c) 2017 Takashi Sakamoto
* Licensed under the terms of the GNU General Public License, version 2.
*/
#undef TRACE_SYSTEM
#define TRACE_SYSTEM snd_firewire_motu
#if !defined(_SND_FIREWIRE_MOTU_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
#define _SND_FIREWIRE_MOTU_TRACE_H
#include <linux/tracepoint.h>
static void copy_sph(u32 *frame, __be32 *buffer, unsigned int data_blocks,
unsigned int data_block_quadlets);
static void copy_message(u64 *frames, __be32 *buffer, unsigned int data_blocks,
unsigned int data_block_quadlets);
TRACE_EVENT(in_data_block_sph,
TP_PROTO(struct amdtp_stream *s, unsigned int data_blocks, __be32 *buffer),
TP_ARGS(s, data_blocks, buffer),
TP_STRUCT__entry(
__field(int, src)
__field(int, dst)
__field(unsigned int, data_blocks)
__dynamic_array(u32, tstamps, data_blocks)
),
TP_fast_assign(
__entry->src = fw_parent_device(s->unit)->node_id;
__entry->dst = fw_parent_device(s->unit)->card->node_id;
__entry->data_blocks = data_blocks;
copy_sph(__get_dynamic_array(tstamps), buffer, data_blocks, s->data_block_quadlets);
),
TP_printk(
"%04x %04x %u %s",
__entry->src,
__entry->dst,
__entry->data_blocks,
__print_array(__get_dynamic_array(tstamps), __entry->data_blocks, 4)
)
);
TRACE_EVENT(out_data_block_sph,
TP_PROTO(struct amdtp_stream *s, unsigned int data_blocks, __be32 *buffer),
TP_ARGS(s, data_blocks, buffer),
TP_STRUCT__entry(
__field(int, src)
__field(int, dst)
__field(unsigned int, data_blocks)
__dynamic_array(u32, tstamps, data_blocks)
),
TP_fast_assign(
__entry->src = fw_parent_device(s->unit)->card->node_id;
__entry->dst = fw_parent_device(s->unit)->node_id;
__entry->data_blocks = data_blocks;
copy_sph(__get_dynamic_array(tstamps), buffer, data_blocks, s->data_block_quadlets);
),
TP_printk(
"%04x %04x %u %s",
__entry->src,
__entry->dst,
__entry->data_blocks,
__print_array(__get_dynamic_array(tstamps), __entry->data_blocks, 4)
)
);
TRACE_EVENT(in_data_block_message,
TP_PROTO(struct amdtp_stream *s, unsigned int data_blocks, __be32 *buffer),
TP_ARGS(s, data_blocks, buffer),
TP_STRUCT__entry(
__field(int, src)
__field(int, dst)
__field(unsigned int, data_blocks)
__dynamic_array(u64, messages, data_blocks)
),
TP_fast_assign(
__entry->src = fw_parent_device(s->unit)->node_id;
__entry->dst = fw_parent_device(s->unit)->card->node_id;
__entry->data_blocks = data_blocks;
copy_message(__get_dynamic_array(messages), buffer, data_blocks, s->data_block_quadlets);
),
TP_printk(
"%04x %04x %u %s",
__entry->src,
__entry->dst,
__entry->data_blocks,
__print_array(__get_dynamic_array(messages), __entry->data_blocks, 8)
)
);
TRACE_EVENT(out_data_block_message,
TP_PROTO(struct amdtp_stream *s, unsigned int data_blocks, __be32 *buffer),
TP_ARGS(s, data_blocks, buffer),
TP_STRUCT__entry(
__field(int, src)
__field(int, dst)
__field(unsigned int, data_blocks)
__dynamic_array(u64, messages, data_blocks)
),
TP_fast_assign(
__entry->src = fw_parent_device(s->unit)->card->node_id;
__entry->dst = fw_parent_device(s->unit)->node_id;
__entry->data_blocks = data_blocks;
copy_message(__get_dynamic_array(messages), buffer, data_blocks, s->data_block_quadlets);
),
TP_printk(
"%04x %04x %u %s",
__entry->src,
__entry->dst,
__entry->data_blocks,
__print_array(__get_dynamic_array(messages), __entry->data_blocks, 8)
)
);
#endif
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH .
#undef TRACE_INCLUDE_FILE
#define TRACE_INCLUDE_FILE amdtp-motu-trace
#include <trace/define_trace.h>
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
/*
* motu-proc.c - a part of driver for MOTU FireWire series
*
* Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp>
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include "./motu.h"
static const char *const clock_names[] = {
[SND_MOTU_CLOCK_SOURCE_INTERNAL] = "Internal",
[SND_MOTU_CLOCK_SOURCE_ADAT_ON_DSUB] = "ADAT on Dsub-9pin interface",
[SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT] = "ADAT on optical interface",
[SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_A] = "ADAT on optical interface A",
[SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_B] = "ADAT on optical interface B",
[SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT] = "S/PDIF on optical interface",
[SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_A] = "S/PDIF on optical interface A",
[SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_B] = "S/PDIF on optical interface B",
[SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX] = "S/PCIF on coaxial interface",
[SND_MOTU_CLOCK_SOURCE_AESEBU_ON_XLR] = "AESEBU on XLR interface",
[SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC] = "Word clock on BNC interface",
};
static void proc_read_clock(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_motu *motu = entry->private_data;
const struct snd_motu_protocol *const protocol = motu->spec->protocol;
unsigned int rate;
enum snd_motu_clock_source source;
if (protocol->get_clock_rate(motu, &rate) < 0)
return;
if (protocol->get_clock_source(motu, &source) < 0)
return;
snd_iprintf(buffer, "Rate:\t%d\n", rate);
snd_iprintf(buffer, "Source:\t%s\n", clock_names[source]);
}
static void proc_read_format(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_motu *motu = entry->private_data;
const struct snd_motu_protocol *const protocol = motu->spec->protocol;
unsigned int mode;
struct snd_motu_packet_format *formats;
int i;
if (protocol->cache_packet_formats(motu) < 0)
return;
snd_iprintf(buffer, "tx:\tmsg\tfixed\tdiffered\n");
for (i = 0; i < SND_MOTU_CLOCK_RATE_COUNT; ++i) {
mode = i >> 1;
formats = &motu->tx_packet_formats;
snd_iprintf(buffer,
"%u:\t%u\t%u\t%u\n",
snd_motu_clock_rates[i],
formats->msg_chunks,
formats->fixed_part_pcm_chunks[mode],
formats->differed_part_pcm_chunks[mode]);
}
snd_iprintf(buffer, "rx:\tmsg\tfixed\tdiffered\n");
for (i = 0; i < SND_MOTU_CLOCK_RATE_COUNT; ++i) {
mode = i >> 1;
formats = &motu->rx_packet_formats;
snd_iprintf(buffer,
"%u:\t%u\t%u\t%u\n",
snd_motu_clock_rates[i],
formats->msg_chunks,
formats->fixed_part_pcm_chunks[mode],
formats->differed_part_pcm_chunks[mode]);
}
}
static void add_node(struct snd_motu *motu, struct snd_info_entry *root,
const char *name,
void (*op)(struct snd_info_entry *e,
struct snd_info_buffer *b))
{
struct snd_info_entry *entry;
entry = snd_info_create_card_entry(motu->card, name, root);
if (entry == NULL)
return;
snd_info_set_text_ops(entry, motu, op);
if (snd_info_register(entry) < 0)
snd_info_free_entry(entry);
}
void snd_motu_proc_init(struct snd_motu *motu)
{
struct snd_info_entry *root;
/*
* All nodes are automatically removed at snd_card_disconnect(),
* by following to link list.
*/
root = snd_info_create_card_entry(motu->card, "firewire",
motu->card->proc_root);
if (root == NULL)
return;
root->mode = S_IFDIR | S_IRUGO | S_IXUGO;
if (snd_info_register(root) < 0) {
snd_info_free_entry(root);
return;
}
add_node(motu, root, "clock", proc_read_clock);
add_node(motu, root, "format", proc_read_format);
}
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
...@@ -34,7 +34,9 @@ int avc_stream_set_format(struct fw_unit *unit, enum avc_general_plug_dir dir, ...@@ -34,7 +34,9 @@ int avc_stream_set_format(struct fw_unit *unit, enum avc_general_plug_dir dir,
err = fcp_avc_transaction(unit, buf, len + 10, buf, len + 10, err = fcp_avc_transaction(unit, buf, len + 10, buf, len + 10,
BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
BIT(6) | BIT(7) | BIT(8)); BIT(6) | BIT(7) | BIT(8));
if ((err > 0) && (err < len + 10)) if (err < 0)
;
else if (err < len + 10)
err = -EIO; err = -EIO;
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
err = -ENOSYS; err = -ENOSYS;
...@@ -77,7 +79,9 @@ int avc_stream_get_format(struct fw_unit *unit, ...@@ -77,7 +79,9 @@ int avc_stream_get_format(struct fw_unit *unit,
err = fcp_avc_transaction(unit, buf, 12, buf, *len, err = fcp_avc_transaction(unit, buf, 12, buf, *len,
BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
BIT(6) | BIT(7)); BIT(6) | BIT(7));
if ((err > 0) && (err < 10)) if (err < 0)
;
else if (err < 12)
err = -EIO; err = -EIO;
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
err = -ENOSYS; err = -ENOSYS;
...@@ -139,7 +143,9 @@ int avc_general_inquiry_sig_fmt(struct fw_unit *unit, unsigned int rate, ...@@ -139,7 +143,9 @@ int avc_general_inquiry_sig_fmt(struct fw_unit *unit, unsigned int rate,
/* do transaction and check buf[1-5] are the same against command */ /* do transaction and check buf[1-5] are the same against command */
err = fcp_avc_transaction(unit, buf, 8, buf, 8, err = fcp_avc_transaction(unit, buf, 8, buf, 8,
BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5)); BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5));
if ((err > 0) && (err < 8)) if (err < 0)
;
else if (err < 8)
err = -EIO; err = -EIO;
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
err = -ENOSYS; err = -ENOSYS;
......
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册