/* * dice_stream.c - a part of driver for DICE based devices * * Copyright (c) Clemens Ladisch <clemens@ladisch.de> * Copyright (c) 2014 Takashi Sakamoto <o-takashi@sakamocchi.jp> * * Licensed under the terms of the GNU General Public License, version 2. */ #include "dice.h" #define CALLBACK_TIMEOUT 200 const unsigned int snd_dice_rates[SND_DICE_RATES_COUNT] = { /* mode 0 */ [0] = 32000, [1] = 44100, [2] = 48000, /* mode 1 */ [3] = 88200, [4] = 96000, /* mode 2 */ [5] = 176400, [6] = 192000, }; int snd_dice_stream_get_rate_mode(struct snd_dice *dice, unsigned int rate, unsigned int *mode) { int i; for (i = 0; i < ARRAY_SIZE(snd_dice_rates); i++) { if (!(dice->clock_caps & BIT(i))) continue; if (snd_dice_rates[i] != rate) continue; *mode = (i - 1) / 2; return 0; } return -EINVAL; } static void release_resources(struct snd_dice *dice, struct fw_iso_resources *resources) { unsigned int channel; /* Reset channel number */ channel = cpu_to_be32((u32)-1); if (resources == &dice->tx_resources) snd_dice_transaction_write_tx(dice, TX_ISOCHRONOUS, &channel, 4); else snd_dice_transaction_write_rx(dice, RX_ISOCHRONOUS, &channel, 4); fw_iso_resources_free(resources); } static int keep_resources(struct snd_dice *dice, struct fw_iso_resources *resources, unsigned int max_payload_bytes) { unsigned int channel; int err; err = fw_iso_resources_allocate(resources, max_payload_bytes, fw_parent_device(dice->unit)->max_speed); if (err < 0) goto end; /* Set channel number */ channel = cpu_to_be32(resources->channel); if (resources == &dice->tx_resources) err = snd_dice_transaction_write_tx(dice, TX_ISOCHRONOUS, &channel, 4); else err = snd_dice_transaction_write_rx(dice, RX_ISOCHRONOUS, &channel, 4); if (err < 0) release_resources(dice, resources); end: return err; } static void stop_stream(struct snd_dice *dice, struct amdtp_stream *stream) { amdtp_stream_pcm_abort(stream); amdtp_stream_stop(stream); if (stream == &dice->tx_stream) release_resources(dice, &dice->tx_resources); else release_resources(dice, &dice->rx_resources); } static int start_stream(struct snd_dice *dice, struct amdtp_stream *stream, unsigned int rate) { struct fw_iso_resources *resources; unsigned int i, mode, pcm_chs, midi_ports; int err; err = snd_dice_stream_get_rate_mode(dice, rate, &mode); if (err < 0) goto end; if (stream == &dice->tx_stream) { resources = &dice->tx_resources; pcm_chs = dice->tx_channels[mode]; midi_ports = dice->tx_midi_ports[mode]; } else { resources = &dice->rx_resources; pcm_chs = dice->rx_channels[mode]; midi_ports = dice->rx_midi_ports[mode]; } /* * At 176.4/192.0 kHz, Dice has a quirk to transfer two PCM frames in * one data block of AMDTP packet. Thus sampling transfer frequency is * a half of PCM sampling frequency, i.e. PCM frames at 192.0 kHz are * transferred on AMDTP packets at 96 kHz. Two successive samples of a * channel are stored consecutively in the packet. This quirk is called * as 'Dual Wire'. * For this quirk, blocking mode is required and PCM buffer size should * be aligned to SYT_INTERVAL. */ if (mode > 1) { rate /= 2; pcm_chs *= 2; stream->double_pcm_frames = true; } else { stream->double_pcm_frames = false; } amdtp_stream_set_parameters(stream, rate, pcm_chs, midi_ports); if (mode > 1) { pcm_chs /= 2; for (i = 0; i < pcm_chs; i++) { stream->pcm_positions[i] = i * 2; stream->pcm_positions[i + pcm_chs] = i * 2 + 1; } } err = keep_resources(dice, resources, amdtp_stream_get_max_payload(stream)); if (err < 0) { dev_err(&dice->unit->device, "fail to keep isochronous resources\n"); goto end; } err = amdtp_stream_start(stream, resources->channel, fw_parent_device(dice->unit)->max_speed); if (err < 0) release_resources(dice, resources); end: return err; } static int get_sync_mode(struct snd_dice *dice, enum cip_flags *sync_mode) { u32 source; int err; err = snd_dice_transaction_get_clock_source(dice, &source); if (err < 0) goto end; switch (source) { /* So-called 'SYT Match' modes, sync_to_syt value of packets received */ case CLOCK_SOURCE_ARX4: /* in 4th stream */ case CLOCK_SOURCE_ARX3: /* in 3rd stream */ case CLOCK_SOURCE_ARX2: /* in 2nd stream */ err = -ENOSYS; break; case CLOCK_SOURCE_ARX1: /* in 1st stream, which this driver uses */ *sync_mode = 0; break; default: *sync_mode = CIP_SYNC_TO_DEVICE; break; } end: return err; } int snd_dice_stream_start_duplex(struct snd_dice *dice, unsigned int rate) { struct amdtp_stream *master, *slave; unsigned int curr_rate; enum cip_flags sync_mode; int err = 0; if (dice->substreams_counter == 0) goto end; err = get_sync_mode(dice, &sync_mode); if (err < 0) goto end; if (sync_mode == CIP_SYNC_TO_DEVICE) { master = &dice->tx_stream; slave = &dice->rx_stream; } else { master = &dice->rx_stream; slave = &dice->tx_stream; } /* Some packet queueing errors. */ if (amdtp_streaming_error(master) || amdtp_streaming_error(slave)) stop_stream(dice, master); /* Stop stream if rate is different. */ err = snd_dice_transaction_get_rate(dice, &curr_rate); if (err < 0) { dev_err(&dice->unit->device, "fail to get sampling rate\n"); goto end; } if (rate == 0) rate = curr_rate; if (rate != curr_rate) stop_stream(dice, master); if (!amdtp_stream_running(master)) { stop_stream(dice, slave); snd_dice_transaction_clear_enable(dice); amdtp_stream_set_sync(sync_mode, master, slave); err = snd_dice_transaction_set_rate(dice, rate); if (err < 0) { dev_err(&dice->unit->device, "fail to set sampling rate\n"); goto end; } /* Start both streams. */ err = start_stream(dice, master, rate); if (err < 0) { dev_err(&dice->unit->device, "fail to start AMDTP master stream\n"); goto end; } err = start_stream(dice, slave, rate); if (err < 0) { dev_err(&dice->unit->device, "fail to start AMDTP slave stream\n"); stop_stream(dice, master); goto end; } err = snd_dice_transaction_set_enable(dice); if (err < 0) { dev_err(&dice->unit->device, "fail to enable interface\n"); stop_stream(dice, master); stop_stream(dice, slave); goto end; } /* Wait first callbacks */ if (!amdtp_stream_wait_callback(master, CALLBACK_TIMEOUT) || !amdtp_stream_wait_callback(slave, CALLBACK_TIMEOUT)) { snd_dice_transaction_clear_enable(dice); stop_stream(dice, master); stop_stream(dice, slave); err = -ETIMEDOUT; } } end: return err; } void snd_dice_stream_stop_duplex(struct snd_dice *dice) { if (dice->substreams_counter > 0) return; snd_dice_transaction_clear_enable(dice); stop_stream(dice, &dice->tx_stream); stop_stream(dice, &dice->rx_stream); } static int init_stream(struct snd_dice *dice, struct amdtp_stream *stream) { int err; struct fw_iso_resources *resources; enum amdtp_stream_direction dir; if (stream == &dice->tx_stream) { resources = &dice->tx_resources; dir = AMDTP_IN_STREAM; } else { resources = &dice->rx_resources; dir = AMDTP_OUT_STREAM; } err = fw_iso_resources_init(resources, dice->unit); if (err < 0) goto end; resources->channels_mask = 0x00000000ffffffffuLL; err = amdtp_stream_init(stream, dice->unit, dir, CIP_BLOCKING); if (err < 0) { amdtp_stream_destroy(stream); fw_iso_resources_destroy(resources); } end: return err; } /* * This function should be called before starting streams or after stopping * streams. */ static void destroy_stream(struct snd_dice *dice, struct amdtp_stream *stream) { struct fw_iso_resources *resources; if (stream == &dice->tx_stream) resources = &dice->tx_resources; else resources = &dice->rx_resources; amdtp_stream_destroy(stream); fw_iso_resources_destroy(resources); } int snd_dice_stream_init_duplex(struct snd_dice *dice) { int err; dice->substreams_counter = 0; err = init_stream(dice, &dice->tx_stream); if (err < 0) goto end; err = init_stream(dice, &dice->rx_stream); if (err < 0) destroy_stream(dice, &dice->tx_stream); end: return err; } void snd_dice_stream_destroy_duplex(struct snd_dice *dice) { snd_dice_transaction_clear_enable(dice); destroy_stream(dice, &dice->tx_stream); destroy_stream(dice, &dice->rx_stream); dice->substreams_counter = 0; } void snd_dice_stream_update_duplex(struct snd_dice *dice) { /* * On a bus reset, the DICE firmware disables streaming and then goes * off contemplating its own navel for hundreds of milliseconds before * it can react to any of our attempts to reenable streaming. This * means that we lose synchronization anyway, so we force our streams * to stop so that the application can restart them in an orderly * manner. */ dice->global_enabled = false; stop_stream(dice, &dice->rx_stream); stop_stream(dice, &dice->tx_stream); fw_iso_resources_update(&dice->rx_resources); fw_iso_resources_update(&dice->tx_resources); } static void dice_lock_changed(struct snd_dice *dice) { dice->dev_lock_changed = true; wake_up(&dice->hwdep_wait); } int snd_dice_stream_lock_try(struct snd_dice *dice) { int err; spin_lock_irq(&dice->lock); if (dice->dev_lock_count < 0) { err = -EBUSY; goto out; } if (dice->dev_lock_count++ == 0) dice_lock_changed(dice); err = 0; out: spin_unlock_irq(&dice->lock); return err; } void snd_dice_stream_lock_release(struct snd_dice *dice) { spin_lock_irq(&dice->lock); if (WARN_ON(dice->dev_lock_count <= 0)) goto out; if (--dice->dev_lock_count == 0) dice_lock_changed(dice); out: spin_unlock_irq(&dice->lock); }