[alsa-devel] [PATCH 00/15 v4] ALSA: Enhancement for existed FireWire drivers
This patchset is the rest of patches to enable ALSA Dice driver to handle more models and to improve OXFW driver. This patchset is for linux-next branch.
== Updates from my previous post:
[alsa-devel] [PATCH 00/30 v3] ALSA: Enhancement for existed FireWire drivers http://mailman.alsa-project.org/pipermail/alsa-devel/2014-November/084791.ht...
- move mutex operations to a condition in pcm.hw_params (dice/oxfw) - fix naming regression (oxfw)
== Supplemental patch for userspace library
This patchset adds Hwdep functionality for OXFW driver, thus userspace library needs a patch for entries of snd_hwdep_iface_t. Please apply below patch.
[alsa-devel] [PATCH alsa-lib] hwdep: add OXFW driver support http://mailman.alsa-project.org/pipermail/alsa-devel/2014-November/084824.ht...
Takashi Sakamoto (15): ALSA: dice: Change the way to start stream ALSA: dice: Add support for duplex streams with synchronization ALSA: dice: Support for non SYT-Match sampling clock source mode ALSA: dice: Add support for capturing PCM samples ALSA: dice: Add support for MIDI capture/playback ALSA: oxfw: Change the way to name card ALSA: oxfw: Add support for AV/C stream format command to get/set supported stream formation ALSA: oxfw: Change the way to make PCM rules/constraints ALSA: oxfw: Add proc interface for debugging purpose ALSA: oxfw: Change the way to start stream ALSA: oxfw: Add support for Behringer/Mackie devices ALSA: oxfw: Add support AMDTP in-stream ALSA: oxfw: add support for capturing PCM samples ALSA: oxfw: Add support for capture/playback MIDI messages ALSA: oxfw: Add hwdep interface
include/uapi/sound/asound.h | 3 +- include/uapi/sound/firewire.h | 3 +- sound/firewire/Kconfig | 10 +- sound/firewire/dice/Makefile | 4 +- sound/firewire/dice/dice-midi.c | 194 ++++++++++ sound/firewire/dice/dice-pcm.c | 252 +++++++++---- sound/firewire/dice/dice-stream.c | 332 ++++++++++++---- sound/firewire/dice/dice-transaction.c | 5 - sound/firewire/dice/dice.c | 37 +- sound/firewire/dice/dice.h | 33 +- sound/firewire/oxfw/Makefile | 3 +- sound/firewire/oxfw/oxfw-command.c | 153 ++++++++ sound/firewire/oxfw/oxfw-hwdep.c | 190 ++++++++++ sound/firewire/oxfw/oxfw-midi.c | 207 ++++++++++ sound/firewire/oxfw/oxfw-pcm.c | 425 +++++++++++++++------ sound/firewire/oxfw/oxfw-proc.c | 113 ++++++ sound/firewire/oxfw/oxfw-stream.c | 667 +++++++++++++++++++++++++++++++-- sound/firewire/oxfw/oxfw.c | 181 +++++++-- sound/firewire/oxfw/oxfw.h | 104 ++++- 19 files changed, 2539 insertions(+), 377 deletions(-) create mode 100644 sound/firewire/dice/dice-midi.c create mode 100644 sound/firewire/oxfw/oxfw-command.c create mode 100644 sound/firewire/oxfw/oxfw-hwdep.c create mode 100644 sound/firewire/oxfw/oxfw-midi.c create mode 100644 sound/firewire/oxfw/oxfw-proc.c
Streaming functionality can start streams when rate is given but currently some codes are in PCM functionality.
This commit changes the way to start stream and add some arrangement to make it easy to understand the way.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/dice/dice-pcm.c | 75 ++------------- sound/firewire/dice/dice-stream.c | 191 +++++++++++++++++++++++++++----------- sound/firewire/dice/dice.c | 4 - sound/firewire/dice/dice.h | 4 +- 4 files changed, 143 insertions(+), 131 deletions(-)
diff --git a/sound/firewire/dice/dice-pcm.c b/sound/firewire/dice/dice-pcm.c index 2e531bd..b185391 100644 --- a/sound/firewire/dice/dice-pcm.c +++ b/sound/firewire/dice/dice-pcm.c @@ -169,65 +169,11 @@ static int playback_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params) { struct snd_dice *dice = substream->private_data; - unsigned int mode, rate, channels, i; - int err; - - mutex_lock(&dice->mutex); - snd_dice_stream_stop(dice); - mutex_unlock(&dice->mutex); - - err = snd_pcm_lib_alloc_vmalloc_buffer(substream, - params_buffer_bytes(hw_params)); - if (err < 0) - return err; - - rate = params_rate(hw_params); - err = snd_dice_transaction_set_rate(dice, rate); - if (err < 0) - return err; - - if (snd_dice_stream_get_rate_mode(dice, rate, &mode) < 0) - return err; - - /* - * At 176.4/192.0 kHz, Dice has a quirk to transfer two PCM frames in - * one data block of AMDTP packet. Thus sampling transfer frequency is - * a half of PCM sampling frequency, i.e. PCM frames at 192.0 kHz are - * transferred on AMDTP packets at 96 kHz. Two successive samples of a - * channel are stored consecutively in the packet. This quirk is called - * as 'Dual Wire'. - * For this quirk, blocking mode is required and PCM buffer size should - * be aligned to SYT_INTERVAL. - */ - channels = params_channels(hw_params); - if (mode > 1) { - if (channels > AMDTP_MAX_CHANNELS_FOR_PCM / 2) { - err = -ENOSYS; - return err; - } - - rate /= 2; - channels *= 2; - dice->rx_stream.double_pcm_frames = true; - } else { - dice->rx_stream.double_pcm_frames = false; - } - - amdtp_stream_set_parameters(&dice->rx_stream, rate, channels, - dice->rx_midi_ports[mode]); - if (mode > 1) { - channels /= 2; - - for (i = 0; i < channels; i++) { - dice->rx_stream.pcm_positions[i] = i * 2; - dice->rx_stream.pcm_positions[i + channels] = i * 2 + 1; - } - } - amdtp_stream_set_pcm_format(&dice->rx_stream, params_format(hw_params));
- return 0; + return snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); }
static int playback_hw_free(struct snd_pcm_substream *substream) @@ -247,21 +193,12 @@ static int playback_prepare(struct snd_pcm_substream *substream) int err;
mutex_lock(&dice->mutex); - - if (amdtp_streaming_error(&dice->rx_stream)) - snd_dice_stream_stop_packets(dice); - - err = snd_dice_stream_start(dice); - if (err < 0) { - mutex_unlock(&dice->mutex); - return err; - } - + err = snd_dice_stream_start(dice, substream->runtime->rate); mutex_unlock(&dice->mutex); + if (err >= 0) + amdtp_stream_pcm_prepare(&dice->rx_stream);
- amdtp_stream_pcm_prepare(&dice->rx_stream); - - return 0; + return err; }
static int playback_trigger(struct snd_pcm_substream *substream, int cmd) diff --git a/sound/firewire/dice/dice-stream.c b/sound/firewire/dice/dice-stream.c index 4c4c4ff..b9d7a48 100644 --- a/sound/firewire/dice/dice-stream.c +++ b/sound/firewire/dice/dice-stream.c @@ -9,6 +9,8 @@
#include "dice.h"
+#define CALLBACK_TIMEOUT 200 + const unsigned int snd_dice_rates[SND_DICE_RATES_COUNT] = { /* mode 0 */ [0] = 32000, @@ -39,83 +41,162 @@ int snd_dice_stream_get_rate_mode(struct snd_dice *dice, unsigned int rate, return -EINVAL; }
-int snd_dice_stream_start_packets(struct snd_dice *dice) +static void release_resources(struct snd_dice *dice) { - int err; + unsigned int channel;
- if (amdtp_stream_running(&dice->rx_stream)) - return 0; - - err = amdtp_stream_start(&dice->rx_stream, dice->rx_resources.channel, - fw_parent_device(dice->unit)->max_speed); - if (err < 0) - return err; - - err = snd_dice_transaction_set_enable(dice); - if (err < 0) { - amdtp_stream_stop(&dice->rx_stream); - return err; - } + /* Reset channel number */ + channel = cpu_to_be32((u32)-1); + snd_dice_transaction_write_rx(dice, RX_ISOCHRONOUS, &channel, 4);
- return 0; + fw_iso_resources_free(&dice->rx_resources); }
-int snd_dice_stream_start(struct snd_dice *dice) +static int keep_resources(struct snd_dice *dice, unsigned int max_payload_bytes) { - __be32 channel; + unsigned int channel; int err;
- if (!dice->rx_resources.allocated) { - err = fw_iso_resources_allocate(&dice->rx_resources, - amdtp_stream_get_max_payload(&dice->rx_stream), + err = fw_iso_resources_allocate(&dice->rx_resources, max_payload_bytes, fw_parent_device(dice->unit)->max_speed); - if (err < 0) - goto error; - - channel = cpu_to_be32(dice->rx_resources.channel); - err = snd_dice_transaction_write_tx(dice, RX_ISOCHRONOUS, - &channel, 4); - if (err < 0) - goto err_resources; - } - - err = snd_dice_stream_start_packets(dice); if (err < 0) - goto err_rx_channel; - - return 0; + goto end;
-err_rx_channel: - channel = cpu_to_be32((u32)-1); - snd_dice_transaction_write_rx(dice, RX_ISOCHRONOUS, &channel, 4); -err_resources: - fw_iso_resources_free(&dice->rx_resources); -error: + /* Set channel number */ + channel = cpu_to_be32(dice->rx_resources.channel); + err = snd_dice_transaction_write_rx(dice, RX_ISOCHRONOUS, + &channel, 4); + if (err < 0) + release_resources(dice); +end: return err; }
-void snd_dice_stream_stop_packets(struct snd_dice *dice) +static void stop_stream(struct snd_dice *dice) { if (!amdtp_stream_running(&dice->rx_stream)) return;
- snd_dice_transaction_clear_enable(dice); + amdtp_stream_pcm_abort(&dice->rx_stream); amdtp_stream_stop(&dice->rx_stream); + release_resources(dice); }
-void snd_dice_stream_stop(struct snd_dice *dice) +static int start_stream(struct snd_dice *dice, unsigned int rate) { - __be32 channel; + unsigned int i, mode, pcm_chs, midi_ports; + int err;
- snd_dice_stream_stop_packets(dice); + err = snd_dice_stream_get_rate_mode(dice, rate, &mode); + if (err < 0) + goto end;
- if (!dice->rx_resources.allocated) - return; + /* + * At 176.4/192.0 kHz, Dice has a quirk to transfer two PCM frames in + * one data block of AMDTP packet. Thus sampling transfer frequency is + * a half of PCM sampling frequency, i.e. PCM frames at 192.0 kHz are + * transferred on AMDTP packets at 96 kHz. Two successive samples of a + * channel are stored consecutively in the packet. This quirk is called + * as 'Dual Wire'. + * For this quirk, blocking mode is required and PCM buffer size should + * be aligned to SYT_INTERVAL. + */ + pcm_chs = dice->rx_channels[mode]; + midi_ports = dice->rx_midi_ports[mode]; + if (mode > 1) { + rate /= 2; + pcm_chs *= 2; + dice->rx_stream.double_pcm_frames = true; + } else { + dice->rx_stream.double_pcm_frames = false; + }
- channel = cpu_to_be32((u32)-1); - snd_dice_transaction_write_rx(dice, RX_ISOCHRONOUS, &channel, 4); + amdtp_stream_set_parameters(&dice->rx_stream, rate, + pcm_chs, midi_ports); + if (mode > 1) { + pcm_chs /= 2;
- fw_iso_resources_free(&dice->rx_resources); + for (i = 0; i < pcm_chs; i++) { + dice->rx_stream.pcm_positions[i] = i * 2; + dice->rx_stream.pcm_positions[i + pcm_chs] = i * 2 + 1; + } + } + + err = keep_resources(dice, + amdtp_stream_get_max_payload(&dice->rx_stream)); + if (err < 0) { + dev_err(&dice->unit->device, + "fail to keep isochronous resources\n"); + goto end; + } + + err = amdtp_stream_start(&dice->rx_stream, dice->rx_resources.channel, + fw_parent_device(dice->unit)->max_speed); + if (err < 0) + release_resources(dice); +end: + return err; +} + +int snd_dice_stream_start(struct snd_dice *dice, unsigned int rate) +{ + unsigned int curr_rate; + int err; + + /* Some packet queueing errors. */ + if (amdtp_streaming_error(&dice->rx_stream)) + stop_stream(dice); + + /* Stop stream if rate is different. */ + err = snd_dice_transaction_get_rate(dice, &curr_rate); + if (err < 0) { + dev_err(&dice->unit->device, + "fail to get sampling rate\n"); + goto end; + } + if (rate != curr_rate) + stop_stream(dice); + + if (!amdtp_stream_running(&dice->rx_stream)) { + snd_dice_transaction_clear_enable(dice); + + err = snd_dice_transaction_set_rate(dice, rate); + if (err < 0) { + dev_err(&dice->unit->device, + "fail to set sampling rate\n"); + goto end; + } + + /* Start stream. */ + err = start_stream(dice, rate); + if (err < 0) { + dev_err(&dice->unit->device, + "fail to start AMDTP stream\n"); + goto end; + } + err = snd_dice_transaction_set_enable(dice); + if (err < 0) { + dev_err(&dice->unit->device, + "fail to enable interface\n"); + stop_stream(dice); + goto end; + } + + if (!amdtp_stream_wait_callback(&dice->rx_stream, + CALLBACK_TIMEOUT)) { + snd_dice_transaction_clear_enable(dice); + stop_stream(dice); + err = -ETIMEDOUT; + } + } +end: + return err; +} + +void snd_dice_stream_stop(struct snd_dice *dice) +{ + snd_dice_transaction_clear_enable(dice); + stop_stream(dice); }
int snd_dice_stream_init(struct snd_dice *dice) @@ -145,8 +226,8 @@ error:
void snd_dice_stream_destroy(struct snd_dice *dice) { - amdtp_stream_pcm_abort(&dice->rx_stream); - snd_dice_stream_stop(dice); + snd_dice_transaction_clear_enable(dice); + stop_stream(dice); amdtp_stream_destroy(&dice->rx_stream); fw_iso_resources_destroy(&dice->rx_resources); } @@ -163,8 +244,8 @@ void snd_dice_stream_update(struct snd_dice *dice) */ dice->global_enabled = false;
- amdtp_stream_pcm_abort(&dice->rx_stream); - snd_dice_stream_stop_packets(dice); + stop_stream(dice); + fw_iso_resources_update(&dice->rx_resources); }
diff --git a/sound/firewire/dice/dice.c b/sound/firewire/dice/dice.c index 8e2c172..03a7988 100644 --- a/sound/firewire/dice/dice.c +++ b/sound/firewire/dice/dice.c @@ -304,12 +304,8 @@ static void dice_remove(struct fw_unit *unit)
snd_card_disconnect(dice->card);
- mutex_lock(&dice->mutex); - snd_dice_stream_destroy(dice);
- mutex_unlock(&dice->mutex); - snd_card_free_when_closed(dice->card); }
diff --git a/sound/firewire/dice/dice.h b/sound/firewire/dice/dice.h index 969189a..8be530f 100644 --- a/sound/firewire/dice/dice.h +++ b/sound/firewire/dice/dice.h @@ -160,9 +160,7 @@ extern const unsigned int snd_dice_rates[SND_DICE_RATES_COUNT]; int snd_dice_stream_get_rate_mode(struct snd_dice *dice, unsigned int rate, unsigned int *mode);
-int snd_dice_stream_start_packets(struct snd_dice *dice); -int snd_dice_stream_start(struct snd_dice *dice); -void snd_dice_stream_stop_packets(struct snd_dice *dice); +int snd_dice_stream_start(struct snd_dice *dice, unsigned int rate); void snd_dice_stream_stop(struct snd_dice *dice); int snd_dice_stream_init(struct snd_dice *dice); void snd_dice_stream_destroy(struct snd_dice *dice);
This commit adds support for AMDTP in-stream. As a result, Dice driver supports full duplex streams with synchronization.
AMDTP can transfer timestamps in its packets. By handling the timestamp, devices can synchronize to the other devices or drivers on the same bus.
When Dice chipset is 'enabled', it starts streams with correct settings. This 'enable' register is global, thus, when a stream is started to run, an opposite stream can't start unless turning off 'enable'. Therefore a pair of streams must be running. This causes a loss of CPU usage when single stream is needed for neither playbacking or capturing.
This commit assumes that playback-only models also have a functionality to transmit stream for delivering timestamps.
Currently, sampling clock source is restricted to SYT-Match mode. This is improved in followed commit. I note that at SYT-Match mode, Dice can select from 4 streams for synchronization but this driver uses the 1st stream only for simplicity.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/dice/dice-pcm.c | 4 +- sound/firewire/dice/dice-stream.c | 230 ++++++++++++++++++++++++++++---------- sound/firewire/dice/dice.c | 29 +++-- sound/firewire/dice/dice.h | 26 +++-- 4 files changed, 202 insertions(+), 87 deletions(-)
diff --git a/sound/firewire/dice/dice-pcm.c b/sound/firewire/dice/dice-pcm.c index b185391..b9ce026 100644 --- a/sound/firewire/dice/dice-pcm.c +++ b/sound/firewire/dice/dice-pcm.c @@ -181,7 +181,7 @@ static int playback_hw_free(struct snd_pcm_substream *substream) struct snd_dice *dice = substream->private_data;
mutex_lock(&dice->mutex); - snd_dice_stream_stop(dice); + snd_dice_stream_stop_duplex(dice); mutex_unlock(&dice->mutex);
return snd_pcm_lib_free_vmalloc_buffer(substream); @@ -193,7 +193,7 @@ static int playback_prepare(struct snd_pcm_substream *substream) int err;
mutex_lock(&dice->mutex); - err = snd_dice_stream_start(dice, substream->runtime->rate); + err = snd_dice_stream_start_duplex(dice, substream->runtime->rate); mutex_unlock(&dice->mutex); if (err >= 0) amdtp_stream_pcm_prepare(&dice->rx_stream); diff --git a/sound/firewire/dice/dice-stream.c b/sound/firewire/dice/dice-stream.c index b9d7a48..e60b84d 100644 --- a/sound/firewire/dice/dice-stream.c +++ b/sound/firewire/dice/dice-stream.c @@ -41,55 +41,79 @@ int snd_dice_stream_get_rate_mode(struct snd_dice *dice, unsigned int rate, return -EINVAL; }
-static void release_resources(struct snd_dice *dice) +static void release_resources(struct snd_dice *dice, + struct fw_iso_resources *resources) { unsigned int channel;
/* Reset channel number */ channel = cpu_to_be32((u32)-1); - snd_dice_transaction_write_rx(dice, RX_ISOCHRONOUS, &channel, 4); - - fw_iso_resources_free(&dice->rx_resources); + if (resources == &dice->tx_resources) + snd_dice_transaction_write_tx(dice, TX_ISOCHRONOUS, + &channel, 4); + else + snd_dice_transaction_write_rx(dice, RX_ISOCHRONOUS, + &channel, 4); + + fw_iso_resources_free(resources); }
-static int keep_resources(struct snd_dice *dice, unsigned int max_payload_bytes) +static int keep_resources(struct snd_dice *dice, + struct fw_iso_resources *resources, + unsigned int max_payload_bytes) { unsigned int channel; int err;
- err = fw_iso_resources_allocate(&dice->rx_resources, max_payload_bytes, + err = fw_iso_resources_allocate(resources, max_payload_bytes, fw_parent_device(dice->unit)->max_speed); if (err < 0) goto end;
/* Set channel number */ - channel = cpu_to_be32(dice->rx_resources.channel); - err = snd_dice_transaction_write_rx(dice, RX_ISOCHRONOUS, - &channel, 4); + channel = cpu_to_be32(resources->channel); + if (resources == &dice->tx_resources) + err = snd_dice_transaction_write_tx(dice, TX_ISOCHRONOUS, + &channel, 4); + else + err = snd_dice_transaction_write_rx(dice, RX_ISOCHRONOUS, + &channel, 4); if (err < 0) - release_resources(dice); + release_resources(dice, resources); end: return err; }
-static void stop_stream(struct snd_dice *dice) +static void stop_stream(struct snd_dice *dice, struct amdtp_stream *stream) { - if (!amdtp_stream_running(&dice->rx_stream)) - return; + amdtp_stream_pcm_abort(stream); + amdtp_stream_stop(stream);
- amdtp_stream_pcm_abort(&dice->rx_stream); - amdtp_stream_stop(&dice->rx_stream); - release_resources(dice); + if (stream == &dice->tx_stream) + release_resources(dice, &dice->tx_resources); + else + release_resources(dice, &dice->rx_resources); }
-static int start_stream(struct snd_dice *dice, unsigned int rate) +static int start_stream(struct snd_dice *dice, struct amdtp_stream *stream, + unsigned int rate) { + struct fw_iso_resources *resources; unsigned int i, mode, pcm_chs, midi_ports; int err;
err = snd_dice_stream_get_rate_mode(dice, rate, &mode); if (err < 0) goto end; + if (stream == &dice->tx_stream) { + resources = &dice->tx_resources; + pcm_chs = dice->tx_channels[mode]; + midi_ports = dice->tx_midi_ports[mode]; + } else { + resources = &dice->rx_resources; + pcm_chs = dice->rx_channels[mode]; + midi_ports = dice->rx_midi_ports[mode]; + }
/* * At 176.4/192.0 kHz, Dice has a quirk to transfer two PCM frames in @@ -101,51 +125,71 @@ static int start_stream(struct snd_dice *dice, unsigned int rate) * For this quirk, blocking mode is required and PCM buffer size should * be aligned to SYT_INTERVAL. */ - pcm_chs = dice->rx_channels[mode]; - midi_ports = dice->rx_midi_ports[mode]; if (mode > 1) { rate /= 2; pcm_chs *= 2; - dice->rx_stream.double_pcm_frames = true; + stream->double_pcm_frames = true; } else { - dice->rx_stream.double_pcm_frames = false; + stream->double_pcm_frames = false; }
- amdtp_stream_set_parameters(&dice->rx_stream, rate, - pcm_chs, midi_ports); + amdtp_stream_set_parameters(stream, rate, pcm_chs, midi_ports); if (mode > 1) { pcm_chs /= 2;
for (i = 0; i < pcm_chs; i++) { - dice->rx_stream.pcm_positions[i] = i * 2; - dice->rx_stream.pcm_positions[i + pcm_chs] = i * 2 + 1; + stream->pcm_positions[i] = i * 2; + stream->pcm_positions[i + pcm_chs] = i * 2 + 1; } }
- err = keep_resources(dice, - amdtp_stream_get_max_payload(&dice->rx_stream)); + err = keep_resources(dice, resources, + amdtp_stream_get_max_payload(stream)); if (err < 0) { dev_err(&dice->unit->device, "fail to keep isochronous resources\n"); goto end; }
- err = amdtp_stream_start(&dice->rx_stream, dice->rx_resources.channel, + err = amdtp_stream_start(stream, resources->channel, fw_parent_device(dice->unit)->max_speed); if (err < 0) - release_resources(dice); + release_resources(dice, resources); end: return err; }
-int snd_dice_stream_start(struct snd_dice *dice, unsigned int rate) +static int get_sync_mode(struct snd_dice *dice, enum cip_flags *sync_mode) +{ + /* Currently, clock source is fixed at SYT-Match mode. */ + *sync_mode = 0; + return 0; +} + +int snd_dice_stream_start_duplex(struct snd_dice *dice, unsigned int rate) { + struct amdtp_stream *master, *slave; unsigned int curr_rate; - int err; + enum cip_flags sync_mode; + int err = 0; + + if (dice->substreams_counter == 0) + goto end; + + err = get_sync_mode(dice, &sync_mode); + if (err < 0) + goto end; + if (sync_mode == CIP_SYNC_TO_DEVICE) { + master = &dice->tx_stream; + slave = &dice->rx_stream; + } else { + master = &dice->rx_stream; + slave = &dice->tx_stream; + }
/* Some packet queueing errors. */ - if (amdtp_streaming_error(&dice->rx_stream)) - stop_stream(dice); + if (amdtp_streaming_error(master) || amdtp_streaming_error(slave)) + stop_stream(dice, master);
/* Stop stream if rate is different. */ err = snd_dice_transaction_get_rate(dice, &curr_rate); @@ -155,11 +199,14 @@ int snd_dice_stream_start(struct snd_dice *dice, unsigned int rate) goto end; } if (rate != curr_rate) - stop_stream(dice); + stop_stream(dice, master);
- if (!amdtp_stream_running(&dice->rx_stream)) { + if (!amdtp_stream_running(master)) { + stop_stream(dice, slave); snd_dice_transaction_clear_enable(dice);
+ amdtp_stream_set_sync(sync_mode, master, slave); + err = snd_dice_transaction_set_rate(dice, rate); if (err < 0) { dev_err(&dice->unit->device, @@ -167,25 +214,35 @@ int snd_dice_stream_start(struct snd_dice *dice, unsigned int rate) goto end; }
- /* Start stream. */ - err = start_stream(dice, rate); + /* Start both streams. */ + err = start_stream(dice, master, rate); + if (err < 0) { + dev_err(&dice->unit->device, + "fail to start AMDTP master stream\n"); + goto end; + } + err = start_stream(dice, slave, rate); if (err < 0) { dev_err(&dice->unit->device, - "fail to start AMDTP stream\n"); + "fail to start AMDTP slave stream\n"); + stop_stream(dice, master); goto end; } err = snd_dice_transaction_set_enable(dice); if (err < 0) { dev_err(&dice->unit->device, "fail to enable interface\n"); - stop_stream(dice); + stop_stream(dice, master); + stop_stream(dice, slave); goto end; }
- if (!amdtp_stream_wait_callback(&dice->rx_stream, - CALLBACK_TIMEOUT)) { + /* Wait first callbacks */ + if (!amdtp_stream_wait_callback(master, CALLBACK_TIMEOUT) || + !amdtp_stream_wait_callback(slave, CALLBACK_TIMEOUT)) { snd_dice_transaction_clear_enable(dice); - stop_stream(dice); + stop_stream(dice, master); + stop_stream(dice, slave); err = -ETIMEDOUT; } } @@ -193,46 +250,93 @@ end: return err; }
-void snd_dice_stream_stop(struct snd_dice *dice) +void snd_dice_stream_stop_duplex(struct snd_dice *dice) { + if (dice->substreams_counter > 0) + return; + snd_dice_transaction_clear_enable(dice); - stop_stream(dice); + + stop_stream(dice, &dice->tx_stream); + stop_stream(dice, &dice->rx_stream); }
-int snd_dice_stream_init(struct snd_dice *dice) +static int init_stream(struct snd_dice *dice, struct amdtp_stream *stream) { int err; + struct fw_iso_resources *resources; + enum amdtp_stream_direction dir; + + if (stream == &dice->tx_stream) { + resources = &dice->tx_resources; + dir = AMDTP_IN_STREAM; + } else { + resources = &dice->rx_resources; + dir = AMDTP_OUT_STREAM; + }
- err = fw_iso_resources_init(&dice->rx_resources, dice->unit); + err = fw_iso_resources_init(resources, dice->unit); if (err < 0) goto end; - dice->rx_resources.channels_mask = 0x00000000ffffffffuLL; + resources->channels_mask = 0x00000000ffffffffuLL;
- err = amdtp_stream_init(&dice->rx_stream, dice->unit, AMDTP_OUT_STREAM, - CIP_BLOCKING); + err = amdtp_stream_init(stream, dice->unit, dir, CIP_BLOCKING); + if (err < 0) { + amdtp_stream_destroy(stream); + fw_iso_resources_destroy(resources); + } +end: + return err; +} + +static void destroy_stream(struct snd_dice *dice, struct amdtp_stream *stream) +{ + amdtp_stream_destroy(stream); + + if (stream == &dice->tx_stream) + fw_iso_resources_destroy(&dice->tx_resources); + else + fw_iso_resources_destroy(&dice->rx_resources); +} + +int snd_dice_stream_init_duplex(struct snd_dice *dice) +{ + int err; + + dice->substreams_counter = 0; + + err = init_stream(dice, &dice->tx_stream); if (err < 0) - goto error; + goto end;
- err = snd_dice_transaction_set_clock_source(dice, CLOCK_SOURCE_ARX1); + err = init_stream(dice, &dice->rx_stream); if (err < 0) - goto error; + goto end; + + /* Currently, clock source is fixed at SYT-Match mode. */ + err = snd_dice_transaction_set_clock_source(dice, CLOCK_SOURCE_ARX1); + if (err < 0) { + destroy_stream(dice, &dice->rx_stream); + destroy_stream(dice, &dice->tx_stream); + } end: return err; -error: - amdtp_stream_destroy(&dice->rx_stream); - fw_iso_resources_destroy(&dice->rx_resources); - return err; }
-void snd_dice_stream_destroy(struct snd_dice *dice) +void snd_dice_stream_destroy_duplex(struct snd_dice *dice) { snd_dice_transaction_clear_enable(dice); - stop_stream(dice); - amdtp_stream_destroy(&dice->rx_stream); - fw_iso_resources_destroy(&dice->rx_resources); + + stop_stream(dice, &dice->tx_stream); + destroy_stream(dice, &dice->tx_stream); + + stop_stream(dice, &dice->rx_stream); + destroy_stream(dice, &dice->rx_stream); + + dice->substreams_counter = 0; }
-void snd_dice_stream_update(struct snd_dice *dice) +void snd_dice_stream_update_duplex(struct snd_dice *dice) { /* * On a bus reset, the DICE firmware disables streaming and then goes @@ -244,9 +348,11 @@ void snd_dice_stream_update(struct snd_dice *dice) */ dice->global_enabled = false;
- stop_stream(dice); + stop_stream(dice, &dice->rx_stream); + stop_stream(dice, &dice->tx_stream);
fw_iso_resources_update(&dice->rx_resources); + fw_iso_resources_update(&dice->tx_resources); }
static void dice_lock_changed(struct snd_dice *dice) diff --git a/sound/firewire/dice/dice.c b/sound/firewire/dice/dice.c index 03a7988..85bcfaf 100644 --- a/sound/firewire/dice/dice.c +++ b/sound/firewire/dice/dice.c @@ -30,7 +30,6 @@ static int dice_interface_check(struct fw_unit *unit) int key, val, vendor = -1, model = -1, err; unsigned int category, i; __be32 *pointers, value; - __be32 tx_data[4]; __be32 version;
pointers = kmalloc_array(ARRAY_SIZE(min_values), sizeof(__be32), @@ -85,16 +84,6 @@ static int dice_interface_check(struct fw_unit *unit) } }
- /* We support playback only. Let capture devices be handled by FFADO. */ - err = snd_fw_transaction(unit, TCODE_READ_BLOCK_REQUEST, - DICE_PRIVATE_SPACE + - be32_to_cpu(pointers[2]) * 4, - tx_data, sizeof(tx_data), 0); - if (err < 0 || (tx_data[0] && tx_data[3])) { - err = -ENODEV; - goto end; - } - /* * Check that the implemented DICE driver specification major version * number matches. @@ -142,6 +131,8 @@ static int dice_read_mode_params(struct snd_dice *dice, unsigned int mode) int err;
if (highest_supported_mode_rate(dice, mode, &rate) < 0) { + dice->tx_channels[mode] = 0; + dice->tx_midi_ports[mode] = 0; dice->rx_channels[mode] = 0; dice->rx_midi_ports[mode] = 0; return 0; @@ -151,6 +142,14 @@ static int dice_read_mode_params(struct snd_dice *dice, unsigned int mode) if (err < 0) return err;
+ err = snd_dice_transaction_read_tx(dice, TX_NUMBER_AUDIO, + values, sizeof(values)); + if (err < 0) + return err; + + dice->tx_channels[mode] = be32_to_cpu(values[0]); + dice->tx_midi_ports[mode] = be32_to_cpu(values[1]); + err = snd_dice_transaction_read_rx(dice, RX_NUMBER_AUDIO, values, sizeof(values)); if (err < 0) @@ -280,13 +279,13 @@ static int dice_probe(struct fw_unit *unit, const struct ieee1394_device_id *id)
snd_dice_create_proc(dice);
- err = snd_dice_stream_init(dice); + err = snd_dice_stream_init_duplex(dice); if (err < 0) goto error;
err = snd_card_register(card); if (err < 0) { - snd_dice_stream_destroy(dice); + snd_dice_stream_destroy_duplex(dice); goto error; }
@@ -304,7 +303,7 @@ static void dice_remove(struct fw_unit *unit)
snd_card_disconnect(dice->card);
- snd_dice_stream_destroy(dice); + snd_dice_stream_destroy_duplex(dice);
snd_card_free_when_closed(dice->card); } @@ -317,7 +316,7 @@ static void dice_bus_reset(struct fw_unit *unit) snd_dice_transaction_reinit(dice);
mutex_lock(&dice->mutex); - snd_dice_stream_update(dice); + snd_dice_stream_update_duplex(dice); mutex_unlock(&dice->mutex); }
diff --git a/sound/firewire/dice/dice.h b/sound/firewire/dice/dice.h index 8be530f..a62ee22 100644 --- a/sound/firewire/dice/dice.h +++ b/sound/firewire/dice/dice.h @@ -52,18 +52,28 @@ struct snd_dice { unsigned int rsrv_offset;
unsigned int clock_caps; + unsigned int tx_channels[3]; unsigned int rx_channels[3]; + unsigned int tx_midi_ports[3]; unsigned int rx_midi_ports[3]; + struct fw_address_handler notification_handler; int owner_generation; + u32 notification_bits; + + /* For uapi */ int dev_lock_count; /* > 0 driver, < 0 userspace */ bool dev_lock_changed; - bool global_enabled; - struct completion clock_accepted; wait_queue_head_t hwdep_wait; - u32 notification_bits; + + /* For streaming */ + struct fw_iso_resources tx_resources; struct fw_iso_resources rx_resources; + struct amdtp_stream tx_stream; struct amdtp_stream rx_stream; + bool global_enabled; + struct completion clock_accepted; + unsigned int substreams_counter; };
enum snd_dice_addr_type { @@ -160,11 +170,11 @@ extern const unsigned int snd_dice_rates[SND_DICE_RATES_COUNT]; int snd_dice_stream_get_rate_mode(struct snd_dice *dice, unsigned int rate, unsigned int *mode);
-int snd_dice_stream_start(struct snd_dice *dice, unsigned int rate); -void snd_dice_stream_stop(struct snd_dice *dice); -int snd_dice_stream_init(struct snd_dice *dice); -void snd_dice_stream_destroy(struct snd_dice *dice); -void snd_dice_stream_update(struct snd_dice *dice); +int snd_dice_stream_start_duplex(struct snd_dice *dice, unsigned int rate); +void snd_dice_stream_stop_duplex(struct snd_dice *dice); +int snd_dice_stream_init_duplex(struct snd_dice *dice); +void snd_dice_stream_destroy_duplex(struct snd_dice *dice); +void snd_dice_stream_update_duplex(struct snd_dice *dice);
int snd_dice_stream_lock_try(struct snd_dice *dice); void snd_dice_stream_lock_release(struct snd_dice *dice);
This commit allows this driver to handle devices with non SYT-Match sampling clock source.
When sampling clock source is SYT-Match mode, devices handle 'presentation timestamp' in received packets and generates sampling clock according to the information. In this case, driver is synchronization master and must transfer correct value in SYT field of each packets in outgoing stream, then the outgoing stream is a master stream.
On the other hand, non SYT-Match mode, devices do this. So drivers must pick up the value in SYT field of incoming packets and use the value for outgoing stream. Currently firewire-lib module achieve this work.
Furthermore, without SYT-Match and internal clock source, the sampling rate should be fixed for the other devices connected to the handled device. This commit add a restriction of sampling rate at this situation.
With these implementations, this driver has no need to set clock source. This commit remove set function.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/dice/dice-pcm.c | 35 ++++++++++++++++++++++++++++++++++ sound/firewire/dice/dice-stream.c | 35 ++++++++++++++++++++++------------ sound/firewire/dice/dice-transaction.c | 5 ----- sound/firewire/dice/dice.h | 2 -- 4 files changed, 58 insertions(+), 19 deletions(-)
diff --git a/sound/firewire/dice/dice-pcm.c b/sound/firewire/dice/dice-pcm.c index b9ce026..062b7a3 100644 --- a/sound/firewire/dice/dice-pcm.c +++ b/sound/firewire/dice/dice-pcm.c @@ -140,6 +140,8 @@ end: static int pcm_open(struct snd_pcm_substream *substream) { struct snd_dice *dice = substream->private_data; + unsigned int source, rate; + bool internal; int err;
err = snd_dice_stream_lock_try(dice); @@ -149,6 +151,39 @@ static int pcm_open(struct snd_pcm_substream *substream) err = init_hw_info(dice, substream); if (err < 0) goto err_locked; + + err = snd_dice_transaction_get_clock_source(dice, &source); + if (err < 0) + goto err_locked; + switch (source) { + case CLOCK_SOURCE_AES1: + case CLOCK_SOURCE_AES2: + case CLOCK_SOURCE_AES3: + case CLOCK_SOURCE_AES4: + case CLOCK_SOURCE_AES_ANY: + case CLOCK_SOURCE_ADAT: + case CLOCK_SOURCE_TDIF: + case CLOCK_SOURCE_WC: + internal = false; + break; + default: + internal = true; + break; + } + + /* + * When source of clock is not internal, available sampling rate is + * limited at current sampling rate. + */ + if (!internal) { + err = snd_dice_transaction_get_rate(dice, &rate); + if (err < 0) + goto err_locked; + substream->runtime->hw.rate_min = rate; + substream->runtime->hw.rate_max = rate; + } + + snd_pcm_set_sync(substream); end: return err; err_locked: diff --git a/sound/firewire/dice/dice-stream.c b/sound/firewire/dice/dice-stream.c index e60b84d..20765a0 100644 --- a/sound/firewire/dice/dice-stream.c +++ b/sound/firewire/dice/dice-stream.c @@ -161,9 +161,29 @@ end:
static int get_sync_mode(struct snd_dice *dice, enum cip_flags *sync_mode) { - /* Currently, clock source is fixed at SYT-Match mode. */ - *sync_mode = 0; - return 0; + u32 source; + int err; + + err = snd_dice_transaction_get_clock_source(dice, &source); + if (err < 0) + goto end; + + switch (source) { + /* So-called 'SYT Match' modes, sync_to_syt value of packets received */ + case CLOCK_SOURCE_ARX4: /* in 4th stream */ + case CLOCK_SOURCE_ARX3: /* in 3rd stream */ + case CLOCK_SOURCE_ARX2: /* in 2nd stream */ + err = -ENOSYS; + break; + case CLOCK_SOURCE_ARX1: /* in 1st stream, which this driver uses */ + *sync_mode = 0; + break; + default: + *sync_mode = CIP_SYNC_TO_DEVICE; + break; + } +end: + return err; }
int snd_dice_stream_start_duplex(struct snd_dice *dice, unsigned int rate) @@ -310,15 +330,6 @@ int snd_dice_stream_init_duplex(struct snd_dice *dice) goto end;
err = init_stream(dice, &dice->rx_stream); - if (err < 0) - goto end; - - /* Currently, clock source is fixed at SYT-Match mode. */ - err = snd_dice_transaction_set_clock_source(dice, CLOCK_SOURCE_ARX1); - if (err < 0) { - destroy_stream(dice, &dice->rx_stream); - destroy_stream(dice, &dice->tx_stream); - } end: return err; } diff --git a/sound/firewire/dice/dice-transaction.c b/sound/firewire/dice/dice-transaction.c index 1fe304c..aee7461 100644 --- a/sound/firewire/dice/dice-transaction.c +++ b/sound/firewire/dice/dice-transaction.c @@ -137,11 +137,6 @@ int snd_dice_transaction_get_clock_source(struct snd_dice *dice,
return err; } -int snd_dice_transaction_set_clock_source(struct snd_dice *dice, - unsigned int source) -{ - return set_clock_info(dice, UINT_MAX, source); -}
int snd_dice_transaction_get_rate(struct snd_dice *dice, unsigned int *rate) { diff --git a/sound/firewire/dice/dice.h b/sound/firewire/dice/dice.h index a62ee22..f30326e 100644 --- a/sound/firewire/dice/dice.h +++ b/sound/firewire/dice/dice.h @@ -152,8 +152,6 @@ static inline int snd_dice_transaction_read_sync(struct snd_dice *dice, buf, len); }
-int snd_dice_transaction_set_clock_source(struct snd_dice *dice, - unsigned int source); int snd_dice_transaction_get_clock_source(struct snd_dice *dice, unsigned int *source); int snd_dice_transaction_set_rate(struct snd_dice *dice, unsigned int rate);
This commit adds a support for capturing PCM samples.
When opposite PCM substream is already running, available sampling rate is limited at current one.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/Kconfig | 3 - sound/firewire/dice/dice-pcm.c | 162 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 148 insertions(+), 17 deletions(-)
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index 2a5b9a6..0932860 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -20,9 +20,6 @@ config SND_DICE Say Y here to include support for many DACs based on the DICE chip family (DICE-II/Jr/Mini) which TC Applied Technologies produces.
- At the moment, this driver supports playback only. If you - want to use devices that support capturing, use FFADO instead. - To compile this driver as a module, choose M here: the module will be called snd-dice.
diff --git a/sound/firewire/dice/dice-pcm.c b/sound/firewire/dice/dice-pcm.c index 062b7a3..14f27ca 100644 --- a/sound/firewire/dice/dice-pcm.c +++ b/sound/firewire/dice/dice-pcm.c @@ -12,7 +12,8 @@ static int dice_rate_constraint(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) { - struct snd_dice *dice = rule->private; + struct snd_pcm_substream *substream = rule->private; + struct snd_dice *dice = substream->private_data;
const struct snd_interval *c = hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); @@ -21,7 +22,12 @@ static int dice_rate_constraint(struct snd_pcm_hw_params *params, struct snd_interval rates = { .min = UINT_MAX, .max = 0, .integer = 1 }; - unsigned int i, rate, mode, *pcm_channels = dice->rx_channels; + unsigned int i, rate, mode, *pcm_channels; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + pcm_channels = dice->tx_channels; + else + pcm_channels = dice->rx_channels;
for (i = 0; i < ARRAY_SIZE(snd_dice_rates); ++i) { rate = snd_dice_rates[i]; @@ -41,7 +47,8 @@ static int dice_rate_constraint(struct snd_pcm_hw_params *params, static int dice_channels_constraint(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) { - struct snd_dice *dice = rule->private; + struct snd_pcm_substream *substream = rule->private; + struct snd_dice *dice = substream->private_data;
const struct snd_interval *r = hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); @@ -50,7 +57,12 @@ static int dice_channels_constraint(struct snd_pcm_hw_params *params, struct snd_interval channels = { .min = UINT_MAX, .max = 0, .integer = 1 }; - unsigned int i, rate, mode, *pcm_channels = dice->rx_channels; + unsigned int i, rate, mode, *pcm_channels; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + pcm_channels = dice->tx_channels; + else + pcm_channels = dice->rx_channels;
for (i = 0; i < ARRAY_SIZE(snd_dice_rates); ++i) { rate = snd_dice_rates[i]; @@ -109,30 +121,42 @@ static int init_hw_info(struct snd_dice *dice, { struct snd_pcm_runtime *runtime = substream->runtime; struct snd_pcm_hardware *hw = &runtime->hw; + struct amdtp_stream *stream; + unsigned int *pcm_channels; int err;
hw->info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_BATCH | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_JOINT_DUPLEX | SNDRV_PCM_INFO_BLOCK_TRANSFER; - hw->formats = AMDTP_OUT_PCM_FORMAT_BITS;
- limit_channels_and_rates(dice, runtime, dice->rx_channels); + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + hw->formats = AMDTP_IN_PCM_FORMAT_BITS; + stream = &dice->tx_stream; + pcm_channels = dice->tx_channels; + } else { + hw->formats = AMDTP_OUT_PCM_FORMAT_BITS; + stream = &dice->rx_stream; + pcm_channels = dice->rx_channels; + } + + limit_channels_and_rates(dice, runtime, pcm_channels); limit_period_and_buffer(hw);
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, - dice_rate_constraint, dice, + dice_rate_constraint, substream, SNDRV_PCM_HW_PARAM_CHANNELS, -1); if (err < 0) goto end; err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, - dice_channels_constraint, dice, + dice_channels_constraint, substream, SNDRV_PCM_HW_PARAM_RATE, -1); if (err < 0) goto end;
- err = amdtp_stream_add_pcm_hw_constraints(&dice->rx_stream, runtime); + err = amdtp_stream_add_pcm_hw_constraints(stream, runtime); end: return err; } @@ -172,10 +196,12 @@ static int pcm_open(struct snd_pcm_substream *substream) }
/* - * When source of clock is not internal, available sampling rate is - * limited at current sampling rate. + * When source of clock is not internal or any PCM streams are running, + * available sampling rate is limited at current sampling rate. */ - if (!internal) { + if (!internal || + amdtp_stream_pcm_running(&dice->tx_stream) || + amdtp_stream_pcm_running(&dice->rx_stream)) { err = snd_dice_transaction_get_rate(dice, &rate); if (err < 0) goto err_locked; @@ -200,10 +226,35 @@ static int pcm_close(struct snd_pcm_substream *substream) return 0; }
+static int capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_dice *dice = substream->private_data; + + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&dice->mutex); + dice->substreams_counter++; + mutex_unlock(&dice->mutex); + } + + amdtp_stream_set_pcm_format(&dice->tx_stream, + params_format(hw_params)); + + return snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); +} static int playback_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params) { struct snd_dice *dice = substream->private_data; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&dice->mutex); + dice->substreams_counter++; + mutex_unlock(&dice->mutex); + } + amdtp_stream_set_pcm_format(&dice->rx_stream, params_format(hw_params));
@@ -211,17 +262,51 @@ static int playback_hw_params(struct snd_pcm_substream *substream, params_buffer_bytes(hw_params)); }
+static int capture_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_dice *dice = substream->private_data; + + mutex_lock(&dice->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + dice->substreams_counter--; + + snd_dice_stream_stop_duplex(dice); + + mutex_unlock(&dice->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + static int playback_hw_free(struct snd_pcm_substream *substream) { struct snd_dice *dice = substream->private_data;
mutex_lock(&dice->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + dice->substreams_counter--; + snd_dice_stream_stop_duplex(dice); + mutex_unlock(&dice->mutex);
return snd_pcm_lib_free_vmalloc_buffer(substream); }
+static int capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_dice *dice = substream->private_data; + int err; + + mutex_lock(&dice->mutex); + err = snd_dice_stream_start_duplex(dice, substream->runtime->rate); + mutex_unlock(&dice->mutex); + if (err >= 0) + amdtp_stream_pcm_prepare(&dice->tx_stream); + + return 0; +} static int playback_prepare(struct snd_pcm_substream *substream) { struct snd_dice *dice = substream->private_data; @@ -236,6 +321,23 @@ static int playback_prepare(struct snd_pcm_substream *substream) return err; }
+static int capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_dice *dice = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&dice->tx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&dice->tx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} static int playback_trigger(struct snd_pcm_substream *substream, int cmd) { struct snd_dice *dice = substream->private_data; @@ -254,6 +356,12 @@ static int playback_trigger(struct snd_pcm_substream *substream, int cmd) return 0; }
+static snd_pcm_uframes_t capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_dice *dice = substream->private_data; + + return amdtp_stream_pcm_pointer(&dice->tx_stream); +} static snd_pcm_uframes_t playback_pointer(struct snd_pcm_substream *substream) { struct snd_dice *dice = substream->private_data; @@ -263,6 +371,18 @@ static snd_pcm_uframes_t playback_pointer(struct snd_pcm_substream *substream)
int snd_dice_create_pcm(struct snd_dice *dice) { + static struct snd_pcm_ops capture_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = capture_hw_params, + .hw_free = capture_hw_free, + .prepare = capture_prepare, + .trigger = capture_trigger, + .pointer = capture_pointer, + .page = snd_pcm_lib_get_vmalloc_page, + .mmap = snd_pcm_lib_mmap_vmalloc, + }; static struct snd_pcm_ops playback_ops = { .open = pcm_open, .close = pcm_close, @@ -276,14 +396,28 @@ int snd_dice_create_pcm(struct snd_dice *dice) .mmap = snd_pcm_lib_mmap_vmalloc, }; struct snd_pcm *pcm; + unsigned int i, capture, playback; int err;
- err = snd_pcm_new(dice->card, "DICE", 0, 1, 0, &pcm); + capture = playback = 0; + for (i = 0; i < 3; i++) { + if (dice->tx_channels[i] > 0) + capture = 1; + if (dice->rx_channels[i] > 0) + playback = 1; + } + + err = snd_pcm_new(dice->card, "DICE", 0, playback, capture, &pcm); if (err < 0) return err; pcm->private_data = dice; strcpy(pcm->name, dice->card->shortname); - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops); + + if (capture > 0) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_ops); + + if (playback > 0) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops);
return 0; }
This commit adds a support for MIDI capture/playback
When MIDI substrams already start streaming and PCM substreams are going to join at different sampling rate, streams are stopped once. Then sampling rate is changed and streams are restarted.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/dice/Makefile | 4 +- sound/firewire/dice/dice-midi.c | 194 ++++++++++++++++++++++++++++++++++++++ sound/firewire/dice/dice-stream.c | 2 + sound/firewire/dice/dice.c | 4 + sound/firewire/dice/dice.h | 3 + 5 files changed, 205 insertions(+), 2 deletions(-) create mode 100644 sound/firewire/dice/dice-midi.c
diff --git a/sound/firewire/dice/Makefile b/sound/firewire/dice/Makefile index 9a48289..9ef228e 100644 --- a/sound/firewire/dice/Makefile +++ b/sound/firewire/dice/Makefile @@ -1,3 +1,3 @@ -snd-dice-objs := dice-transaction.o dice-stream.o dice-proc.o dice-pcm.o \ - dice-hwdep.o dice.o +snd-dice-objs := dice-transaction.o dice-stream.o dice-proc.o dice-midi.o \ + dice-pcm.o dice-hwdep.o dice.o obj-m += snd-dice.o diff --git a/sound/firewire/dice/dice-midi.c b/sound/firewire/dice/dice-midi.c new file mode 100644 index 0000000..fc35055 --- /dev/null +++ b/sound/firewire/dice/dice-midi.c @@ -0,0 +1,194 @@ +/* + * dice_midi.c - a part of driver for Dice based devices + * + * Copyright (c) 2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ +#include "dice.h" + +static int capture_open(struct snd_rawmidi_substream *substream) +{ + struct snd_dice *dice = substream->rmidi->private_data; + int err; + + err = snd_dice_stream_lock_try(dice); + if (err < 0) + return err; + + mutex_lock(&dice->mutex); + + dice->substreams_counter++; + err = snd_dice_stream_start_duplex(dice, 0); + + mutex_unlock(&dice->mutex); + + if (err < 0) + snd_dice_stream_lock_release(dice); + + return err; +} + +static int playback_open(struct snd_rawmidi_substream *substream) +{ + struct snd_dice *dice = substream->rmidi->private_data; + int err; + + err = snd_dice_stream_lock_try(dice); + if (err < 0) + return err; + + mutex_lock(&dice->mutex); + + dice->substreams_counter++; + err = snd_dice_stream_start_duplex(dice, 0); + + mutex_unlock(&dice->mutex); + + if (err < 0) + snd_dice_stream_lock_release(dice); + + return err; +} + +static int capture_close(struct snd_rawmidi_substream *substream) +{ + struct snd_dice *dice = substream->rmidi->private_data; + + mutex_lock(&dice->mutex); + + dice->substreams_counter--; + snd_dice_stream_stop_duplex(dice); + + mutex_unlock(&dice->mutex); + + snd_dice_stream_lock_release(dice); + return 0; +} + +static int playback_close(struct snd_rawmidi_substream *substream) +{ + struct snd_dice *dice = substream->rmidi->private_data; + + mutex_lock(&dice->mutex); + + dice->substreams_counter--; + snd_dice_stream_stop_duplex(dice); + + mutex_unlock(&dice->mutex); + + snd_dice_stream_lock_release(dice); + return 0; +} + +static void capture_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_dice *dice = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&dice->lock, flags); + + if (up) + amdtp_stream_midi_trigger(&dice->tx_stream, + substrm->number, substrm); + else + amdtp_stream_midi_trigger(&dice->tx_stream, + substrm->number, NULL); + + spin_unlock_irqrestore(&dice->lock, flags); +} + +static void playback_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_dice *dice = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&dice->lock, flags); + + if (up) + amdtp_stream_midi_trigger(&dice->rx_stream, + substrm->number, substrm); + else + amdtp_stream_midi_trigger(&dice->rx_stream, + substrm->number, NULL); + + spin_unlock_irqrestore(&dice->lock, flags); +} + +static struct snd_rawmidi_ops capture_ops = { + .open = capture_open, + .close = capture_close, + .trigger = capture_trigger, +}; + +static struct snd_rawmidi_ops playback_ops = { + .open = playback_open, + .close = playback_close, + .trigger = playback_trigger, +}; + +static void set_midi_substream_names(struct snd_dice *dice, + struct snd_rawmidi_str *str) +{ + struct snd_rawmidi_substream *subs; + + list_for_each_entry(subs, &str->substreams, list) { + snprintf(subs->name, sizeof(subs->name), + "%s MIDI %d", dice->card->shortname, subs->number + 1); + } +} + +int snd_dice_create_midi(struct snd_dice *dice) +{ + struct snd_rawmidi *rmidi; + struct snd_rawmidi_str *str; + unsigned int i, midi_in_ports, midi_out_ports; + int err; + + midi_in_ports = midi_out_ports = 0; + for (i = 0; i < 3; i++) { + midi_in_ports = max(dice->tx_midi_ports[i], midi_in_ports); + midi_out_ports = max(dice->rx_midi_ports[i], midi_out_ports); + } + + if (midi_in_ports + midi_out_ports == 0) + return 0; + + /* create midi ports */ + err = snd_rawmidi_new(dice->card, dice->card->driver, 0, + midi_out_ports, midi_in_ports, + &rmidi); + if (err < 0) + return err; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", dice->card->shortname); + rmidi->private_data = dice; + + if (midi_in_ports > 0) { + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &capture_ops); + + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]; + + set_midi_substream_names(dice, str); + } + + if (midi_out_ports > 0) { + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &playback_ops); + + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]; + + set_midi_substream_names(dice, str); + } + + if ((midi_out_ports > 0) && (midi_in_ports > 0)) + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; + + return 0; +} diff --git a/sound/firewire/dice/dice-stream.c b/sound/firewire/dice/dice-stream.c index 20765a0..fa9cf76 100644 --- a/sound/firewire/dice/dice-stream.c +++ b/sound/firewire/dice/dice-stream.c @@ -218,6 +218,8 @@ int snd_dice_stream_start_duplex(struct snd_dice *dice, unsigned int rate) "fail to get sampling rate\n"); goto end; } + if (rate == 0) + rate = curr_rate; if (rate != curr_rate) stop_stream(dice, master);
diff --git a/sound/firewire/dice/dice.c b/sound/firewire/dice/dice.c index 85bcfaf..90d8f40 100644 --- a/sound/firewire/dice/dice.c +++ b/sound/firewire/dice/dice.c @@ -279,6 +279,10 @@ static int dice_probe(struct fw_unit *unit, const struct ieee1394_device_id *id)
snd_dice_create_proc(dice);
+ err = snd_dice_create_midi(dice); + if (err < 0) + goto error; + err = snd_dice_stream_init_duplex(dice); if (err < 0) goto error; diff --git a/sound/firewire/dice/dice.h b/sound/firewire/dice/dice.h index f30326e..ecf5dc8 100644 --- a/sound/firewire/dice/dice.h +++ b/sound/firewire/dice/dice.h @@ -32,6 +32,7 @@ #include <sound/initval.h> #include <sound/pcm.h> #include <sound/pcm_params.h> +#include <sound/rawmidi.h>
#include "../amdtp.h" #include "../iso-resources.h" @@ -183,4 +184,6 @@ int snd_dice_create_hwdep(struct snd_dice *dice);
void snd_dice_create_proc(struct snd_dice *dice);
+int snd_dice_create_midi(struct snd_dice *dice); + #endif
Takashi Sakamoto wrote:
This commit adds a support for MIDI capture/playback
+++ b/sound/firewire/dice/dice-midi.c +static int capture_open(struct snd_rawmidi_substream *substream) +{
- struct snd_dice *dice = substream->rmidi->private_data;
- int err;
- err = snd_dice_stream_lock_try(dice);
- if (err < 0)
return err;
- mutex_lock(&dice->mutex);
- dice->substreams_counter++;
- err = snd_dice_stream_start_duplex(dice, 0);
- mutex_unlock(&dice->mutex);
- if (err < 0)
snd_dice_stream_lock_release(dice);
- return err;
+}
+static int playback_open(struct snd_rawmidi_substream *substream) +{
- struct snd_dice *dice = substream->rmidi->private_data;
- int err;
- err = snd_dice_stream_lock_try(dice);
- if (err < 0)
return err;
- mutex_lock(&dice->mutex);
- dice->substreams_counter++;
- err = snd_dice_stream_start_duplex(dice, 0);
- mutex_unlock(&dice->mutex);
- if (err < 0)
snd_dice_stream_lock_release(dice);
- return err;
+}
These two functions are identical.
+static int capture_close(struct snd_rawmidi_substream *substream) +{
- struct snd_dice *dice = substream->rmidi->private_data;
- mutex_lock(&dice->mutex);
- dice->substreams_counter--;
- snd_dice_stream_stop_duplex(dice);
- mutex_unlock(&dice->mutex);
- snd_dice_stream_lock_release(dice);
- return 0;
+}
+static int playback_close(struct snd_rawmidi_substream *substream) +{
- struct snd_dice *dice = substream->rmidi->private_data;
- mutex_lock(&dice->mutex);
- dice->substreams_counter--;
- snd_dice_stream_stop_duplex(dice);
- mutex_unlock(&dice->mutex);
- snd_dice_stream_lock_release(dice);
- return 0;
+}
These two too.
+static void set_midi_substream_names(struct snd_dice *dice,
struct snd_rawmidi_str *str)
+{
- list_for_each_entry(subs, &str->substreams, list) {
snprintf(subs->name, sizeof(subs->name),
"%s MIDI %d", dice->card->shortname, subs->number + 1);
This is not really important, but the number is useless when the device has only one MIDI port.
Regards, Clemens
On Dec 8 2014 06:44, Clemens Ladisch wrote:
Takashi Sakamoto wrote:
This commit adds a support for MIDI capture/playback
+++ b/sound/firewire/dice/dice-midi.c +static int capture_open(struct snd_rawmidi_substream *substream) +{ ... +}
+static int playback_open(struct snd_rawmidi_substream *substream) +{ ... +}
These two functions are identical.
+static int capture_close(struct snd_rawmidi_substream *substream) +{ ... +}
+static int playback_close(struct snd_rawmidi_substream *substream) +{ ... +}
These two too.
Indeed. We can use the same functions for each of MIDI open/close callback.
+static void set_midi_substream_names(struct snd_dice *dice,
struct snd_rawmidi_str *str)
+{
- list_for_each_entry(subs, &str->substreams, list) {
snprintf(subs->name, sizeof(subs->name),
"%s MIDI %d", dice->card->shortname, subs->number + 1);
This is not really important, but the number is useless when the device has only one MIDI port.
Fireworks/BeBoB/Dice/OXFW drivers have the same codes to name MIDI ports. So I want to change them in the same time, later.
This below code is for your idea.
static void set_midi_substream_names(const char *shortname, struct snd_rawmidi_str *str) { struct snd_rawmidi_substream *subs;
list_for_each_entry(subs, &str->substreams, list) { if (str->substream_count == 1) { snprintf(subs->name, sizeof(subs->name), "%s MIDI", shortname); break; }
snprintf(subs->name, sizeof(subs->name), "%s MIDI %d", shortname, subs->number + 1); } }
Hmm. I can see the 'shortname' again...
If I apply "vendor model" template for the shortname, the length of MIDI ports are too long for MIDI port name. For example,
$ cat /proc/asound/card0/midi0 Echo Digital Audio AudioFire4 MIDI ...
$ amidi -lDir Device Name IO hw:0,0,0 Echo Digital Audio AudioFire4 M(trimmed)
Currently I apply "vendor model" template for OXFW driver only, but I should reconsider about this...
Regards
Takashi Sakamoto o-takashi@sakamocchi.jp
This is a preparation for more models. In following commit, members of 'struct snd_card' related to name becomes to consists of vendor and model strings in device's config-rom.
Current supported devices also has strings in their config rom, but the strings are too long to name sound card, thus this driver still keep hard-coded vendor and model names for them.
This commit changes the rule for short name. It consists of "vendor model" name. This change causes no regression of identifical name for current supported devices because ALSA framework pick up the last word from shorname for its identical name. The old shortname for these devices is the same as model name, thus the identical name is still the last word of model name.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/oxfw/oxfw.c | 55 ++++++++++++++++++++++++++++------------------ sound/firewire/oxfw/oxfw.h | 4 ++-- 2 files changed, 36 insertions(+), 23 deletions(-)
diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c index 951d9a4..1a08330 100644 --- a/sound/firewire/oxfw/oxfw.c +++ b/sound/firewire/oxfw/oxfw.c @@ -25,14 +25,36 @@ MODULE_AUTHOR("Clemens Ladisch clemens@ladisch.de"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("snd-firewire-speakers");
-static u32 oxfw_read_firmware_version(struct fw_unit *unit) +static int name_card(struct snd_oxfw *oxfw) { - __be32 data; + struct fw_device *fw_dev = fw_parent_device(oxfw->unit); + const char *d, *v, *m; + u32 firmware; int err;
- err = snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST, - OXFORD_FIRMWARE_ID_ADDRESS, &data, 4, 0); - return err >= 0 ? be32_to_cpu(data) : 0; + err = snd_fw_transaction(oxfw->unit, TCODE_READ_QUADLET_REQUEST, + OXFORD_FIRMWARE_ID_ADDRESS, &firmware, 4, 0); + if (err < 0) + goto end; + be32_to_cpus(&firmware); + + d = oxfw->device_info->driver_name; + v = oxfw->device_info->vendor_name; + m = oxfw->device_info->model_name; + + strcpy(oxfw->card->driver, d); + strcpy(oxfw->card->mixername, m); + + snprintf(oxfw->card->shortname, sizeof(oxfw->card->shortname), + "%s %s", v, m); + + snprintf(oxfw->card->longname, sizeof(oxfw->card->longname), + "%s %s (OXFW%x %04x), GUID %08x%08x at %s, S%d", + v, m, firmware >> 20, firmware & 0xffff, + fw_dev->config_rom[3], fw_dev->config_rom[4], + dev_name(&oxfw->unit->device), 100 << fw_dev->max_speed); +end: + return err; }
static void oxfw_card_free(struct snd_card *card) @@ -45,10 +67,8 @@ static void oxfw_card_free(struct snd_card *card) static int oxfw_probe(struct fw_unit *unit, const struct ieee1394_device_id *id) { - struct fw_device *fw_dev = fw_parent_device(unit); struct snd_card *card; struct snd_oxfw *oxfw; - u32 firmware; int err;
err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE, @@ -63,16 +83,9 @@ static int oxfw_probe(struct fw_unit *unit, oxfw->unit = unit; oxfw->device_info = (const struct device_info *)id->driver_data;
- strcpy(card->driver, oxfw->device_info->driver_name); - strcpy(card->shortname, oxfw->device_info->short_name); - firmware = oxfw_read_firmware_version(unit); - snprintf(card->longname, sizeof(card->longname), - "%s (OXFW%x %04x), GUID %08x%08x at %s, S%d", - oxfw->device_info->long_name, - firmware >> 20, firmware & 0xffff, - fw_dev->config_rom[3], fw_dev->config_rom[4], - dev_name(&unit->device), 100 << fw_dev->max_speed); - strcpy(card->mixername, "OXFW"); + err = name_card(oxfw); + if (err < 0) + goto error;
err = snd_oxfw_create_pcm(oxfw); if (err < 0) @@ -123,8 +136,8 @@ static void oxfw_remove(struct fw_unit *unit)
static const struct device_info griffin_firewave = { .driver_name = "FireWave", - .short_name = "FireWave", - .long_name = "Griffin FireWave Surround", + .vendor_name = "Griffin", + .model_name = "FireWave", .pcm_constraints = firewave_constraints, .mixer_channels = 6, .mute_fb_id = 0x01, @@ -133,8 +146,8 @@ static const struct device_info griffin_firewave = {
static const struct device_info lacie_speakers = { .driver_name = "FWSpeakers", - .short_name = "FireWire Speakers", - .long_name = "LaCie FireWire Speakers", + .vendor_name = "LaCie", + .model_name = "FireWire Speakers", .pcm_constraints = lacie_speakers_constraints, .mixer_channels = 1, .mute_fb_id = 0x01, diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h index 6164bf3..a61c75c 100644 --- a/sound/firewire/oxfw/oxfw.h +++ b/sound/firewire/oxfw/oxfw.h @@ -28,8 +28,8 @@
struct device_info { const char *driver_name; - const char *short_name; - const char *long_name; + const char *vendor_name; + const char *model_name; int (*pcm_constraints)(struct snd_pcm_runtime *runtime); unsigned int mixer_channels; u8 mute_fb_id;
OXFW970/971 may supports AV/C Stream Format Information Specification 1.1 Working Draft (Apr 2005, 1394TA). By using this command, drivers can get to know stream formations which device supports.
This commit adds 'EXTENDED STREAM FORMAT INFORMATION' command. This command has two subfunctions, 'SINGLE' and 'LIST'. Drivers can use 'SINGLE' subfunction to know/set current formation of AMDTP stream, Drivers can use 'LIST' subfunction to know an available formation of AMDTP stream in a certain sampling rate.
But some devices don't implement the 'LIST' subfunction. So this commit uses an assumption that 'if they don't implement it, they don't change stream formation depending on current each sampling rate'. With this assumption, this driver generates formations for such devices by: 1.getting current formation by SINGLE subfunction 2.getting supported sampling rates 3.applying current formation for all of supported sampling rates
Followed commit implements a parser of this format information.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/oxfw/Makefile | 2 +- sound/firewire/oxfw/oxfw-command.c | 153 +++++++++++++++++++++++++++++++++++++ sound/firewire/oxfw/oxfw.h | 33 ++++++++ 3 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/oxfw/oxfw-command.c
diff --git a/sound/firewire/oxfw/Makefile b/sound/firewire/oxfw/Makefile index 0cf48fd..b107134 100644 --- a/sound/firewire/oxfw/Makefile +++ b/sound/firewire/oxfw/Makefile @@ -1,2 +1,2 @@ -snd-oxfw-objs := oxfw-stream.o oxfw-control.o oxfw-pcm.o oxfw.o +snd-oxfw-objs := oxfw-command.o oxfw-stream.o oxfw-control.o oxfw-pcm.o oxfw.o obj-m += snd-oxfw.o diff --git a/sound/firewire/oxfw/oxfw-command.c b/sound/firewire/oxfw/oxfw-command.c new file mode 100644 index 0000000..8c603c5 --- /dev/null +++ b/sound/firewire/oxfw/oxfw-command.c @@ -0,0 +1,153 @@ +/* + * oxfw_command.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) 2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "oxfw.h" + +int avc_stream_set_format(struct fw_unit *unit, enum avc_general_plug_dir dir, + unsigned int pid, u8 *format, unsigned int len) +{ + u8 *buf; + int err; + + buf = kmalloc(len + 10, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x00; /* CONTROL */ + buf[1] = 0xff; /* UNIT */ + buf[2] = 0xbf; /* EXTENDED STREAM FORMAT INFORMATION */ + buf[3] = 0xc0; /* SINGLE subfunction */ + buf[4] = dir; /* Plug Direction */ + buf[5] = 0x00; /* UNIT */ + buf[6] = 0x00; /* PCR (Isochronous Plug) */ + buf[7] = 0xff & pid; /* Plug ID */ + buf[8] = 0xff; /* Padding */ + buf[9] = 0xff; /* Support status in response */ + memcpy(buf + 10, format, len); + + /* do transaction and check buf[1-7] are the same against command */ + err = fcp_avc_transaction(unit, buf, len + 10, buf, len + 10, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(7) | BIT(8)); + if ((err > 0) && (err < len + 10)) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else + err = 0; + + kfree(buf); + + return err; +} + +int avc_stream_get_format(struct fw_unit *unit, + enum avc_general_plug_dir dir, unsigned int pid, + u8 *buf, unsigned int *len, unsigned int eid) +{ + unsigned int subfunc; + int err; + + if (eid == 0xff) + subfunc = 0xc0; /* SINGLE */ + else + subfunc = 0xc1; /* LIST */ + + buf[0] = 0x01; /* STATUS */ + buf[1] = 0xff; /* UNIT */ + buf[2] = 0xbf; /* EXTENDED STREAM FORMAT INFORMATION */ + buf[3] = subfunc; /* SINGLE or LIST */ + buf[4] = dir; /* Plug Direction */ + buf[5] = 0x00; /* Unit */ + buf[6] = 0x00; /* PCR (Isochronous Plug) */ + buf[7] = 0xff & pid; /* Plug ID */ + buf[8] = 0xff; /* Padding */ + buf[9] = 0xff; /* support status in response */ + buf[10] = 0xff & eid; /* entry ID for LIST subfunction */ + buf[11] = 0xff; /* padding */ + + /* do transaction and check buf[1-7] are the same against command */ + err = fcp_avc_transaction(unit, buf, 12, buf, *len, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(7)); + if ((err > 0) && (err < 10)) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (buf[0] == 0x0b) /* IN TRANSITION */ + err = -EAGAIN; + /* LIST subfunction has entry ID */ + else if ((subfunc == 0xc1) && (buf[10] != eid)) + err = -EIO; + if (err < 0) + goto end; + + /* keep just stream format information */ + if (subfunc == 0xc0) { + memmove(buf, buf + 10, err - 10); + *len = err - 10; + } else { + memmove(buf, buf + 11, err - 11); + *len = err - 11; + } + + err = 0; +end: + return err; +} + +int avc_general_inquiry_sig_fmt(struct fw_unit *unit, unsigned int rate, + enum avc_general_plug_dir dir, + unsigned short pid) +{ + unsigned int sfc; + u8 *buf; + int err; + + for (sfc = 0; sfc < CIP_SFC_COUNT; sfc++) { + if (amdtp_rate_table[sfc] == rate) + break; + } + if (sfc == CIP_SFC_COUNT) + return -EINVAL; + + buf = kzalloc(8, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x02; /* SPECIFIC INQUIRY */ + buf[1] = 0xff; /* UNIT */ + if (dir == AVC_GENERAL_PLUG_DIR_IN) + buf[2] = 0x19; /* INPUT PLUG SIGNAL FORMAT */ + else + buf[2] = 0x18; /* OUTPUT PLUG SIGNAL FORMAT */ + buf[3] = 0xff & pid; /* plug id */ + buf[4] = 0x90; /* EOH_1, Form_1, FMT. AM824 */ + buf[5] = 0x07 & sfc; /* FDF-hi. AM824, frequency */ + buf[6] = 0xff; /* FDF-mid. AM824, SYT hi (not used) */ + buf[7] = 0xff; /* FDF-low. AM824, SYT lo (not used) */ + + /* do transaction and check buf[1-5] are the same against command */ + err = fcp_avc_transaction(unit, buf, 8, buf, 8, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5)); + if ((err > 0) && (err < 8)) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + if (err < 0) + goto end; + + err = 0; +end: + kfree(buf); + return err; +} diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h index a61c75c..a7031d4 100644 --- a/sound/firewire/oxfw/oxfw.h +++ b/sound/firewire/oxfw/oxfw.h @@ -49,6 +49,39 @@ struct snd_oxfw { s16 volume_max; };
+/* + * AV/C Stream Format Information Specification 1.1 Working Draft + * (Apr 2005, 1394TA) + */ +int avc_stream_set_format(struct fw_unit *unit, enum avc_general_plug_dir dir, + unsigned int pid, u8 *format, unsigned int len); +int avc_stream_get_format(struct fw_unit *unit, + enum avc_general_plug_dir dir, unsigned int pid, + u8 *buf, unsigned int *len, unsigned int eid); +static inline int +avc_stream_get_format_single(struct fw_unit *unit, + enum avc_general_plug_dir dir, unsigned int pid, + u8 *buf, unsigned int *len) +{ + return avc_stream_get_format(unit, dir, pid, buf, len, 0xff); +} +static inline int +avc_stream_get_format_list(struct fw_unit *unit, + enum avc_general_plug_dir dir, unsigned int pid, + u8 *buf, unsigned int *len, + unsigned int eid) +{ + return avc_stream_get_format(unit, dir, pid, buf, len, eid); +} + +/* + * AV/C Digital Interface Command Set General Specification 4.2 + * (Sep 2004, 1394TA) + */ +int avc_general_inquiry_sig_fmt(struct fw_unit *unit, unsigned int rate, + enum avc_general_plug_dir dir, + unsigned short pid); + int snd_oxfw_stream_init_simplex(struct snd_oxfw *oxfw); int snd_oxfw_stream_start_simplex(struct snd_oxfw *oxfw); void snd_oxfw_stream_stop_simplex(struct snd_oxfw *oxfw);
Takashi Sakamoto wrote:
+++ b/sound/firewire/oxfw/oxfw-command.c +int avc_stream_set_format(struct fw_unit *unit, enum avc_general_plug_dir dir,
unsigned int pid, u8 *format, unsigned int len)
+{
- buf[0] = 0x00; /* CONTROL */
- buf[1] = 0xff; /* UNIT */
- buf[2] = 0xbf; /* EXTENDED STREAM FORMAT INFORMATION */
- buf[3] = 0xc0; /* SINGLE subfunction */
- buf[4] = dir; /* Plug Direction */
- buf[5] = 0x00; /* UNIT */
- buf[6] = 0x00; /* PCR (Isochronous Plug) */
- buf[7] = 0xff & pid; /* Plug ID */
- buf[8] = 0xff; /* Padding */
- buf[9] = 0xff; /* Support status in response */
- memcpy(buf + 10, format, len);
- /* do transaction and check buf[1-7] are the same against command */
- err = fcp_avc_transaction(unit, buf, len + 10, buf, len + 10,
BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
BIT(6) | BIT(7) | BIT(8));
The comment says 1-7, the code says 1-8.
Regards, Clemens
On Dec 6 2014 07:01, Clemens Ladisch wrote:
Takashi Sakamoto wrote:
+++ b/sound/firewire/oxfw/oxfw-command.c +int avc_stream_set_format(struct fw_unit *unit, enum avc_general_plug_dir dir,
unsigned int pid, u8 *format, unsigned int len)
+{
- buf[0] = 0x00; /* CONTROL */
- buf[1] = 0xff; /* UNIT */
- buf[2] = 0xbf; /* EXTENDED STREAM FORMAT INFORMATION */
- buf[3] = 0xc0; /* SINGLE subfunction */
- buf[4] = dir; /* Plug Direction */
- buf[5] = 0x00; /* UNIT */
- buf[6] = 0x00; /* PCR (Isochronous Plug) */
- buf[7] = 0xff & pid; /* Plug ID */
- buf[8] = 0xff; /* Padding */
- buf[9] = 0xff; /* Support status in response */
- memcpy(buf + 10, format, len);
- /* do transaction and check buf[1-7] are the same against command */
- err = fcp_avc_transaction(unit, buf, len + 10, buf, len + 10,
BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
BIT(6) | BIT(7) | BIT(8));
The comment says 1-7, the code says 1-8.
Oops, indeed.
I correct it in my next patchset with the other correction, in a few days.
Thanks
Takashi Sakamoto o-takashi@sakamocchi.jp
In previous commit, this driver can get to know stream formations at each supported sampling rates. This commit uses it to make PCM rules/constraints and obsoletes hard-coded rules/constraints.
For this purpose, this commit adds 'struct snd_oxfw_stream_formation' and snd_oxfw_stream_parse_format() to parse data channel formation of data block.
According to datasheet of OXFW970/971, they support 32.0kHz to 196.0kHz.
As long as developers investigate, some devices are confirmed to have several formats for the same sampling rate.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/oxfw/oxfw-pcm.c | 197 ++++++++++++++++------------ sound/firewire/oxfw/oxfw-stream.c | 268 ++++++++++++++++++++++++++++++++++++++ sound/firewire/oxfw/oxfw.c | 10 +- sound/firewire/oxfw/oxfw.h | 21 ++- 4 files changed, 410 insertions(+), 86 deletions(-)
diff --git a/sound/firewire/oxfw/oxfw-pcm.c b/sound/firewire/oxfw/oxfw-pcm.c index d39f17a..0c0be98 100644 --- a/sound/firewire/oxfw/oxfw-pcm.c +++ b/sound/firewire/oxfw/oxfw-pcm.c @@ -7,117 +7,152 @@
#include "oxfw.h"
-static int firewave_rate_constraint(struct snd_pcm_hw_params *params, - struct snd_pcm_hw_rule *rule) +static int hw_rule_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) { - static unsigned int stereo_rates[] = { 48000, 96000 }; - struct snd_interval *channels = - hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); - struct snd_interval *rate = - hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); - - /* two channels work only at 48/96 kHz */ - if (snd_interval_max(channels) < 6) - return snd_interval_list(rate, 2, stereo_rates, 0); - return 0; + u8 **formats = rule->private; + struct snd_interval *r = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + const struct snd_interval *c = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval t = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + struct snd_oxfw_stream_formation formation; + unsigned int i, err; + + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { + if (formats[i] == NULL) + continue; + + err = snd_oxfw_stream_parse_format(formats[i], &formation); + if (err < 0) + continue; + if (!snd_interval_test(c, formation.pcm)) + continue; + + t.min = min(t.min, formation.rate); + t.max = max(t.max, formation.rate); + + } + return snd_interval_refine(r, &t); }
-static int firewave_channels_constraint(struct snd_pcm_hw_params *params, - struct snd_pcm_hw_rule *rule) +static int hw_rule_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) { - static const struct snd_interval all_channels = { .min = 6, .max = 6 }; - struct snd_interval *rate = - hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); - struct snd_interval *channels = - hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); - - /* 32/44.1 kHz work only with all six channels */ - if (snd_interval_max(rate) < 48000) - return snd_interval_refine(channels, &all_channels); - return 0; + u8 **formats = rule->private; + struct snd_interval *c = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + const struct snd_interval *r = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_oxfw_stream_formation formation; + unsigned int i, j, err; + unsigned int count, list[SND_OXFW_STREAM_FORMAT_ENTRIES] = {0}; + + count = 0; + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { + if (formats[i] == NULL) + break; + + err = snd_oxfw_stream_parse_format(formats[i], &formation); + if (err < 0) + continue; + if (!snd_interval_test(r, formation.rate)) + continue; + if (list[count] == formation.pcm) + continue; + + for (j = 0; j < ARRAY_SIZE(list); j++) { + if (list[j] == formation.pcm) + break; + } + if (j == ARRAY_SIZE(list)) { + list[count] = formation.pcm; + if (++count == ARRAY_SIZE(list)) + break; + } + } + + return snd_interval_list(c, count, list, 0); }
-int firewave_constraints(struct snd_pcm_runtime *runtime) +static void limit_channels_and_rates(struct snd_pcm_hardware *hw, u8 **formats) { - static unsigned int channels_list[] = { 2, 6 }; - static struct snd_pcm_hw_constraint_list channels_list_constraint = { - .count = 2, - .list = channels_list, - }; - int err; + struct snd_oxfw_stream_formation formation; + unsigned int i, err;
- runtime->hw.rates = SNDRV_PCM_RATE_32000 | - SNDRV_PCM_RATE_44100 | - SNDRV_PCM_RATE_48000 | - SNDRV_PCM_RATE_96000; - runtime->hw.channels_max = 6; + hw->channels_min = UINT_MAX; + hw->channels_max = 0;
- err = snd_pcm_hw_constraint_list(runtime, 0, - SNDRV_PCM_HW_PARAM_CHANNELS, - &channels_list_constraint); - if (err < 0) - return err; - err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, - firewave_rate_constraint, NULL, - SNDRV_PCM_HW_PARAM_CHANNELS, -1); - if (err < 0) - return err; - err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, - firewave_channels_constraint, NULL, - SNDRV_PCM_HW_PARAM_RATE, -1); - if (err < 0) - return err; + hw->rate_min = UINT_MAX; + hw->rate_max = 0; + hw->rates = 0;
- return 0; + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { + if (formats[i] == NULL) + break; + + err = snd_oxfw_stream_parse_format(formats[i], &formation); + if (err < 0) + continue; + + hw->channels_min = min(hw->channels_min, formation.pcm); + hw->channels_max = max(hw->channels_max, formation.pcm); + + hw->rate_min = min(hw->rate_min, formation.rate); + hw->rate_max = max(hw->rate_max, formation.rate); + hw->rates |= snd_pcm_rate_to_rate_bit(formation.rate); + } }
-int lacie_speakers_constraints(struct snd_pcm_runtime *runtime) +static void limit_period_and_buffer(struct snd_pcm_hardware *hw) { - runtime->hw.rates = SNDRV_PCM_RATE_32000 | - SNDRV_PCM_RATE_44100 | - SNDRV_PCM_RATE_48000 | - SNDRV_PCM_RATE_88200 | - SNDRV_PCM_RATE_96000; + hw->periods_min = 2; /* SNDRV_PCM_INFO_BATCH */ + hw->periods_max = UINT_MAX;
- return 0; + 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_open(struct snd_pcm_substream *substream) { - static const struct snd_pcm_hardware hardware = { - .info = SNDRV_PCM_INFO_MMAP | - SNDRV_PCM_INFO_MMAP_VALID | - SNDRV_PCM_INFO_BATCH | - SNDRV_PCM_INFO_INTERLEAVED | - SNDRV_PCM_INFO_BLOCK_TRANSFER, - .formats = AMDTP_OUT_PCM_FORMAT_BITS, - .channels_min = 2, - .channels_max = 2, - .buffer_bytes_max = 4 * 1024 * 1024, - .period_bytes_min = 1, - .period_bytes_max = UINT_MAX, - .periods_min = 1, - .periods_max = UINT_MAX, - }; struct snd_oxfw *oxfw = substream->private_data; struct snd_pcm_runtime *runtime = substream->runtime; - bool used; + u8 **formats; int err;
- err = cmp_connection_check_used(&oxfw->in_conn, &used); - if ((err < 0) || used) - goto end; + formats = oxfw->rx_stream_formats; + + runtime->hw.info = SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID;
- runtime->hw = hardware; + limit_channels_and_rates(&runtime->hw, formats); + limit_period_and_buffer(&runtime->hw);
- err = oxfw->device_info->pcm_constraints(runtime); + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + hw_rule_channels, formats, + SNDRV_PCM_HW_PARAM_RATE, -1); if (err < 0) goto end; - err = snd_pcm_limit_hw_rates(runtime); + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + hw_rule_rate, formats, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); if (err < 0) goto end;
err = amdtp_stream_add_pcm_hw_constraints(&oxfw->rx_stream, runtime); + if (err < 0) + goto end; + + snd_pcm_set_sync(substream); end: return err; } diff --git a/sound/firewire/oxfw/oxfw-stream.c b/sound/firewire/oxfw/oxfw-stream.c index ebd156f..17e3802 100644 --- a/sound/firewire/oxfw/oxfw-stream.c +++ b/sound/firewire/oxfw/oxfw-stream.c @@ -8,6 +8,35 @@
#include "oxfw.h"
+#define AVC_GENERIC_FRAME_MAXIMUM_BYTES 512 + +/* + * According to datasheet of Oxford Semiconductor: + * OXFW970: 32.0/44.1/48.0/96.0 Khz, 8 audio channels I/O + * OXFW971: 32.0/44.1/48.0/88.2/96.0/192.0 kHz, 16 audio channels I/O, MIDI I/O + */ +static const unsigned int oxfw_rate_table[] = { + [0] = 32000, + [1] = 44100, + [2] = 48000, + [3] = 88200, + [4] = 96000, + [5] = 192000, +}; + +/* + * See Table 5.7 – Sampling frequency for Multi-bit Audio + * in AV/C Stream Format Information Specification 1.1 (Apr 2005, 1394TA) + */ +static const unsigned int avc_stream_rate_table[] = { + [0] = 0x02, + [1] = 0x03, + [2] = 0x04, + [3] = 0x0a, + [4] = 0x05, + [5] = 0x07, +}; + int snd_oxfw_stream_init_simplex(struct snd_oxfw *oxfw) { int err; @@ -78,3 +107,242 @@ void snd_oxfw_stream_update_simplex(struct snd_oxfw *oxfw) else amdtp_stream_update(&oxfw->rx_stream); } + +/* + * See Table 6.16 - AM824 Stream Format + * Figure 6.19 - format_information field for AM824 Compound + * in AV/C Stream Format Information Specification 1.1 (Apr 2005, 1394TA) + * Also 'Clause 12 AM824 sequence adaption layers' in IEC 61883-6:2005 + */ +int snd_oxfw_stream_parse_format(u8 *format, + struct snd_oxfw_stream_formation *formation) +{ + unsigned int i, e, channels, type; + + memset(formation, 0, sizeof(struct snd_oxfw_stream_formation)); + + /* + * this module can support a hierarchy combination that: + * Root: Audio and Music (0x90) + * Level 1: AM824 Compound (0x40) + */ + if ((format[0] != 0x90) || (format[1] != 0x40)) + return -ENOSYS; + + /* check the sampling rate */ + for (i = 0; i < ARRAY_SIZE(avc_stream_rate_table); i++) { + if (format[2] == avc_stream_rate_table[i]) + break; + } + if (i == ARRAY_SIZE(avc_stream_rate_table)) + return -ENOSYS; + + formation->rate = oxfw_rate_table[i]; + + for (e = 0; e < format[4]; e++) { + channels = format[5 + e * 2]; + type = format[6 + e * 2]; + + switch (type) { + /* IEC 60958 Conformant, currently handled as MBLA */ + case 0x00: + /* Multi Bit Linear Audio (Raw) */ + case 0x06: + formation->pcm += channels; + break; + /* MIDI Conformant */ + case 0x0d: + formation->midi = channels; + break; + /* IEC 61937-3 to 7 */ + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + /* Multi Bit Linear Audio */ + case 0x07: /* DVD-Audio */ + case 0x0c: /* High Precision */ + /* One Bit Audio */ + case 0x08: /* (Plain) Raw */ + case 0x09: /* (Plain) SACD */ + case 0x0a: /* (Encoded) Raw */ + case 0x0b: /* (Encoded) SACD */ + /* SMPTE Time-Code conformant */ + case 0x0e: + /* Sample Count */ + case 0x0f: + /* Anciliary Data */ + case 0x10: + /* Synchronization Stream (Stereo Raw audio) */ + case 0x40: + /* Don't care */ + case 0xff: + default: + return -ENOSYS; /* not supported */ + } + } + + if (formation->pcm > AMDTP_MAX_CHANNELS_FOR_PCM || + formation->midi > AMDTP_MAX_CHANNELS_FOR_MIDI) + return -ENOSYS; + + return 0; +} + +static int +assume_stream_formats(struct snd_oxfw *oxfw, enum avc_general_plug_dir dir, + unsigned int pid, u8 *buf, unsigned int *len, + u8 **formats) +{ + struct snd_oxfw_stream_formation formation; + unsigned int i, eid; + int err; + + /* get format at current sampling rate */ + err = avc_stream_get_format_single(oxfw->unit, dir, pid, buf, len); + if (err < 0) { + dev_err(&oxfw->unit->device, + "fail to get current stream format for isoc %s plug %d:%d\n", + (dir == AVC_GENERAL_PLUG_DIR_IN) ? "in" : "out", + pid, err); + goto end; + } + + /* parse and set stream format */ + eid = 0; + err = snd_oxfw_stream_parse_format(buf, &formation); + if (err < 0) + goto end; + + formats[eid] = kmalloc(*len, GFP_KERNEL); + if (formats[eid] == NULL) { + err = -ENOMEM; + goto end; + } + memcpy(formats[eid], buf, *len); + + /* apply the format for each available sampling rate */ + for (i = 0; i < ARRAY_SIZE(oxfw_rate_table); i++) { + if (formation.rate == oxfw_rate_table[i]) + continue; + + err = avc_general_inquiry_sig_fmt(oxfw->unit, + oxfw_rate_table[i], + dir, pid); + if (err < 0) + continue; + + eid++; + formats[eid] = kmalloc(*len, GFP_KERNEL); + if (formats[eid] == NULL) { + err = -ENOMEM; + goto end; + } + memcpy(formats[eid], buf, *len); + formats[eid][2] = avc_stream_rate_table[i]; + } + + err = 0; + oxfw->assumed = true; +end: + return err; +} + +static int fill_stream_formats(struct snd_oxfw *oxfw, + enum avc_general_plug_dir dir, + unsigned short pid) +{ + u8 *buf, **formats; + unsigned int len, eid = 0; + struct snd_oxfw_stream_formation dummy; + int err; + + buf = kmalloc(AVC_GENERIC_FRAME_MAXIMUM_BYTES, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + formats = oxfw->rx_stream_formats; + + /* get first entry */ + len = AVC_GENERIC_FRAME_MAXIMUM_BYTES; + err = avc_stream_get_format_list(oxfw->unit, dir, 0, buf, &len, 0); + if (err == -ENOSYS) { + /* LIST subfunction is not implemented */ + len = AVC_GENERIC_FRAME_MAXIMUM_BYTES; + err = assume_stream_formats(oxfw, dir, pid, buf, &len, + formats); + goto end; + } else if (err < 0) { + dev_err(&oxfw->unit->device, + "fail to get stream format %d for isoc %s plug %d:%d\n", + eid, (dir == AVC_GENERAL_PLUG_DIR_IN) ? "in" : "out", + pid, err); + goto end; + } + + /* LIST subfunction is implemented */ + while (eid < SND_OXFW_STREAM_FORMAT_ENTRIES) { + /* The format is too short. */ + if (len < 3) { + err = -EIO; + break; + } + + /* parse and set stream format */ + err = snd_oxfw_stream_parse_format(buf, &dummy); + if (err < 0) + break; + + formats[eid] = kmalloc(len, GFP_KERNEL); + if (formats[eid] == NULL) { + err = -ENOMEM; + break; + } + memcpy(formats[eid], buf, len); + + /* get next entry */ + len = AVC_GENERIC_FRAME_MAXIMUM_BYTES; + err = avc_stream_get_format_list(oxfw->unit, dir, 0, + buf, &len, ++eid); + /* No entries remained. */ + if (err == -EINVAL) { + err = 0; + break; + } else if (err < 0) { + dev_err(&oxfw->unit->device, + "fail to get stream format %d for isoc %s plug %d:%d\n", + eid, (dir == AVC_GENERAL_PLUG_DIR_IN) ? "in" : + "out", + pid, err); + break; + } + } +end: + kfree(buf); + return err; +} + +int snd_oxfw_stream_discover(struct snd_oxfw *oxfw) +{ + u8 plugs[AVC_PLUG_INFO_BUF_BYTES]; + int err; + + /* the number of plugs for isoc in/out, ext in/out */ + err = avc_general_get_plug_info(oxfw->unit, 0x1f, 0x07, 0x00, plugs); + if (err < 0) { + dev_err(&oxfw->unit->device, + "fail to get info for isoc/external in/out plugs: %d\n", + err); + goto end; + } else if (plugs[0] == 0) { + err = -ENOSYS; + goto end; + } + + /* use iPCR[0] if exists */ + if (plugs[0] > 0) + err = fill_stream_formats(oxfw, AVC_GENERAL_PLUG_DIR_IN, 0); +end: + return err; +} diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c index 1a08330..d49c12a 100644 --- a/sound/firewire/oxfw/oxfw.c +++ b/sound/firewire/oxfw/oxfw.c @@ -60,6 +60,10 @@ end: static void oxfw_card_free(struct snd_card *card) { struct snd_oxfw *oxfw = card->private_data; + unsigned int i; + + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) + kfree(oxfw->rx_stream_formats[i]);
mutex_destroy(&oxfw->mutex); } @@ -83,6 +87,10 @@ static int oxfw_probe(struct fw_unit *unit, oxfw->unit = unit; oxfw->device_info = (const struct device_info *)id->driver_data;
+ err = snd_oxfw_stream_discover(oxfw); + if (err < 0) + goto error; + err = name_card(oxfw); if (err < 0) goto error; @@ -138,7 +146,6 @@ static const struct device_info griffin_firewave = { .driver_name = "FireWave", .vendor_name = "Griffin", .model_name = "FireWave", - .pcm_constraints = firewave_constraints, .mixer_channels = 6, .mute_fb_id = 0x01, .volume_fb_id = 0x02, @@ -148,7 +155,6 @@ static const struct device_info lacie_speakers = { .driver_name = "FWSpeakers", .vendor_name = "LaCie", .model_name = "FireWire Speakers", - .pcm_constraints = lacie_speakers_constraints, .mixer_channels = 1, .mute_fb_id = 0x01, .volume_fb_id = 0x01, diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h index a7031d4..9c3d3e3 100644 --- a/sound/firewire/oxfw/oxfw.h +++ b/sound/firewire/oxfw/oxfw.h @@ -30,19 +30,24 @@ struct device_info { const char *driver_name; const char *vendor_name; const char *model_name; - int (*pcm_constraints)(struct snd_pcm_runtime *runtime); unsigned int mixer_channels; u8 mute_fb_id; u8 volume_fb_id; };
+/* This is an arbitrary number for convinience. */ +#define SND_OXFW_STREAM_FORMAT_ENTRIES 10 struct snd_oxfw { struct snd_card *card; struct fw_unit *unit; const struct device_info *device_info; struct mutex mutex; + + u8 *rx_stream_formats[SND_OXFW_STREAM_FORMAT_ENTRIES]; + bool assumed; struct cmp_connection in_conn; struct amdtp_stream rx_stream; + bool mute; s16 volume[6]; s16 volume_min; @@ -88,8 +93,18 @@ void snd_oxfw_stream_stop_simplex(struct snd_oxfw *oxfw); void snd_oxfw_stream_destroy_simplex(struct snd_oxfw *oxfw); void snd_oxfw_stream_update_simplex(struct snd_oxfw *oxfw);
-int firewave_constraints(struct snd_pcm_runtime *runtime); -int lacie_speakers_constraints(struct snd_pcm_runtime *runtime); +struct snd_oxfw_stream_formation { + unsigned int rate; + unsigned int pcm; + unsigned int midi; +}; +int snd_oxfw_stream_parse_format(u8 *format, + struct snd_oxfw_stream_formation *formation); +int snd_oxfw_stream_get_current_formation(struct snd_oxfw *oxfw, + enum avc_general_plug_dir dir, + struct snd_oxfw_stream_formation *formation); +int snd_oxfw_stream_discover(struct snd_oxfw *oxfw); + int snd_oxfw_create_pcm(struct snd_oxfw *oxfw);
int snd_oxfw_create_mixer(struct snd_oxfw *oxfw);
This commit adds proc interface to get information about stream formation. This commit also adds snd_oxfw_stream_get_current_formation() to get current stream formation.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/oxfw/Makefile | 3 +- sound/firewire/oxfw/oxfw-proc.c | 84 +++++++++++++++++++++++++++++++++++++++ sound/firewire/oxfw/oxfw-stream.c | 27 +++++++++++++ sound/firewire/oxfw/oxfw.c | 2 + sound/firewire/oxfw/oxfw.h | 3 ++ 5 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/oxfw/oxfw-proc.c
diff --git a/sound/firewire/oxfw/Makefile b/sound/firewire/oxfw/Makefile index b107134..e9297c6 100644 --- a/sound/firewire/oxfw/Makefile +++ b/sound/firewire/oxfw/Makefile @@ -1,2 +1,3 @@ -snd-oxfw-objs := oxfw-command.o oxfw-stream.o oxfw-control.o oxfw-pcm.o oxfw.o +snd-oxfw-objs := oxfw-command.o oxfw-stream.o oxfw-control.o oxfw-pcm.o \ + oxfw-proc.o oxfw.o obj-m += snd-oxfw.o diff --git a/sound/firewire/oxfw/oxfw-proc.c b/sound/firewire/oxfw/oxfw-proc.c new file mode 100644 index 0000000..18e0305 --- /dev/null +++ b/sound/firewire/oxfw/oxfw-proc.c @@ -0,0 +1,84 @@ +/* + * oxfw_proc.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) 2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./oxfw.h" + +static void proc_read_formation(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_oxfw *oxfw = entry->private_data; + struct snd_oxfw_stream_formation formation, curr; + u8 *format; + char flag; + unsigned int i, err; + + /* Show input. */ + err = snd_oxfw_stream_get_current_formation(oxfw, + AVC_GENERAL_PLUG_DIR_IN, + &curr); + if (err < 0) + return; + + snd_iprintf(buffer, "Input Stream to device:\n"); + snd_iprintf(buffer, "\tRate\tPCM\tMIDI\n"); + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { + format = oxfw->rx_stream_formats[i]; + if (format == NULL) + continue; + + err = snd_oxfw_stream_parse_format(format, &formation); + if (err < 0) + continue; + + if (memcmp(&formation, &curr, sizeof(curr)) == 0) + flag = '*'; + else + flag = ' '; + + snd_iprintf(buffer, "%c\t%d\t%d\t%d\n", flag, + formation.rate, formation.pcm, formation.midi); + } + +} + +static void add_node(struct snd_oxfw *oxfw, struct snd_info_entry *root, + const char *name, + void (*op)(struct snd_info_entry *e, + struct snd_info_buffer *b)) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_card_entry(oxfw->card, name, root); + if (entry == NULL) + return; + + snd_info_set_text_ops(entry, oxfw, op); + if (snd_info_register(entry) < 0) + snd_info_free_entry(entry); +} + +void snd_oxfw_proc_init(struct snd_oxfw *oxfw) +{ + struct snd_info_entry *root; + + /* + * All nodes are automatically removed at snd_card_disconnect(), + * by following to link list. + */ + root = snd_info_create_card_entry(oxfw->card, "firewire", + oxfw->card->proc_root); + if (root == NULL) + return; + root->mode = S_IFDIR | S_IRUGO | S_IXUGO; + if (snd_info_register(root) < 0) { + snd_info_free_entry(root); + return; + } + + add_node(oxfw, root, "formation", proc_read_formation); +} diff --git a/sound/firewire/oxfw/oxfw-stream.c b/sound/firewire/oxfw/oxfw-stream.c index 17e3802..210bf5a 100644 --- a/sound/firewire/oxfw/oxfw-stream.c +++ b/sound/firewire/oxfw/oxfw-stream.c @@ -108,6 +108,33 @@ void snd_oxfw_stream_update_simplex(struct snd_oxfw *oxfw) amdtp_stream_update(&oxfw->rx_stream); }
+int snd_oxfw_stream_get_current_formation(struct snd_oxfw *oxfw, + enum avc_general_plug_dir dir, + struct snd_oxfw_stream_formation *formation) +{ + u8 *format; + unsigned int len; + int err; + + len = AVC_GENERIC_FRAME_MAXIMUM_BYTES; + format = kmalloc(len, GFP_KERNEL); + if (format == NULL) + return -ENOMEM; + + err = avc_stream_get_format_single(oxfw->unit, dir, 0, format, &len); + if (err < 0) + goto end; + if (len < 3) { + err = -EIO; + goto end; + } + + err = snd_oxfw_stream_parse_format(format, formation); +end: + kfree(format); + return err; +} + /* * See Table 6.16 - AM824 Stream Format * Figure 6.19 - format_information field for AM824 Compound diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c index d49c12a..4256398 100644 --- a/sound/firewire/oxfw/oxfw.c +++ b/sound/firewire/oxfw/oxfw.c @@ -103,6 +103,8 @@ static int oxfw_probe(struct fw_unit *unit, if (err < 0) goto error;
+ snd_oxfw_proc_init(oxfw); + err = snd_oxfw_stream_init_simplex(oxfw); if (err < 0) goto error; diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h index 9c3d3e3..8c832ea 100644 --- a/sound/firewire/oxfw/oxfw.h +++ b/sound/firewire/oxfw/oxfw.h @@ -18,6 +18,7 @@ #include <sound/initval.h> #include <sound/pcm.h> #include <sound/pcm_params.h> +#include <sound/info.h>
#include "../lib.h" #include "../fcp.h" @@ -108,3 +109,5 @@ int snd_oxfw_stream_discover(struct snd_oxfw *oxfw); int snd_oxfw_create_pcm(struct snd_oxfw *oxfw);
int snd_oxfw_create_mixer(struct snd_oxfw *oxfw); + +void snd_oxfw_proc_init(struct snd_oxfw *oxfw);
In past commit, this driver can keep stream formations for each sampling rate. So its stream functionality can decide stream formations with given some parameters.
This commit moves related codes from PCM functionality to stream functionality. Furthermore, to set stream format correctly, this commit uses AV/C Stream Format Information command instead of AV/C Input/Output Plug Signal Format command.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/oxfw/oxfw-pcm.c | 44 ++---------- sound/firewire/oxfw/oxfw-stream.c | 139 +++++++++++++++++++++++++++++++++++--- sound/firewire/oxfw/oxfw.h | 3 +- 3 files changed, 138 insertions(+), 48 deletions(-)
diff --git a/sound/firewire/oxfw/oxfw-pcm.c b/sound/firewire/oxfw/oxfw-pcm.c index 0c0be98..ea2b439 100644 --- a/sound/firewire/oxfw/oxfw-pcm.c +++ b/sound/firewire/oxfw/oxfw-pcm.c @@ -166,39 +166,10 @@ static int pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params) { struct snd_oxfw *oxfw = substream->private_data; - int err; - - mutex_lock(&oxfw->mutex); - - snd_oxfw_stream_stop_simplex(oxfw); - - err = snd_pcm_lib_alloc_vmalloc_buffer(substream, - params_buffer_bytes(hw_params)); - if (err < 0) - goto error; - - amdtp_stream_set_parameters(&oxfw->rx_stream, - params_rate(hw_params), - params_channels(hw_params), - 0); - - amdtp_stream_set_pcm_format(&oxfw->rx_stream, - params_format(hw_params)); - - err = avc_general_set_sig_fmt(oxfw->unit, params_rate(hw_params), - AVC_GENERAL_PLUG_DIR_IN, 0); - if (err < 0) { - dev_err(&oxfw->unit->device, "failed to set sample rate\n"); - goto err_buffer; - }
- return 0; - -err_buffer: - snd_pcm_lib_free_vmalloc_buffer(substream); -error: - mutex_unlock(&oxfw->mutex); - return err; + amdtp_stream_set_pcm_format(&oxfw->rx_stream, params_format(hw_params)); + return snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); }
static int pcm_hw_free(struct snd_pcm_substream *substream) @@ -215,19 +186,18 @@ static int pcm_hw_free(struct snd_pcm_substream *substream) static int pcm_prepare(struct snd_pcm_substream *substream) { struct snd_oxfw *oxfw = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; int err;
mutex_lock(&oxfw->mutex); - - snd_oxfw_stream_stop_simplex(oxfw); - - err = snd_oxfw_stream_start_simplex(oxfw); + err = snd_oxfw_stream_start_simplex(oxfw, runtime->rate, + runtime->channels); + mutex_unlock(&oxfw->mutex); if (err < 0) goto end;
amdtp_stream_pcm_prepare(&oxfw->rx_stream); end: - mutex_unlock(&oxfw->mutex); return err; }
diff --git a/sound/firewire/oxfw/oxfw-stream.c b/sound/firewire/oxfw/oxfw-stream.c index 210bf5a..1820497 100644 --- a/sound/firewire/oxfw/oxfw-stream.c +++ b/sound/firewire/oxfw/oxfw-stream.c @@ -7,8 +7,10 @@ */
#include "oxfw.h" +#include <linux/delay.h>
#define AVC_GENERIC_FRAME_MAXIMUM_BYTES 512 +#define CALLBACK_TIMEOUT 200
/* * According to datasheet of Oxford Semiconductor: @@ -37,6 +39,47 @@ static const unsigned int avc_stream_rate_table[] = { [5] = 0x07, };
+static int set_stream_format(struct snd_oxfw *oxfw, struct amdtp_stream *s, + unsigned int rate, unsigned int pcm_channels) +{ + u8 **formats; + struct snd_oxfw_stream_formation formation; + enum avc_general_plug_dir dir; + unsigned int i, err, len; + + formats = oxfw->rx_stream_formats; + dir = AVC_GENERAL_PLUG_DIR_IN; + + /* Seek stream format for requirements. */ + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { + err = snd_oxfw_stream_parse_format(formats[i], &formation); + if (err < 0) + return err; + + if ((formation.rate == rate) && (formation.pcm == pcm_channels)) + break; + } + if (i == SND_OXFW_STREAM_FORMAT_ENTRIES) + return -EINVAL; + + /* If assumed, just change rate. */ + if (oxfw->assumed) + return avc_general_set_sig_fmt(oxfw->unit, rate, + AVC_GENERAL_PLUG_DIR_IN, 0); + + /* Calculate format length. */ + len = 5 + formats[i][4] * 2; + + err = avc_stream_set_format(oxfw->unit, dir, 0, formats[i], len); + if (err < 0) + return err; + + /* Some requests just after changing format causes freezing. */ + msleep(100); + + return 0; +} + int snd_oxfw_stream_init_simplex(struct snd_oxfw *oxfw) { int err; @@ -63,30 +106,106 @@ static void stop_stream(struct snd_oxfw *oxfw) cmp_connection_break(&oxfw->in_conn); }
-int snd_oxfw_stream_start_simplex(struct snd_oxfw *oxfw) +static int start_stream(struct snd_oxfw *oxfw, unsigned int rate, + unsigned int pcm_channels) { - int err = 0; + u8 **formats; + struct cmp_connection *conn; + struct snd_oxfw_stream_formation formation; + unsigned int i, midi_ports; + struct amdtp_stream *stream; + int err;
- if (amdtp_streaming_error(&oxfw->rx_stream)) - stop_stream(oxfw); + stream = &oxfw->rx_stream; + formats = oxfw->rx_stream_formats; + conn = &oxfw->in_conn;
- if (amdtp_stream_running(&oxfw->rx_stream)) + /* Get stream formation */ + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { + if (formats[i] == NULL) + break; + + err = snd_oxfw_stream_parse_format(formats[i], &formation); + if (err < 0) + goto end; + if (rate != formation.rate) + continue; + if (pcm_channels == 0 || pcm_channels == formation.pcm) + break; + } + if (i == SND_OXFW_STREAM_FORMAT_ENTRIES) { + err = -EINVAL; goto end; + }
- err = cmp_connection_establish(&oxfw->in_conn, - amdtp_stream_get_max_payload(&oxfw->rx_stream)); + pcm_channels = formation.pcm; + midi_ports = DIV_ROUND_UP(formation.midi, 8); + + /* The stream should have one pcm channels at least */ + if (pcm_channels == 0) { + err = -EINVAL; + goto end; + } + amdtp_stream_set_parameters(stream, rate, pcm_channels, midi_ports); + + err = cmp_connection_establish(conn, + amdtp_stream_get_max_payload(stream)); if (err < 0) goto end;
- err = amdtp_stream_start(&oxfw->rx_stream, - oxfw->in_conn.resources.channel, - oxfw->in_conn.speed); + err = amdtp_stream_start(stream, + conn->resources.channel, + conn->speed); + if (err < 0) { + cmp_connection_break(conn); + goto end; + } + + /* Wait first packet */ + err = amdtp_stream_wait_callback(stream, CALLBACK_TIMEOUT); if (err < 0) stop_stream(oxfw); end: return err; }
+int snd_oxfw_stream_start_simplex(struct snd_oxfw *oxfw, unsigned int rate, + unsigned int pcm_channels) +{ + struct snd_oxfw_stream_formation formation; + int err = 0; + + /* packet queueing error */ + if (amdtp_streaming_error(&oxfw->rx_stream)) + stop_stream(oxfw); + + err = snd_oxfw_stream_get_current_formation(oxfw, + AVC_GENERAL_PLUG_DIR_IN, + &formation); + if (err < 0) + goto end; + + if ((formation.rate != rate) || (formation.pcm != pcm_channels)) { + stop_stream(oxfw); + + /* arrange sampling rate */ + err = set_stream_format(oxfw, &oxfw->rx_stream, rate, + pcm_channels); + if (err < 0) { + dev_err(&oxfw->unit->device, + "fail to set stream format: %d\n", err); + goto end; + } + } + + err = start_stream(oxfw, rate, pcm_channels); + if (err < 0) + dev_err(&oxfw->unit->device, + "fail to start stream: %d\n", err); +end: + return err; +} + void snd_oxfw_stream_stop_simplex(struct snd_oxfw *oxfw) { stop_stream(oxfw); diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h index 8c832ea..c09ef38 100644 --- a/sound/firewire/oxfw/oxfw.h +++ b/sound/firewire/oxfw/oxfw.h @@ -89,7 +89,8 @@ int avc_general_inquiry_sig_fmt(struct fw_unit *unit, unsigned int rate, unsigned short pid);
int snd_oxfw_stream_init_simplex(struct snd_oxfw *oxfw); -int snd_oxfw_stream_start_simplex(struct snd_oxfw *oxfw); +int snd_oxfw_stream_start_simplex(struct snd_oxfw *oxfw, unsigned int rate, + unsigned int pcm_channels); void snd_oxfw_stream_stop_simplex(struct snd_oxfw *oxfw); void snd_oxfw_stream_destroy_simplex(struct snd_oxfw *oxfw); void snd_oxfw_stream_update_simplex(struct snd_oxfw *oxfw);
Some devices produced by Behringer/Mackie are based on OXFW970/971. This commit adds support for them. Additionally, this commit changes the way to name card with some information in config rom.
Ids of some Mackie(Loud) models are not identified, therefore this commit applies name detection for these models.
The devices support capture/playback of PCM-samples and some of them supports capture/playback of MIDI messages. These functionalities are implemented by followed commits.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/Kconfig | 6 +++ sound/firewire/oxfw/oxfw.c | 91 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 91 insertions(+), 6 deletions(-)
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index 0932860..6364e5b 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -31,6 +31,12 @@ config SND_OXFW Oxford Semiconductor FW970/971 chipset. * Griffin Firewave * LaCie Firewire Speakers + * Behringer F-Control Audio 202 + * Mackie(Loud) Onyx-i series (former models) + * Mackie(Loud) Onyx Satellite + * Mackie(Loud) Tapco Link.Firewire + * Mackie(Loud) d.2 pro/d.4 pro + * Mackie(Loud) U.420/U.420d
To compile this driver as a module, choose M here: the module will be called snd-oxfw. diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c index 4256398..26ce615 100644 --- a/sound/firewire/oxfw/oxfw.c +++ b/sound/firewire/oxfw/oxfw.c @@ -14,7 +14,9 @@ #define OXFORD_HARDWARE_ID_OXFW970 0x39443841 #define OXFORD_HARDWARE_ID_OXFW971 0x39373100
+#define VENDOR_LOUD 0x000ff2 #define VENDOR_GRIFFIN 0x001292 +#define VENDOR_BEHRINGER 0x001564 #define VENDOR_LACIE 0x00d04b
#define SPECIFIER_1394TA 0x00a02d @@ -25,22 +27,69 @@ MODULE_AUTHOR("Clemens Ladisch clemens@ladisch.de"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("snd-firewire-speakers");
+static bool detect_loud_models(struct fw_unit *unit) +{ + const char *const models[] = { + "Onyxi", + "Onyx-i", + "d.Pro", + "Mackie Onyx Satellite", + "Tapco LINK.firewire 4x6", + "U.420"}; + char model[32]; + unsigned int i; + int err; + + err = fw_csr_string(unit->directory, CSR_MODEL, + model, sizeof(model)); + if (err < 0) + return err; + + for (i = 0; i < ARRAY_SIZE(models); i++) { + if (strcmp(models[i], model) == 0) + break; + } + + return (i < ARRAY_SIZE(models)); +} + static int name_card(struct snd_oxfw *oxfw) { struct fw_device *fw_dev = fw_parent_device(oxfw->unit); + char vendor[24]; + char model[32]; const char *d, *v, *m; u32 firmware; int err;
+ /* get vendor name from root directory */ + err = fw_csr_string(fw_dev->config_rom + 5, CSR_VENDOR, + vendor, sizeof(vendor)); + if (err < 0) + goto end; + + /* get model name from unit directory */ + err = fw_csr_string(oxfw->unit->directory, CSR_MODEL, + model, sizeof(model)); + if (err < 0) + goto end; + err = snd_fw_transaction(oxfw->unit, TCODE_READ_QUADLET_REQUEST, OXFORD_FIRMWARE_ID_ADDRESS, &firmware, 4, 0); if (err < 0) goto end; be32_to_cpus(&firmware);
- d = oxfw->device_info->driver_name; - v = oxfw->device_info->vendor_name; - m = oxfw->device_info->model_name; + /* to apply card definitions */ + if (oxfw->device_info) { + d = oxfw->device_info->driver_name; + v = oxfw->device_info->vendor_name; + m = oxfw->device_info->model_name; + } else { + d = "OXFW"; + v = vendor; + m = model; + }
strcpy(oxfw->card->driver, d); strcpy(oxfw->card->mixername, m); @@ -75,6 +124,9 @@ static int oxfw_probe(struct fw_unit *unit, struct snd_oxfw *oxfw; int err;
+ if ((id->vendor_id == VENDOR_LOUD) && !detect_loud_models(unit)) + return -ENODEV; + err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE, sizeof(*oxfw), &card); if (err < 0) @@ -99,9 +151,11 @@ static int oxfw_probe(struct fw_unit *unit, if (err < 0) goto error;
- err = snd_oxfw_create_mixer(oxfw); - if (err < 0) - goto error; + if (oxfw->device_info) { + err = snd_oxfw_create_mixer(oxfw); + if (err < 0) + goto error; + }
snd_oxfw_proc_init(oxfw);
@@ -185,6 +239,31 @@ static const struct ieee1394_device_id oxfw_id_table[] = { .version = VERSION_AVC, .driver_data = (kernel_ulong_t)&lacie_speakers, }, + /* Behringer,F-Control Audio 202 */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = VENDOR_BEHRINGER, + .model_id = 0x00fc22, + }, + /* + * Any Mackie(Loud) models (name string/model id): + * Onyx-i series (former models): 0x081216 + * Mackie Onyx Satellite: 0x00200f + * Tapco LINK.firewire 4x6: 0x000460 + * d.2 pro: Unknown + * d.4 pro: Unknown + * U.420: Unknown + * U.420d: Unknown + */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_SPECIFIER_ID | + IEEE1394_MATCH_VERSION, + .vendor_id = VENDOR_LOUD, + .specifier_id = SPECIFIER_1394TA, + .version = VERSION_AVC, + }, { } }; MODULE_DEVICE_TABLE(ieee1394, oxfw_id_table);
Previous commit adds support for some devices which can capture PCM samples. These devices transmit AMDTP stream in non-blocking mode. This commit adds functionality to handle AMDTP incoming stream.
OXFW seems to have two quirks: - Transmits packets with non-zero dbc in its beginning - Transmits packets with wrong values in syt field
For the first quirk, this commit adds CIP_SKIP_INIT_DBC_CHECK flag for incoming stream to skip first check of dbc.
For the second quirk, this commit doesn't add duplex stream which Fireworks/BeBoB drivers use. So OXFW driver generates syt value for outgoing stream.
Here are examples of a sequence of packets transmitted by Behringer F-Control Audio 202. There are differences between sequences of syt value when OXFW driver transfers outgoing stream or not.
When driver gives no outgoing stream: Index Payload CIP_Header_0 CIP_Header_1 38 14 00020092 900103D1 39 12 00020098 900102FF 40 12 0002009D 9001027F 41 14 000200A2 90010396 42 14 000200A8 900102E8 43 12 000200AE 90010219 44 14 000200B3 90010331 45 12 000200B9 9001025F 46 14 000200BE 90010376 47 12 000200C4 900102A1 00 12 000200C9 9001023E 01 14 000200CE 90010358 02 12 000200D4 90010289 03 16 000200D9 900103A3 04 12 000200E0 900102DD 05 14 000200E5 900103F1 06 12 000200EB 90010335 07 12 000200F0 90010263 08 14 000200F5 9001037C 09 12 000200FB 900102AE
When driver gives outgoing stream: Index Payload CIP_Header_0 CIP_Header_1 38 12 000200BD 900104A8 39 14 000200C2 900104A8 40 12 000200C8 900104AC 41 14 000200CD 900104A9 42 12 000200D3 900104B1 43 14 000200D8 900104A8 44 12 000200DE 900104AA 45 14 000200E3 900104A9 46 14 000200E9 900104AE 47 12 000200EF 900104A8 00 14 000200F4 900104AD 01 12 000200FA 900104A7 02 14 000200FF 900104A9 03 12 00020005 900104A9 04 14 0002000A 900104B1 05 12 00020010 900104AA 06 14 00020015 900104AD 07 12 0002001B 900104A7 08 14 00020020 900104AC 09 12 00020026 900104A7
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/oxfw/oxfw-pcm.c | 6 +- sound/firewire/oxfw/oxfw-proc.c | 29 ++++ sound/firewire/oxfw/oxfw-stream.c | 270 +++++++++++++++++++++++++++++--------- sound/firewire/oxfw/oxfw.c | 25 +++- sound/firewire/oxfw/oxfw.h | 24 +++- 5 files changed, 279 insertions(+), 75 deletions(-)
diff --git a/sound/firewire/oxfw/oxfw-pcm.c b/sound/firewire/oxfw/oxfw-pcm.c index ea2b439..a78339c 100644 --- a/sound/firewire/oxfw/oxfw-pcm.c +++ b/sound/firewire/oxfw/oxfw-pcm.c @@ -177,7 +177,7 @@ static int pcm_hw_free(struct snd_pcm_substream *substream) struct snd_oxfw *oxfw = substream->private_data;
mutex_lock(&oxfw->mutex); - snd_oxfw_stream_stop_simplex(oxfw); + snd_oxfw_stream_stop_simplex(oxfw, &oxfw->rx_stream); mutex_unlock(&oxfw->mutex);
return snd_pcm_lib_free_vmalloc_buffer(substream); @@ -190,8 +190,8 @@ static int pcm_prepare(struct snd_pcm_substream *substream) int err;
mutex_lock(&oxfw->mutex); - err = snd_oxfw_stream_start_simplex(oxfw, runtime->rate, - runtime->channels); + err = snd_oxfw_stream_start_simplex(oxfw, &oxfw->rx_stream, + runtime->rate, runtime->channels); mutex_unlock(&oxfw->mutex); if (err < 0) goto end; diff --git a/sound/firewire/oxfw/oxfw-proc.c b/sound/firewire/oxfw/oxfw-proc.c index 18e0305..604808e 100644 --- a/sound/firewire/oxfw/oxfw-proc.c +++ b/sound/firewire/oxfw/oxfw-proc.c @@ -44,6 +44,35 @@ static void proc_read_formation(struct snd_info_entry *entry, formation.rate, formation.pcm, formation.midi); }
+ if (!oxfw->has_output) + return; + + /* Show output. */ + err = snd_oxfw_stream_get_current_formation(oxfw, + AVC_GENERAL_PLUG_DIR_OUT, + &curr); + if (err < 0) + return; + + snd_iprintf(buffer, "Output Stream from device:\n"); + snd_iprintf(buffer, "\tRate\tPCM\tMIDI\n"); + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { + format = oxfw->tx_stream_formats[i]; + if (format == NULL) + continue; + + err = snd_oxfw_stream_parse_format(format, &formation); + if (err < 0) + continue; + + if (memcmp(&formation, &curr, sizeof(curr)) == 0) + flag = '*'; + else + flag = ' '; + + snd_iprintf(buffer, "%c\t%d\t%d\t%d\n", flag, + formation.rate, formation.pcm, formation.midi); + } }
static void add_node(struct snd_oxfw *oxfw, struct snd_info_entry *root, diff --git a/sound/firewire/oxfw/oxfw-stream.c b/sound/firewire/oxfw/oxfw-stream.c index 1820497..1d15428 100644 --- a/sound/firewire/oxfw/oxfw-stream.c +++ b/sound/firewire/oxfw/oxfw-stream.c @@ -39,6 +39,22 @@ static const unsigned int avc_stream_rate_table[] = { [5] = 0x07, };
+static int set_rate(struct snd_oxfw *oxfw, unsigned int rate) +{ + int err; + + err = avc_general_set_sig_fmt(oxfw->unit, rate, + AVC_GENERAL_PLUG_DIR_IN, 0); + if (err < 0) + goto end; + + if (oxfw->has_output) + err = avc_general_set_sig_fmt(oxfw->unit, rate, + AVC_GENERAL_PLUG_DIR_OUT, 0); +end: + return err; +} + static int set_stream_format(struct snd_oxfw *oxfw, struct amdtp_stream *s, unsigned int rate, unsigned int pcm_channels) { @@ -47,8 +63,13 @@ static int set_stream_format(struct snd_oxfw *oxfw, struct amdtp_stream *s, enum avc_general_plug_dir dir; unsigned int i, err, len;
- formats = oxfw->rx_stream_formats; - dir = AVC_GENERAL_PLUG_DIR_IN; + if (s == &oxfw->tx_stream) { + formats = oxfw->tx_stream_formats; + dir = AVC_GENERAL_PLUG_DIR_OUT; + } else { + formats = oxfw->rx_stream_formats; + dir = AVC_GENERAL_PLUG_DIR_IN; + }
/* Seek stream format for requirements. */ for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { @@ -64,8 +85,7 @@ static int set_stream_format(struct snd_oxfw *oxfw, struct amdtp_stream *s,
/* If assumed, just change rate. */ if (oxfw->assumed) - return avc_general_set_sig_fmt(oxfw->unit, rate, - AVC_GENERAL_PLUG_DIR_IN, 0); + return set_rate(oxfw, rate);
/* Calculate format length. */ len = 5 + formats[i][4] * 2; @@ -80,47 +100,35 @@ static int set_stream_format(struct snd_oxfw *oxfw, struct amdtp_stream *s, return 0; }
-int snd_oxfw_stream_init_simplex(struct snd_oxfw *oxfw) +static void stop_stream(struct snd_oxfw *oxfw, struct amdtp_stream *stream) { - int err; - - err = cmp_connection_init(&oxfw->in_conn, oxfw->unit, - CMP_INPUT, 0); - if (err < 0) - goto end; + amdtp_stream_pcm_abort(stream); + amdtp_stream_stop(stream);
- err = amdtp_stream_init(&oxfw->rx_stream, oxfw->unit, - AMDTP_OUT_STREAM, CIP_NONBLOCKING); - if (err < 0) { - amdtp_stream_destroy(&oxfw->rx_stream); - cmp_connection_destroy(&oxfw->in_conn); - } -end: - return err; -} - -static void stop_stream(struct snd_oxfw *oxfw) -{ - amdtp_stream_pcm_abort(&oxfw->rx_stream); - amdtp_stream_stop(&oxfw->rx_stream); - cmp_connection_break(&oxfw->in_conn); + if (stream == &oxfw->tx_stream) + cmp_connection_break(&oxfw->out_conn); + else + cmp_connection_break(&oxfw->in_conn); }
-static int start_stream(struct snd_oxfw *oxfw, unsigned int rate, - unsigned int pcm_channels) +static int start_stream(struct snd_oxfw *oxfw, struct amdtp_stream *stream, + unsigned int rate, unsigned int pcm_channels) { u8 **formats; struct cmp_connection *conn; struct snd_oxfw_stream_formation formation; unsigned int i, midi_ports; - struct amdtp_stream *stream; int err;
- stream = &oxfw->rx_stream; - formats = oxfw->rx_stream_formats; - conn = &oxfw->in_conn; + if (stream == &oxfw->rx_stream) { + formats = oxfw->rx_stream_formats; + conn = &oxfw->in_conn; + } else { + formats = oxfw->tx_stream_formats; + conn = &oxfw->out_conn; + }
- /* Get stream formation */ + /* Get stream format */ for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { if (formats[i] == NULL) break; @@ -164,67 +172,196 @@ static int start_stream(struct snd_oxfw *oxfw, unsigned int rate, /* Wait first packet */ err = amdtp_stream_wait_callback(stream, CALLBACK_TIMEOUT); if (err < 0) - stop_stream(oxfw); + stop_stream(oxfw, stream); end: return err; }
-int snd_oxfw_stream_start_simplex(struct snd_oxfw *oxfw, unsigned int rate, - unsigned int pcm_channels) +static int check_connection_used_by_others(struct snd_oxfw *oxfw, + struct amdtp_stream *stream) { + struct cmp_connection *conn; + bool used; + int err; + + if (stream == &oxfw->tx_stream) + conn = &oxfw->out_conn; + else + conn = &oxfw->in_conn; + + err = cmp_connection_check_used(conn, &used); + if ((err >= 0) && used && !amdtp_stream_running(stream)) { + dev_err(&oxfw->unit->device, + "Connection established by others: %cPCR[%d]\n", + (conn->direction == CMP_OUTPUT) ? 'o' : 'i', + conn->pcr_index); + err = -EBUSY; + } + + return err; +} + +int snd_oxfw_stream_init_simplex(struct snd_oxfw *oxfw, + struct amdtp_stream *stream) +{ + struct cmp_connection *conn; + enum cmp_direction c_dir; + enum amdtp_stream_direction s_dir; + int err; + + if (stream == &oxfw->tx_stream) { + conn = &oxfw->out_conn; + c_dir = CMP_OUTPUT; + s_dir = AMDTP_IN_STREAM; + } else { + conn = &oxfw->in_conn; + c_dir = CMP_INPUT; + s_dir = AMDTP_OUT_STREAM; + } + + err = cmp_connection_init(conn, oxfw->unit, c_dir, 0); + if (err < 0) + goto end; + + err = amdtp_stream_init(stream, oxfw->unit, s_dir, CIP_NONBLOCKING); + if (err < 0) { + amdtp_stream_destroy(stream); + cmp_connection_destroy(conn); + goto end; + } + + /* OXFW starts to transmit packets with non-zero dbc. */ + if (stream == &oxfw->tx_stream) + oxfw->tx_stream.flags |= CIP_SKIP_INIT_DBC_CHECK; +end: + return err; +} + +int snd_oxfw_stream_start_simplex(struct snd_oxfw *oxfw, + struct amdtp_stream *stream, + unsigned int rate, unsigned int pcm_channels) +{ + struct amdtp_stream *opposite; struct snd_oxfw_stream_formation formation; + enum avc_general_plug_dir dir; + unsigned int substreams, opposite_substreams; int err = 0;
+ if (stream == &oxfw->tx_stream) { + substreams = oxfw->capture_substreams; + opposite = &oxfw->rx_stream; + opposite_substreams = oxfw->playback_substreams; + dir = AVC_GENERAL_PLUG_DIR_OUT; + } else { + substreams = oxfw->playback_substreams; + opposite_substreams = oxfw->capture_substreams; + + if (oxfw->has_output) + opposite = &oxfw->rx_stream; + else + opposite = NULL; + + dir = AVC_GENERAL_PLUG_DIR_IN; + } + + if (substreams == 0) + goto end; + + /* + * Considering JACK/FFADO streaming: + * TODO: This can be removed hwdep functionality becomes popular. + */ + err = check_connection_used_by_others(oxfw, stream); + if (err < 0) + goto end; + /* packet queueing error */ - if (amdtp_streaming_error(&oxfw->rx_stream)) - stop_stream(oxfw); + if (amdtp_streaming_error(stream)) + stop_stream(oxfw, stream);
- err = snd_oxfw_stream_get_current_formation(oxfw, - AVC_GENERAL_PLUG_DIR_IN, - &formation); + err = snd_oxfw_stream_get_current_formation(oxfw, dir, &formation); if (err < 0) goto end;
if ((formation.rate != rate) || (formation.pcm != pcm_channels)) { - stop_stream(oxfw); + if (opposite != NULL) { + err = check_connection_used_by_others(oxfw, opposite); + if (err < 0) + goto end; + stop_stream(oxfw, opposite); + } + stop_stream(oxfw, stream);
- /* arrange sampling rate */ - err = set_stream_format(oxfw, &oxfw->rx_stream, rate, - pcm_channels); + err = set_stream_format(oxfw, stream, rate, pcm_channels); if (err < 0) { dev_err(&oxfw->unit->device, "fail to set stream format: %d\n", err); goto end; } + + /* Start opposite stream if needed. */ + if (opposite && !amdtp_stream_running(opposite) && + (opposite_substreams > 0)) { + err = start_stream(oxfw, opposite, rate, 0); + if (err < 0) { + dev_err(&oxfw->unit->device, + "fail to restart stream: %d\n", err); + goto end; + } + } }
- err = start_stream(oxfw, rate, pcm_channels); - if (err < 0) - dev_err(&oxfw->unit->device, - "fail to start stream: %d\n", err); + /* Start requested stream. */ + if (!amdtp_stream_running(stream)) { + err = start_stream(oxfw, stream, rate, pcm_channels); + if (err < 0) + dev_err(&oxfw->unit->device, + "fail to start stream: %d\n", err); + } end: return err; }
-void snd_oxfw_stream_stop_simplex(struct snd_oxfw *oxfw) +void snd_oxfw_stream_stop_simplex(struct snd_oxfw *oxfw, + struct amdtp_stream *stream) { - stop_stream(oxfw); + if (((stream == &oxfw->tx_stream) && (oxfw->capture_substreams > 0)) || + ((stream == &oxfw->rx_stream) && (oxfw->playback_substreams > 0))) + return; + + stop_stream(oxfw, stream); }
-void snd_oxfw_stream_destroy_simplex(struct snd_oxfw *oxfw) +void snd_oxfw_stream_destroy_simplex(struct snd_oxfw *oxfw, + struct amdtp_stream *stream) { - stop_stream(oxfw); + struct cmp_connection *conn; + + if (stream == &oxfw->tx_stream) + conn = &oxfw->out_conn; + else + conn = &oxfw->in_conn;
- amdtp_stream_destroy(&oxfw->rx_stream); - cmp_connection_destroy(&oxfw->in_conn); + stop_stream(oxfw, stream); + + amdtp_stream_destroy(stream); + cmp_connection_destroy(conn); }
-void snd_oxfw_stream_update_simplex(struct snd_oxfw *oxfw) +void snd_oxfw_stream_update_simplex(struct snd_oxfw *oxfw, + struct amdtp_stream *stream) { - if (cmp_connection_update(&oxfw->in_conn) < 0) - stop_stream(oxfw); + struct cmp_connection *conn; + + if (stream == &oxfw->tx_stream) + conn = &oxfw->out_conn; + else + conn = &oxfw->in_conn; + + if (cmp_connection_update(conn) < 0) + stop_stream(oxfw, stream); else - amdtp_stream_update(&oxfw->rx_stream); + amdtp_stream_update(stream); }
int snd_oxfw_stream_get_current_formation(struct snd_oxfw *oxfw, @@ -408,7 +545,10 @@ static int fill_stream_formats(struct snd_oxfw *oxfw, if (buf == NULL) return -ENOMEM;
- formats = oxfw->rx_stream_formats; + if (dir == AVC_GENERAL_PLUG_DIR_OUT) + formats = oxfw->tx_stream_formats; + else + formats = oxfw->rx_stream_formats;
/* get first entry */ len = AVC_GENERIC_FRAME_MAXIMUM_BYTES; @@ -481,11 +621,19 @@ int snd_oxfw_stream_discover(struct snd_oxfw *oxfw) "fail to get info for isoc/external in/out plugs: %d\n", err); goto end; - } else if (plugs[0] == 0) { + } else if ((plugs[0] == 0) && (plugs[1] == 0)) { err = -ENOSYS; goto end; }
+ /* use oPCR[0] if exists */ + if (plugs[1] > 0) { + err = fill_stream_formats(oxfw, AVC_GENERAL_PLUG_DIR_OUT, 0); + if (err < 0) + goto end; + oxfw->has_output = true; + } + /* use iPCR[0] if exists */ if (plugs[0] > 0) err = fill_stream_formats(oxfw, AVC_GENERAL_PLUG_DIR_IN, 0); diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c index 26ce615..bc0239c 100644 --- a/sound/firewire/oxfw/oxfw.c +++ b/sound/firewire/oxfw/oxfw.c @@ -111,8 +111,10 @@ static void oxfw_card_free(struct snd_card *card) struct snd_oxfw *oxfw = card->private_data; unsigned int i;
- for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { + kfree(oxfw->tx_stream_formats[i]); kfree(oxfw->rx_stream_formats[i]); + }
mutex_destroy(&oxfw->mutex); } @@ -159,13 +161,20 @@ static int oxfw_probe(struct fw_unit *unit,
snd_oxfw_proc_init(oxfw);
- err = snd_oxfw_stream_init_simplex(oxfw); + err = snd_oxfw_stream_init_simplex(oxfw, &oxfw->rx_stream); if (err < 0) goto error; + if (oxfw->has_output) { + err = snd_oxfw_stream_init_simplex(oxfw, &oxfw->tx_stream); + if (err < 0) + goto error; + }
err = snd_card_register(card); if (err < 0) { - snd_oxfw_stream_destroy_simplex(oxfw); + snd_oxfw_stream_destroy_simplex(oxfw, &oxfw->rx_stream); + if (oxfw->has_output) + snd_oxfw_stream_destroy_simplex(oxfw, &oxfw->tx_stream); goto error; } dev_set_drvdata(&unit->device, oxfw); @@ -183,7 +192,11 @@ static void oxfw_bus_reset(struct fw_unit *unit) fcp_bus_reset(oxfw->unit);
mutex_lock(&oxfw->mutex); - snd_oxfw_stream_update_simplex(oxfw); + + snd_oxfw_stream_update_simplex(oxfw, &oxfw->rx_stream); + if (oxfw->has_output) + snd_oxfw_stream_update_simplex(oxfw, &oxfw->tx_stream); + mutex_unlock(&oxfw->mutex); }
@@ -193,7 +206,9 @@ static void oxfw_remove(struct fw_unit *unit)
snd_card_disconnect(oxfw->card);
- snd_oxfw_stream_destroy_simplex(oxfw); + snd_oxfw_stream_destroy_simplex(oxfw, &oxfw->rx_stream); + if (oxfw->has_output) + snd_oxfw_stream_destroy_simplex(oxfw, &oxfw->tx_stream);
snd_card_free_when_closed(oxfw->card); } diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h index c09ef38..2211d11 100644 --- a/sound/firewire/oxfw/oxfw.h +++ b/sound/firewire/oxfw/oxfw.h @@ -44,10 +44,16 @@ struct snd_oxfw { const struct device_info *device_info; struct mutex mutex;
+ bool has_output; + u8 *tx_stream_formats[SND_OXFW_STREAM_FORMAT_ENTRIES]; u8 *rx_stream_formats[SND_OXFW_STREAM_FORMAT_ENTRIES]; bool assumed; + struct cmp_connection out_conn; struct cmp_connection in_conn; + struct amdtp_stream tx_stream; struct amdtp_stream rx_stream; + unsigned int capture_substreams; + unsigned int playback_substreams;
bool mute; s16 volume[6]; @@ -88,12 +94,17 @@ int avc_general_inquiry_sig_fmt(struct fw_unit *unit, unsigned int rate, enum avc_general_plug_dir dir, unsigned short pid);
-int snd_oxfw_stream_init_simplex(struct snd_oxfw *oxfw); -int snd_oxfw_stream_start_simplex(struct snd_oxfw *oxfw, unsigned int rate, - unsigned int pcm_channels); -void snd_oxfw_stream_stop_simplex(struct snd_oxfw *oxfw); -void snd_oxfw_stream_destroy_simplex(struct snd_oxfw *oxfw); -void snd_oxfw_stream_update_simplex(struct snd_oxfw *oxfw); +int snd_oxfw_stream_init_simplex(struct snd_oxfw *oxfw, + struct amdtp_stream *stream); +int snd_oxfw_stream_start_simplex(struct snd_oxfw *oxfw, + struct amdtp_stream *stream, + unsigned int rate, unsigned int pcm_channels); +void snd_oxfw_stream_stop_simplex(struct snd_oxfw *oxfw, + struct amdtp_stream *stream); +void snd_oxfw_stream_destroy_simplex(struct snd_oxfw *oxfw, + struct amdtp_stream *stream); +void snd_oxfw_stream_update_simplex(struct snd_oxfw *oxfw, + struct amdtp_stream *stream);
struct snd_oxfw_stream_formation { unsigned int rate; @@ -105,6 +116,7 @@ int snd_oxfw_stream_parse_format(u8 *format, int snd_oxfw_stream_get_current_formation(struct snd_oxfw *oxfw, enum avc_general_plug_dir dir, struct snd_oxfw_stream_formation *formation); + int snd_oxfw_stream_discover(struct snd_oxfw *oxfw);
int snd_oxfw_create_pcm(struct snd_oxfw *oxfw);
In previous commit, a support for transmitted packets is added. This commit add a support for capturing PCM samples.
When any streams are already started, this driver should not change sampling rate of the device, thus this commit also adds a restriction of sampling rate in this situation.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/oxfw/oxfw-pcm.c | 200 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 180 insertions(+), 20 deletions(-)
diff --git a/sound/firewire/oxfw/oxfw-pcm.c b/sound/firewire/oxfw/oxfw-pcm.c index a78339c..e84fc9c 100644 --- a/sound/firewire/oxfw/oxfw-pcm.c +++ b/sound/firewire/oxfw/oxfw-pcm.c @@ -118,21 +118,31 @@ static void limit_period_and_buffer(struct snd_pcm_hardware *hw) hw->buffer_bytes_max = hw->period_bytes_max * hw->periods_min; }
-static int pcm_open(struct snd_pcm_substream *substream) +static int init_hw_params(struct snd_oxfw *oxfw, + struct snd_pcm_substream *substream) { - struct snd_oxfw *oxfw = substream->private_data; struct snd_pcm_runtime *runtime = substream->runtime; u8 **formats; + struct amdtp_stream *stream; int err;
- formats = oxfw->rx_stream_formats; - runtime->hw.info = SNDRV_PCM_INFO_BATCH | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_JOINT_DUPLEX | SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID;
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + runtime->hw.formats = AMDTP_IN_PCM_FORMAT_BITS; + stream = &oxfw->tx_stream; + formats = oxfw->tx_stream_formats; + } else { + runtime->hw.formats = AMDTP_OUT_PCM_FORMAT_BITS; + stream = &oxfw->rx_stream; + formats = oxfw->rx_stream_formats; + } + limit_channels_and_rates(&runtime->hw, formats); limit_period_and_buffer(&runtime->hw);
@@ -148,10 +158,55 @@ static int pcm_open(struct snd_pcm_substream *substream) if (err < 0) goto end;
- err = amdtp_stream_add_pcm_hw_constraints(&oxfw->rx_stream, runtime); + err = amdtp_stream_add_pcm_hw_constraints(stream, runtime); +end: + return err; +} + +static int limit_to_current_params(struct snd_pcm_substream *substream) +{ + struct snd_oxfw *oxfw = substream->private_data; + struct snd_oxfw_stream_formation formation; + enum avc_general_plug_dir dir; + int err; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + dir = AVC_GENERAL_PLUG_DIR_OUT; + else + dir = AVC_GENERAL_PLUG_DIR_IN; + + err = snd_oxfw_stream_get_current_formation(oxfw, dir, &formation); + if (err < 0) + goto end; + + substream->runtime->hw.channels_min = formation.pcm; + substream->runtime->hw.channels_max = formation.pcm; + substream->runtime->hw.rate_min = formation.rate; + substream->runtime->hw.rate_max = formation.rate; +end: + return err; +} + +static int pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_oxfw *oxfw = substream->private_data; + int err; + + err = init_hw_params(oxfw, substream); if (err < 0) goto end;
+ /* + * When any PCM streams are already running, the available sampling + * rate is limited at current value. + */ + if (amdtp_stream_pcm_running(&oxfw->tx_stream) || + amdtp_stream_pcm_running(&oxfw->rx_stream)) { + err = limit_to_current_params(substream); + if (err < 0) + goto end; + } + snd_pcm_set_sync(substream); end: return err; @@ -162,28 +217,89 @@ static int pcm_close(struct snd_pcm_substream *substream) return 0; }
-static int pcm_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *hw_params) +static int pcm_capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) { struct snd_oxfw *oxfw = substream->private_data;
+ + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&oxfw->mutex); + oxfw->capture_substreams++; + mutex_unlock(&oxfw->mutex); + } + + amdtp_stream_set_pcm_format(&oxfw->tx_stream, params_format(hw_params)); + + return snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); +} +static int pcm_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_oxfw *oxfw = substream->private_data; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&oxfw->mutex); + oxfw->playback_substreams++; + mutex_unlock(&oxfw->mutex); + } + amdtp_stream_set_pcm_format(&oxfw->rx_stream, params_format(hw_params)); + return snd_pcm_lib_alloc_vmalloc_buffer(substream, params_buffer_bytes(hw_params)); }
-static int pcm_hw_free(struct snd_pcm_substream *substream) +static int pcm_capture_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_oxfw *oxfw = substream->private_data; + + mutex_lock(&oxfw->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + oxfw->capture_substreams--; + + snd_oxfw_stream_stop_simplex(oxfw, &oxfw->tx_stream); + + mutex_unlock(&oxfw->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} +static int pcm_playback_hw_free(struct snd_pcm_substream *substream) { struct snd_oxfw *oxfw = substream->private_data;
mutex_lock(&oxfw->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + oxfw->playback_substreams--; + snd_oxfw_stream_stop_simplex(oxfw, &oxfw->rx_stream); + mutex_unlock(&oxfw->mutex);
return snd_pcm_lib_free_vmalloc_buffer(substream); }
-static int pcm_prepare(struct snd_pcm_substream *substream) +static int pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_oxfw *oxfw = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + mutex_lock(&oxfw->mutex); + err = snd_oxfw_stream_start_simplex(oxfw, &oxfw->tx_stream, + runtime->rate, runtime->channels); + mutex_unlock(&oxfw->mutex); + if (err < 0) + goto end; + + amdtp_stream_pcm_prepare(&oxfw->tx_stream); +end: + return err; +} +static int pcm_playback_prepare(struct snd_pcm_substream *substream) { struct snd_oxfw *oxfw = substream->private_data; struct snd_pcm_runtime *runtime = substream->runtime; @@ -201,7 +317,25 @@ end: return err; }
-static int pcm_trigger(struct snd_pcm_substream *substream, int cmd) +static int pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_oxfw *oxfw = substream->private_data; + struct snd_pcm_substream *pcm; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + pcm = substream; + break; + case SNDRV_PCM_TRIGGER_STOP: + pcm = NULL; + break; + default: + return -EINVAL; + } + amdtp_stream_pcm_trigger(&oxfw->tx_stream, pcm); + return 0; +} +static int pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd) { struct snd_oxfw *oxfw = substream->private_data; struct snd_pcm_substream *pcm; @@ -220,35 +354,61 @@ static int pcm_trigger(struct snd_pcm_substream *substream, int cmd) return 0; }
-static snd_pcm_uframes_t pcm_pointer(struct snd_pcm_substream *substream) +static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstm) { - struct snd_oxfw *oxfw = substream->private_data; + struct snd_oxfw *oxfw = sbstm->private_data; + + return amdtp_stream_pcm_pointer(&oxfw->tx_stream); +} +static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstm) +{ + struct snd_oxfw *oxfw = sbstm->private_data;
return amdtp_stream_pcm_pointer(&oxfw->rx_stream); }
int snd_oxfw_create_pcm(struct snd_oxfw *oxfw) { - static struct snd_pcm_ops ops = { + static struct snd_pcm_ops capture_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_capture_hw_params, + .hw_free = pcm_capture_hw_free, + .prepare = pcm_capture_prepare, + .trigger = pcm_capture_trigger, + .pointer = pcm_capture_pointer, + .page = snd_pcm_lib_get_vmalloc_page, + .mmap = snd_pcm_lib_mmap_vmalloc, + }; + static struct snd_pcm_ops playback_ops = { .open = pcm_open, .close = pcm_close, .ioctl = snd_pcm_lib_ioctl, - .hw_params = pcm_hw_params, - .hw_free = pcm_hw_free, - .prepare = pcm_prepare, - .trigger = pcm_trigger, - .pointer = pcm_pointer, + .hw_params = pcm_playback_hw_params, + .hw_free = pcm_playback_hw_free, + .prepare = pcm_playback_prepare, + .trigger = pcm_playback_trigger, + .pointer = pcm_playback_pointer, .page = snd_pcm_lib_get_vmalloc_page, .mmap = snd_pcm_lib_mmap_vmalloc, }; struct snd_pcm *pcm; + unsigned int cap = 0; int err;
- err = snd_pcm_new(oxfw->card, oxfw->card->driver, 0, 1, 0, &pcm); + if (oxfw->has_output) + cap = 1; + + err = snd_pcm_new(oxfw->card, oxfw->card->driver, 0, 1, cap, &pcm); if (err < 0) return err; + pcm->private_data = oxfw; strcpy(pcm->name, oxfw->card->shortname); - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops); + if (cap > 0) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_ops); + return 0; }
This commit adds MIDI functionality with an assumption of 'if the device has MIDI comformant data channels in its stream formation, the device has one MIDI port'.
When no streams have already started, MIDI functionality starts stream with current sampling rate.
When MIDI functionality has already starts some streams and PCM functionality is going to start streams at different sampling rate, this driver stops streams once and changes sampling rate, then restarts streams for both PCM/MIDI substreams.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/oxfw/Makefile | 2 +- sound/firewire/oxfw/oxfw-midi.c | 191 ++++++++++++++++++++++++++++++++++++++ sound/firewire/oxfw/oxfw-stream.c | 4 + sound/firewire/oxfw/oxfw.c | 5 + sound/firewire/oxfw/oxfw.h | 7 ++ 5 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/oxfw/oxfw-midi.c
diff --git a/sound/firewire/oxfw/Makefile b/sound/firewire/oxfw/Makefile index e9297c6..3904a30 100644 --- a/sound/firewire/oxfw/Makefile +++ b/sound/firewire/oxfw/Makefile @@ -1,3 +1,3 @@ snd-oxfw-objs := oxfw-command.o oxfw-stream.o oxfw-control.o oxfw-pcm.o \ - oxfw-proc.o oxfw.o + oxfw-proc.o oxfw-midi.o oxfw.o obj-m += snd-oxfw.o diff --git a/sound/firewire/oxfw/oxfw-midi.c b/sound/firewire/oxfw/oxfw-midi.c new file mode 100644 index 0000000..334b11d --- /dev/null +++ b/sound/firewire/oxfw/oxfw-midi.c @@ -0,0 +1,191 @@ +/* + * oxfw_midi.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) 2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "oxfw.h" + +static int midi_capture_open(struct snd_rawmidi_substream *substream) +{ + struct snd_oxfw *oxfw = substream->rmidi->private_data; + int err; + + mutex_lock(&oxfw->mutex); + + oxfw->capture_substreams++; + err = snd_oxfw_stream_start_simplex(oxfw, &oxfw->tx_stream, 0, 0); + + mutex_unlock(&oxfw->mutex); + + return err; +} + +static int midi_playback_open(struct snd_rawmidi_substream *substream) +{ + struct snd_oxfw *oxfw = substream->rmidi->private_data; + int err; + + mutex_lock(&oxfw->mutex); + + oxfw->playback_substreams++; + err = snd_oxfw_stream_start_simplex(oxfw, &oxfw->rx_stream, 0, 0); + + mutex_unlock(&oxfw->mutex); + + return err; +} + +static int midi_capture_close(struct snd_rawmidi_substream *substream) +{ + struct snd_oxfw *oxfw = substream->rmidi->private_data; + + mutex_lock(&oxfw->mutex); + + oxfw->capture_substreams--; + snd_oxfw_stream_stop_simplex(oxfw, &oxfw->tx_stream); + + mutex_unlock(&oxfw->mutex); + + return 0; +} + +static int midi_playback_close(struct snd_rawmidi_substream *substream) +{ + struct snd_oxfw *oxfw = substream->rmidi->private_data; + + mutex_lock(&oxfw->mutex); + + oxfw->playback_substreams--; + snd_oxfw_stream_stop_simplex(oxfw, &oxfw->rx_stream); + + mutex_unlock(&oxfw->mutex); + + return 0; +} + +static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_oxfw *oxfw = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&oxfw->lock, flags); + + if (up) + amdtp_stream_midi_trigger(&oxfw->tx_stream, + substrm->number, substrm); + else + amdtp_stream_midi_trigger(&oxfw->tx_stream, + substrm->number, NULL); + + spin_unlock_irqrestore(&oxfw->lock, flags); +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_oxfw *oxfw = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&oxfw->lock, flags); + + if (up) + amdtp_stream_midi_trigger(&oxfw->rx_stream, + substrm->number, substrm); + else + amdtp_stream_midi_trigger(&oxfw->rx_stream, + substrm->number, NULL); + + spin_unlock_irqrestore(&oxfw->lock, flags); +} + +static struct snd_rawmidi_ops midi_capture_ops = { + .open = midi_capture_open, + .close = midi_capture_close, + .trigger = midi_capture_trigger, +}; + +static struct snd_rawmidi_ops midi_playback_ops = { + .open = midi_playback_open, + .close = midi_playback_close, + .trigger = midi_playback_trigger, +}; + +static void set_midi_substream_names(struct snd_oxfw *oxfw, + struct snd_rawmidi_str *str) +{ + struct snd_rawmidi_substream *subs; + + list_for_each_entry(subs, &str->substreams, list) { + snprintf(subs->name, sizeof(subs->name), + "%s MIDI %d", + oxfw->card->shortname, subs->number + 1); + } +} + +int snd_oxfw_create_midi(struct snd_oxfw *oxfw) +{ + struct snd_oxfw_stream_formation formation; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_str *str; + u8 *format; + int i, err; + + /* If its stream has MIDI conformant data channel, add one MIDI port */ + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { + format = oxfw->tx_stream_formats[i]; + if (format != NULL) { + err = snd_oxfw_stream_parse_format(format, &formation); + if (err >= 0 && formation.midi > 0) + oxfw->midi_input_ports = 1; + } + + format = oxfw->rx_stream_formats[i]; + if (format != NULL) { + err = snd_oxfw_stream_parse_format(format, &formation); + if (err >= 0 && formation.midi > 0) + oxfw->midi_output_ports = 1; + } + } + if ((oxfw->midi_input_ports == 0) && (oxfw->midi_output_ports == 0)) + return 0; + + /* create midi ports */ + err = snd_rawmidi_new(oxfw->card, oxfw->card->driver, 0, + oxfw->midi_output_ports, oxfw->midi_input_ports, + &rmidi); + if (err < 0) + return err; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", oxfw->card->shortname); + rmidi->private_data = oxfw; + + if (oxfw->midi_input_ports > 0) { + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &midi_capture_ops); + + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]; + + set_midi_substream_names(oxfw, str); + } + + if (oxfw->midi_output_ports > 0) { + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &midi_playback_ops); + + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]; + + set_midi_substream_names(oxfw, str); + } + + if ((oxfw->midi_output_ports > 0) && (oxfw->midi_input_ports > 0)) + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; + + return 0; +} diff --git a/sound/firewire/oxfw/oxfw-stream.c b/sound/firewire/oxfw/oxfw-stream.c index 1d15428..a38b3c3 100644 --- a/sound/firewire/oxfw/oxfw-stream.c +++ b/sound/firewire/oxfw/oxfw-stream.c @@ -282,6 +282,10 @@ int snd_oxfw_stream_start_simplex(struct snd_oxfw *oxfw, err = snd_oxfw_stream_get_current_formation(oxfw, dir, &formation); if (err < 0) goto end; + if (rate == 0) + rate = formation.rate; + if (pcm_channels == 0) + pcm_channels = formation.pcm;
if ((formation.rate != rate) || (formation.pcm != pcm_channels)) { if (opposite != NULL) { diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c index bc0239c..f6c0986 100644 --- a/sound/firewire/oxfw/oxfw.c +++ b/sound/firewire/oxfw/oxfw.c @@ -140,6 +140,7 @@ static int oxfw_probe(struct fw_unit *unit, mutex_init(&oxfw->mutex); oxfw->unit = unit; oxfw->device_info = (const struct device_info *)id->driver_data; + spin_lock_init(&oxfw->lock);
err = snd_oxfw_stream_discover(oxfw); if (err < 0) @@ -161,6 +162,10 @@ static int oxfw_probe(struct fw_unit *unit,
snd_oxfw_proc_init(oxfw);
+ err = snd_oxfw_create_midi(oxfw); + if (err < 0) + goto error; + err = snd_oxfw_stream_init_simplex(oxfw, &oxfw->rx_stream); if (err < 0) goto error; diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h index 2211d11..83a54fc 100644 --- a/sound/firewire/oxfw/oxfw.h +++ b/sound/firewire/oxfw/oxfw.h @@ -19,6 +19,7 @@ #include <sound/pcm.h> #include <sound/pcm_params.h> #include <sound/info.h> +#include <sound/rawmidi.h>
#include "../lib.h" #include "../fcp.h" @@ -43,6 +44,7 @@ struct snd_oxfw { struct fw_unit *unit; const struct device_info *device_info; struct mutex mutex; + spinlock_t lock;
bool has_output; u8 *tx_stream_formats[SND_OXFW_STREAM_FORMAT_ENTRIES]; @@ -55,6 +57,9 @@ struct snd_oxfw { unsigned int capture_substreams; unsigned int playback_substreams;
+ unsigned int midi_input_ports; + unsigned int midi_output_ports; + bool mute; s16 volume[6]; s16 volume_min; @@ -124,3 +129,5 @@ int snd_oxfw_create_pcm(struct snd_oxfw *oxfw); int snd_oxfw_create_mixer(struct snd_oxfw *oxfw);
void snd_oxfw_proc_init(struct snd_oxfw *oxfw); + +int snd_oxfw_create_midi(struct snd_oxfw *oxfw);
This interface is designed for mixer/control application. By using this interface, an application can get information about firewire node, can lock/unlock kernel streaming and can get notification at starting/stopping kernel streaming.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- include/uapi/sound/asound.h | 3 +- include/uapi/sound/firewire.h | 3 +- sound/firewire/Kconfig | 1 + sound/firewire/oxfw/Makefile | 2 +- sound/firewire/oxfw/oxfw-hwdep.c | 190 ++++++++++++++++++++++++++++++++++++++ sound/firewire/oxfw/oxfw-midi.c | 16 ++++ sound/firewire/oxfw/oxfw-pcm.c | 12 ++- sound/firewire/oxfw/oxfw-stream.c | 39 ++++++++ sound/firewire/oxfw/oxfw.c | 5 + sound/firewire/oxfw/oxfw.h | 13 +++ 10 files changed, 280 insertions(+), 4 deletions(-) create mode 100644 sound/firewire/oxfw/oxfw-hwdep.c
diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index 941d32f..1f23cd6 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -96,9 +96,10 @@ enum { SNDRV_HWDEP_IFACE_FW_DICE, /* TC DICE FireWire device */ SNDRV_HWDEP_IFACE_FW_FIREWORKS, /* Echo Audio Fireworks based device */ SNDRV_HWDEP_IFACE_FW_BEBOB, /* BridgeCo BeBoB based device */ + SNDRV_HWDEP_IFACE_FW_OXFW, /* Oxford OXFW970/971 based device */
/* Don't forget to change the following: */ - SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_BEBOB + SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_OXFW };
struct snd_hwdep_info { diff --git a/include/uapi/sound/firewire.h b/include/uapi/sound/firewire.h index af4bd13..49122df 100644 --- a/include/uapi/sound/firewire.h +++ b/include/uapi/sound/firewire.h @@ -55,7 +55,8 @@ union snd_firewire_event { #define SNDRV_FIREWIRE_TYPE_DICE 1 #define SNDRV_FIREWIRE_TYPE_FIREWORKS 2 #define SNDRV_FIREWIRE_TYPE_BEBOB 3 -/* AV/C, RME, MOTU, ... */ +#define SNDRV_FIREWIRE_TYPE_OXFW 4 +/* RME, MOTU, ... */
struct snd_firewire_get_info { unsigned int type; /* SNDRV_FIREWIRE_TYPE_xxx */ diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index 6364e5b..ecec547 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -26,6 +26,7 @@ config SND_DICE config SND_OXFW tristate "Oxford Semiconductor FW970/971 chipset support" select SND_FIREWIRE_LIB + select SND_HWDEP help Say Y here to include support for FireWire devices based on Oxford Semiconductor FW970/971 chipset. diff --git a/sound/firewire/oxfw/Makefile b/sound/firewire/oxfw/Makefile index 3904a30..a926850 100644 --- a/sound/firewire/oxfw/Makefile +++ b/sound/firewire/oxfw/Makefile @@ -1,3 +1,3 @@ snd-oxfw-objs := oxfw-command.o oxfw-stream.o oxfw-control.o oxfw-pcm.o \ - oxfw-proc.o oxfw-midi.o oxfw.o + oxfw-proc.o oxfw-midi.o oxfw-hwdep.o oxfw.o obj-m += snd-oxfw.o diff --git a/sound/firewire/oxfw/oxfw-hwdep.c b/sound/firewire/oxfw/oxfw-hwdep.c new file mode 100644 index 0000000..ff2687a --- /dev/null +++ b/sound/firewire/oxfw/oxfw-hwdep.c @@ -0,0 +1,190 @@ +/* + * oxfw_hwdep.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) 2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +/* + * This codes give three functionality. + * + * 1.get firewire node information + * 2.get notification about starting/stopping stream + * 3.lock/unlock stream + */ + +#include "oxfw.h" + +static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, + loff_t *offset) +{ + struct snd_oxfw *oxfw = hwdep->private_data; + DEFINE_WAIT(wait); + union snd_firewire_event event; + + spin_lock_irq(&oxfw->lock); + + while (!oxfw->dev_lock_changed) { + prepare_to_wait(&oxfw->hwdep_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&oxfw->lock); + schedule(); + finish_wait(&oxfw->hwdep_wait, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&oxfw->lock); + } + + memset(&event, 0, sizeof(event)); + if (oxfw->dev_lock_changed) { + event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; + event.lock_status.status = (oxfw->dev_lock_count > 0); + oxfw->dev_lock_changed = false; + + count = min_t(long, count, sizeof(event.lock_status)); + } + + spin_unlock_irq(&oxfw->lock); + + if (copy_to_user(buf, &event, count)) + return -EFAULT; + + return count; +} + +static unsigned int hwdep_poll(struct snd_hwdep *hwdep, struct file *file, + poll_table *wait) +{ + struct snd_oxfw *oxfw = hwdep->private_data; + unsigned int events; + + poll_wait(file, &oxfw->hwdep_wait, wait); + + spin_lock_irq(&oxfw->lock); + if (oxfw->dev_lock_changed) + events = POLLIN | POLLRDNORM; + else + events = 0; + spin_unlock_irq(&oxfw->lock); + + return events; +} + +static int hwdep_get_info(struct snd_oxfw *oxfw, void __user *arg) +{ + struct fw_device *dev = fw_parent_device(oxfw->unit); + struct snd_firewire_get_info info; + + memset(&info, 0, sizeof(info)); + info.type = SNDRV_FIREWIRE_TYPE_OXFW; + info.card = dev->card->index; + *(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]); + *(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]); + strlcpy(info.device_name, dev_name(&dev->device), + sizeof(info.device_name)); + + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + + return 0; +} + +static int hwdep_lock(struct snd_oxfw *oxfw) +{ + int err; + + spin_lock_irq(&oxfw->lock); + + if (oxfw->dev_lock_count == 0) { + oxfw->dev_lock_count = -1; + err = 0; + } else { + err = -EBUSY; + } + + spin_unlock_irq(&oxfw->lock); + + return err; +} + +static int hwdep_unlock(struct snd_oxfw *oxfw) +{ + int err; + + spin_lock_irq(&oxfw->lock); + + if (oxfw->dev_lock_count == -1) { + oxfw->dev_lock_count = 0; + err = 0; + } else { + err = -EBADFD; + } + + spin_unlock_irq(&oxfw->lock); + + return err; +} + +static int hwdep_release(struct snd_hwdep *hwdep, struct file *file) +{ + struct snd_oxfw *oxfw = hwdep->private_data; + + spin_lock_irq(&oxfw->lock); + if (oxfw->dev_lock_count == -1) + oxfw->dev_lock_count = 0; + spin_unlock_irq(&oxfw->lock); + + return 0; +} + +static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct snd_oxfw *oxfw = hwdep->private_data; + + switch (cmd) { + case SNDRV_FIREWIRE_IOCTL_GET_INFO: + return hwdep_get_info(oxfw, (void __user *)arg); + case SNDRV_FIREWIRE_IOCTL_LOCK: + return hwdep_lock(oxfw); + case SNDRV_FIREWIRE_IOCTL_UNLOCK: + return hwdep_unlock(oxfw); + default: + return -ENOIOCTLCMD; + } +} + +#ifdef CONFIG_COMPAT +static int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return hwdep_ioctl(hwdep, file, cmd, + (unsigned long)compat_ptr(arg)); +} +#else +#define hwdep_compat_ioctl NULL +#endif + +int snd_oxfw_create_hwdep(struct snd_oxfw *oxfw) +{ + static const struct snd_hwdep_ops hwdep_ops = { + .read = hwdep_read, + .release = hwdep_release, + .poll = hwdep_poll, + .ioctl = hwdep_ioctl, + .ioctl_compat = hwdep_compat_ioctl, + }; + struct snd_hwdep *hwdep; + int err; + + err = snd_hwdep_new(oxfw->card, oxfw->card->driver, 0, &hwdep); + if (err < 0) + goto end; + strcpy(hwdep->name, oxfw->card->driver); + hwdep->iface = SNDRV_HWDEP_IFACE_FW_OXFW; + hwdep->ops = hwdep_ops; + hwdep->private_data = oxfw; + hwdep->exclusive = true; +end: + return err; +} diff --git a/sound/firewire/oxfw/oxfw-midi.c b/sound/firewire/oxfw/oxfw-midi.c index 334b11d..540a303 100644 --- a/sound/firewire/oxfw/oxfw-midi.c +++ b/sound/firewire/oxfw/oxfw-midi.c @@ -13,6 +13,10 @@ static int midi_capture_open(struct snd_rawmidi_substream *substream) struct snd_oxfw *oxfw = substream->rmidi->private_data; int err;
+ err = snd_oxfw_stream_lock_try(oxfw); + if (err < 0) + return err; + mutex_lock(&oxfw->mutex);
oxfw->capture_substreams++; @@ -20,6 +24,9 @@ static int midi_capture_open(struct snd_rawmidi_substream *substream)
mutex_unlock(&oxfw->mutex);
+ if (err < 0) + snd_oxfw_stream_lock_release(oxfw); + return err; }
@@ -28,6 +35,10 @@ static int midi_playback_open(struct snd_rawmidi_substream *substream) struct snd_oxfw *oxfw = substream->rmidi->private_data; int err;
+ err = snd_oxfw_stream_lock_try(oxfw); + if (err < 0) + return err; + mutex_lock(&oxfw->mutex);
oxfw->playback_substreams++; @@ -35,6 +46,9 @@ static int midi_playback_open(struct snd_rawmidi_substream *substream)
mutex_unlock(&oxfw->mutex);
+ if (err < 0) + snd_oxfw_stream_lock_release(oxfw); + return err; }
@@ -49,6 +63,7 @@ static int midi_capture_close(struct snd_rawmidi_substream *substream)
mutex_unlock(&oxfw->mutex);
+ snd_oxfw_stream_lock_release(oxfw); return 0; }
@@ -63,6 +78,7 @@ static int midi_playback_close(struct snd_rawmidi_substream *substream)
mutex_unlock(&oxfw->mutex);
+ snd_oxfw_stream_lock_release(oxfw); return 0; }
diff --git a/sound/firewire/oxfw/oxfw-pcm.c b/sound/firewire/oxfw/oxfw-pcm.c index e84fc9c..9bc556b 100644 --- a/sound/firewire/oxfw/oxfw-pcm.c +++ b/sound/firewire/oxfw/oxfw-pcm.c @@ -192,10 +192,14 @@ static int pcm_open(struct snd_pcm_substream *substream) struct snd_oxfw *oxfw = substream->private_data; int err;
- err = init_hw_params(oxfw, substream); + err = snd_oxfw_stream_lock_try(oxfw); if (err < 0) goto end;
+ err = init_hw_params(oxfw, substream); + if (err < 0) + goto err_locked; + /* * When any PCM streams are already running, the available sampling * rate is limited at current value. @@ -210,10 +214,16 @@ static int pcm_open(struct snd_pcm_substream *substream) snd_pcm_set_sync(substream); end: return err; +err_locked: + snd_oxfw_stream_lock_release(oxfw); + return err; }
static int pcm_close(struct snd_pcm_substream *substream) { + struct snd_oxfw *oxfw = substream->private_data; + + snd_oxfw_stream_lock_release(oxfw); return 0; }
diff --git a/sound/firewire/oxfw/oxfw-stream.c b/sound/firewire/oxfw/oxfw-stream.c index a38b3c3..b77cf80 100644 --- a/sound/firewire/oxfw/oxfw-stream.c +++ b/sound/firewire/oxfw/oxfw-stream.c @@ -644,3 +644,42 @@ int snd_oxfw_stream_discover(struct snd_oxfw *oxfw) end: return err; } + +void snd_oxfw_stream_lock_changed(struct snd_oxfw *oxfw) +{ + oxfw->dev_lock_changed = true; + wake_up(&oxfw->hwdep_wait); +} + +int snd_oxfw_stream_lock_try(struct snd_oxfw *oxfw) +{ + int err; + + spin_lock_irq(&oxfw->lock); + + /* user land lock this */ + if (oxfw->dev_lock_count < 0) { + err = -EBUSY; + goto end; + } + + /* this is the first time */ + if (oxfw->dev_lock_count++ == 0) + snd_oxfw_stream_lock_changed(oxfw); + err = 0; +end: + spin_unlock_irq(&oxfw->lock); + return err; +} + +void snd_oxfw_stream_lock_release(struct snd_oxfw *oxfw) +{ + spin_lock_irq(&oxfw->lock); + + if (WARN_ON(oxfw->dev_lock_count <= 0)) + goto end; + if (--oxfw->dev_lock_count == 0) + snd_oxfw_stream_lock_changed(oxfw); +end: + spin_unlock_irq(&oxfw->lock); +} diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c index f6c0986..b67c132 100644 --- a/sound/firewire/oxfw/oxfw.c +++ b/sound/firewire/oxfw/oxfw.c @@ -141,6 +141,7 @@ static int oxfw_probe(struct fw_unit *unit, oxfw->unit = unit; oxfw->device_info = (const struct device_info *)id->driver_data; spin_lock_init(&oxfw->lock); + init_waitqueue_head(&oxfw->hwdep_wait);
err = snd_oxfw_stream_discover(oxfw); if (err < 0) @@ -166,6 +167,10 @@ static int oxfw_probe(struct fw_unit *unit, if (err < 0) goto error;
+ err = snd_oxfw_create_hwdep(oxfw); + if (err < 0) + goto error; + err = snd_oxfw_stream_init_simplex(oxfw, &oxfw->rx_stream); if (err < 0) goto error; diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h index 83a54fc..cace5ad 100644 --- a/sound/firewire/oxfw/oxfw.h +++ b/sound/firewire/oxfw/oxfw.h @@ -12,6 +12,7 @@ #include <linux/mod_devicetable.h> #include <linux/mutex.h> #include <linux/slab.h> +#include <linux/compat.h>
#include <sound/control.h> #include <sound/core.h> @@ -20,6 +21,8 @@ #include <sound/pcm_params.h> #include <sound/info.h> #include <sound/rawmidi.h> +#include <sound/firewire.h> +#include <sound/hwdep.h>
#include "../lib.h" #include "../fcp.h" @@ -64,6 +67,10 @@ struct snd_oxfw { s16 volume[6]; s16 volume_min; s16 volume_max; + + int dev_lock_count; + bool dev_lock_changed; + wait_queue_head_t hwdep_wait; };
/* @@ -124,6 +131,10 @@ int snd_oxfw_stream_get_current_formation(struct snd_oxfw *oxfw,
int snd_oxfw_stream_discover(struct snd_oxfw *oxfw);
+void snd_oxfw_stream_lock_changed(struct snd_oxfw *oxfw); +int snd_oxfw_stream_lock_try(struct snd_oxfw *oxfw); +void snd_oxfw_stream_lock_release(struct snd_oxfw *oxfw); + int snd_oxfw_create_pcm(struct snd_oxfw *oxfw);
int snd_oxfw_create_mixer(struct snd_oxfw *oxfw); @@ -131,3 +142,5 @@ int snd_oxfw_create_mixer(struct snd_oxfw *oxfw); void snd_oxfw_proc_init(struct snd_oxfw *oxfw);
int snd_oxfw_create_midi(struct snd_oxfw *oxfw); + +int snd_oxfw_create_hwdep(struct snd_oxfw *oxfw);
participants (2)
-
Clemens Ladisch
-
Takashi Sakamoto