Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/sound/firewire/tascam/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 12 kB image not shown  

Quelle  tascam-stream.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0-only
/*
 * tascam-stream.c - a part of driver for TASCAM FireWire series
 *
 * Copyright (c) 2015 Takashi Sakamoto
 */


#include <linux/delay.h>
#include "tascam.h"

#define CLOCK_STATUS_MASK      0xffff0000
#define CLOCK_CONFIG_MASK      0x0000ffff

#define READY_TIMEOUT_MS 4000

static int get_clock(struct snd_tscm *tscm, u32 *data)
{
 int trial = 0;
 __be32 reg;
 int err;

 while (trial++ < 5) {
  err = snd_fw_transaction(tscm->unit, TCODE_READ_QUADLET_REQUEST,
    TSCM_ADDR_BASE + TSCM_OFFSET_CLOCK_STATUS,
    ®, sizeof(reg), 0);
  if (err < 0)
   return err;

  *data = be32_to_cpu(reg);
  if (*data & CLOCK_STATUS_MASK)
   break;

  // In intermediate state after changing clock status.
  msleep(50);
 }

 // Still in the intermediate state.
 if (trial >= 5)
  return -EAGAIN;

 return 0;
}

static int set_clock(struct snd_tscm *tscm, unsigned int rate,
       enum snd_tscm_clock clock)
{
 u32 data;
 __be32 reg;
 int err;

 err = get_clock(tscm, &data);
 if (err < 0)
  return err;
 data &= CLOCK_CONFIG_MASK;

 if (rate > 0) {
  data &= 0x000000ff;
  /* Base rate. */
  if ((rate % 44100) == 0) {
   data |= 0x00000100;
   /* Multiplier. */
   if (rate / 44100 == 2)
    data |= 0x00008000;
  } else if ((rate % 48000) == 0) {
   data |= 0x00000200;
   /* Multiplier. */
   if (rate / 48000 == 2)
    data |= 0x00008000;
  } else {
   return -EAGAIN;
  }
 }

 if (clock != INT_MAX) {
  data &= 0x0000ff00;
  data |= clock + 1;
 }

 reg = cpu_to_be32(data);

 err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
     TSCM_ADDR_BASE + TSCM_OFFSET_CLOCK_STATUS,
     ®, sizeof(reg), 0);
 if (err < 0)
  return err;

 if (data & 0x00008000)
  reg = cpu_to_be32(0x0000001a);
 else
  reg = cpu_to_be32(0x0000000d);

 return snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
      TSCM_ADDR_BASE + TSCM_OFFSET_MULTIPLEX_MODE,
      ®, sizeof(reg), 0);
}

int snd_tscm_stream_get_rate(struct snd_tscm *tscm, unsigned int *rate)
{
 u32 data;
 int err;

 err = get_clock(tscm, &data);
 if (err < 0)
  return err;

 data = (data & 0xff000000) >> 24;

 /* Check base rate. */
 if ((data & 0x0f) == 0x01)
  *rate = 44100;
 else if ((data & 0x0f) == 0x02)
  *rate = 48000;
 else
  return -EAGAIN;

 /* Check multiplier. */
 if ((data & 0xf0) == 0x80)
  *rate *= 2;
 else if ((data & 0xf0) != 0x00)
  return -EAGAIN;

 return err;
}

int snd_tscm_stream_get_clock(struct snd_tscm *tscm, enum snd_tscm_clock *clock)
{
 u32 data;
 int err;

 err = get_clock(tscm, &data);
 if (err < 0)
  return err;

 *clock = ((data & 0x00ff0000) >> 16) - 1;
 if (*clock < 0 || *clock > SND_TSCM_CLOCK_ADAT)
  return -EIO;

 return 0;
}

static int enable_data_channels(struct snd_tscm *tscm)
{
 __be32 reg;
 u32 data;
 unsigned int i;
 int err;

 data = 0;
 for (i = 0; i < tscm->spec->pcm_capture_analog_channels; ++i)
  data |= BIT(i);
 if (tscm->spec->has_adat)
  data |= 0x0000ff00;
 if (tscm->spec->has_spdif)
  data |= 0x00030000;

 reg = cpu_to_be32(data);
 err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
     TSCM_ADDR_BASE + TSCM_OFFSET_TX_PCM_CHANNELS,
     ®, sizeof(reg), 0);
 if (err < 0)
  return err;

 data = 0;
 for (i = 0; i < tscm->spec->pcm_playback_analog_channels; ++i)
  data |= BIT(i);
 if (tscm->spec->has_adat)
  data |= 0x0000ff00;
 if (tscm->spec->has_spdif)
  data |= 0x00030000;

 reg = cpu_to_be32(data);
 return snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
      TSCM_ADDR_BASE + TSCM_OFFSET_RX_PCM_CHANNELS,
      ®, sizeof(reg), 0);
}

static int set_stream_formats(struct snd_tscm *tscm, unsigned int rate)
{
 __be32 reg;
 int err;

 // Set an option for unknown purpose.
 reg = cpu_to_be32(0x00200000);
 err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
     TSCM_ADDR_BASE + TSCM_OFFSET_SET_OPTION,
     ®, sizeof(reg), 0);
 if (err < 0)
  return err;

 return enable_data_channels(tscm);
}

static void finish_session(struct snd_tscm *tscm)
{
 __be32 reg;

 reg = 0;
 snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
      TSCM_ADDR_BASE + TSCM_OFFSET_START_STREAMING,
      ®, sizeof(reg), 0);

 reg = 0;
 snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
      TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_ON,
      ®, sizeof(reg), 0);

 // Unregister channels.
 reg = cpu_to_be32(0x00000000);
 snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
      TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_TX_CH,
      ®, sizeof(reg), 0);
 reg = cpu_to_be32(0x00000000);
 snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
      TSCM_ADDR_BASE + TSCM_OFFSET_UNKNOWN,
      ®, sizeof(reg), 0);
 reg = cpu_to_be32(0x00000000);
 snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
      TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_CH,
      ®, sizeof(reg), 0);
}

static int begin_session(struct snd_tscm *tscm)
{
 __be32 reg;
 int err;

 // Register the isochronous channel for transmitting stream.
 reg = cpu_to_be32(tscm->tx_resources.channel);
 err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
     TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_TX_CH,
     ®, sizeof(reg), 0);
 if (err < 0)
  return err;

 // Unknown.
 reg = cpu_to_be32(0x00000002);
 err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
     TSCM_ADDR_BASE + TSCM_OFFSET_UNKNOWN,
     ®, sizeof(reg), 0);
 if (err < 0)
  return err;

 // Register the isochronous channel for receiving stream.
 reg = cpu_to_be32(tscm->rx_resources.channel);
 err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
     TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_CH,
     ®, sizeof(reg), 0);
 if (err < 0)
  return err;

 reg = cpu_to_be32(0x00000001);
 err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
     TSCM_ADDR_BASE + TSCM_OFFSET_START_STREAMING,
     ®, sizeof(reg), 0);
 if (err < 0)
  return err;

 reg = cpu_to_be32(0x00000001);
 err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
     TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_ON,
     ®, sizeof(reg), 0);
 if (err < 0)
  return err;

 // Set an option for unknown purpose.
 reg = cpu_to_be32(0x00002000);
 err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
     TSCM_ADDR_BASE + TSCM_OFFSET_SET_OPTION,
     ®, sizeof(reg), 0);
 if (err < 0)
  return err;

 // Start multiplexing PCM samples on packets.
 reg = cpu_to_be32(0x00000001);
 return snd_fw_transaction(tscm->unit,
      TCODE_WRITE_QUADLET_REQUEST,
      TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_TX_ON,
      ®, sizeof(reg), 0);
}

static int keep_resources(struct snd_tscm *tscm, unsigned int rate,
     struct amdtp_stream *stream)
{
 struct fw_iso_resources *resources;
 int err;

 if (stream == &tscm->tx_stream)
  resources = &tscm->tx_resources;
 else
  resources = &tscm->rx_resources;

 err = amdtp_tscm_set_parameters(stream, rate);
 if (err < 0)
  return err;

 return fw_iso_resources_allocate(resources,
    amdtp_stream_get_max_payload(stream),
    fw_parent_device(tscm->unit)->max_speed);
}

static int init_stream(struct snd_tscm *tscm, struct amdtp_stream *s)
{
 struct fw_iso_resources *resources;
 enum amdtp_stream_direction dir;
 unsigned int pcm_channels;
 int err;

 if (s == &tscm->tx_stream) {
  resources = &tscm->tx_resources;
  dir = AMDTP_IN_STREAM;
  pcm_channels = tscm->spec->pcm_capture_analog_channels;
 } else {
  resources = &tscm->rx_resources;
  dir = AMDTP_OUT_STREAM;
  pcm_channels = tscm->spec->pcm_playback_analog_channels;
 }

 if (tscm->spec->has_adat)
  pcm_channels += 8;
 if (tscm->spec->has_spdif)
  pcm_channels += 2;

 err = fw_iso_resources_init(resources, tscm->unit);
 if (err < 0)
  return err;

 err = amdtp_tscm_init(s, tscm->unit, dir, pcm_channels);
 if (err < 0)
  fw_iso_resources_free(resources);

 return err;
}

static void destroy_stream(struct snd_tscm *tscm, struct amdtp_stream *s)
{
 amdtp_stream_destroy(s);

 if (s == &tscm->tx_stream)
  fw_iso_resources_destroy(&tscm->tx_resources);
 else
  fw_iso_resources_destroy(&tscm->rx_resources);
}

int snd_tscm_stream_init_duplex(struct snd_tscm *tscm)
{
 int err;

 err = init_stream(tscm, &tscm->tx_stream);
 if (err < 0)
  return err;

 err = init_stream(tscm, &tscm->rx_stream);
 if (err < 0) {
  destroy_stream(tscm, &tscm->tx_stream);
  return err;
 }

 err = amdtp_domain_init(&tscm->domain);
 if (err < 0) {
  destroy_stream(tscm, &tscm->tx_stream);
  destroy_stream(tscm, &tscm->rx_stream);
 }

 return err;
}

// At bus reset, streaming is stopped and some registers are clear.
void snd_tscm_stream_update_duplex(struct snd_tscm *tscm)
{
 amdtp_domain_stop(&tscm->domain);

 amdtp_stream_pcm_abort(&tscm->tx_stream);
 amdtp_stream_pcm_abort(&tscm->rx_stream);
}

// This function should be called before starting streams or after stopping
// streams.
void snd_tscm_stream_destroy_duplex(struct snd_tscm *tscm)
{
 amdtp_domain_destroy(&tscm->domain);

 destroy_stream(tscm, &tscm->rx_stream);
 destroy_stream(tscm, &tscm->tx_stream);
}

int snd_tscm_stream_reserve_duplex(struct snd_tscm *tscm, unsigned int rate,
       unsigned int frames_per_period,
       unsigned int frames_per_buffer)
{
 unsigned int curr_rate;
 int err;

 err = snd_tscm_stream_get_rate(tscm, &curr_rate);
 if (err < 0)
  return err;

 if (tscm->substreams_counter == 0 || rate != curr_rate) {
  amdtp_domain_stop(&tscm->domain);

  finish_session(tscm);

  fw_iso_resources_free(&tscm->tx_resources);
  fw_iso_resources_free(&tscm->rx_resources);

  err = set_clock(tscm, rate, INT_MAX);
  if (err < 0)
   return err;

  err = keep_resources(tscm, rate, &tscm->tx_stream);
  if (err < 0)
   return err;

  err = keep_resources(tscm, rate, &tscm->rx_stream);
  if (err < 0) {
   fw_iso_resources_free(&tscm->tx_resources);
   return err;
  }

  err = amdtp_domain_set_events_per_period(&tscm->domain,
     frames_per_period, frames_per_buffer);
  if (err < 0) {
   fw_iso_resources_free(&tscm->tx_resources);
   fw_iso_resources_free(&tscm->rx_resources);
   return err;
  }

  tscm->need_long_tx_init_skip = (rate != curr_rate);
 }

 return 0;
}

int snd_tscm_stream_start_duplex(struct snd_tscm *tscm, unsigned int rate)
{
 unsigned int generation = tscm->rx_resources.generation;
 int err;

 if (tscm->substreams_counter == 0)
  return 0;

 if (amdtp_streaming_error(&tscm->rx_stream) ||
     amdtp_streaming_error(&tscm->tx_stream)) {
  amdtp_domain_stop(&tscm->domain);
  finish_session(tscm);
 }

 if (generation != fw_parent_device(tscm->unit)->card->generation) {
  err = fw_iso_resources_update(&tscm->tx_resources);
  if (err < 0)
   goto error;

  err = fw_iso_resources_update(&tscm->rx_resources);
  if (err < 0)
   goto error;
 }

 if (!amdtp_stream_running(&tscm->rx_stream)) {
  int spd = fw_parent_device(tscm->unit)->max_speed;
  unsigned int tx_init_skip_cycles;

  err = set_stream_formats(tscm, rate);
  if (err < 0)
   goto error;

  err = begin_session(tscm);
  if (err < 0)
   goto error;

  err = amdtp_domain_add_stream(&tscm->domain, &tscm->rx_stream,
           tscm->rx_resources.channel, spd);
  if (err < 0)
   goto error;

  err = amdtp_domain_add_stream(&tscm->domain, &tscm->tx_stream,
           tscm->tx_resources.channel, spd);
  if (err < 0)
   goto error;

  if (tscm->need_long_tx_init_skip)
   tx_init_skip_cycles = 16000;
  else
   tx_init_skip_cycles = 0;

  // MEMO: Just after starting packet streaming, it transfers packets without any
  // event. Enough after receiving the sequence of packets, it multiplexes events into
  // the packet. However, just after changing sampling transfer frequency, it stops
  // multiplexing during packet transmission. Enough after, it restarts multiplexing
  // again. The device ignores presentation time expressed by the value of syt field
  // of CIP header in received packets. The sequence of the number of data blocks per
  // packet is important for media clock recovery.
  err = amdtp_domain_start(&tscm->domain, tx_init_skip_cycles, truetrue);
  if (err < 0)
   goto error;

  if (!amdtp_domain_wait_ready(&tscm->domain, READY_TIMEOUT_MS)) {
   err = -ETIMEDOUT;
   goto error;
  }
 }

 return 0;
error:
 amdtp_domain_stop(&tscm->domain);
 finish_session(tscm);

 return err;
}

void snd_tscm_stream_stop_duplex(struct snd_tscm *tscm)
{
 if (tscm->substreams_counter == 0) {
  amdtp_domain_stop(&tscm->domain);
  finish_session(tscm);

  fw_iso_resources_free(&tscm->tx_resources);
  fw_iso_resources_free(&tscm->rx_resources);

  tscm->need_long_tx_init_skip = false;
 }
}

void snd_tscm_stream_lock_changed(struct snd_tscm *tscm)
{
 tscm->dev_lock_changed = true;
 wake_up(&tscm->hwdep_wait);
}

int snd_tscm_stream_lock_try(struct snd_tscm *tscm)
{
 int err;

 spin_lock_irq(&tscm->lock);

 /* user land lock this */
 if (tscm->dev_lock_count < 0) {
  err = -EBUSY;
  goto end;
 }

 /* this is the first time */
 if (tscm->dev_lock_count++ == 0)
  snd_tscm_stream_lock_changed(tscm);
 err = 0;
end:
 spin_unlock_irq(&tscm->lock);
 return err;
}

void snd_tscm_stream_lock_release(struct snd_tscm *tscm)
{
 spin_lock_irq(&tscm->lock);

 if (WARN_ON(tscm->dev_lock_count <= 0))
  goto end;
 if (--tscm->dev_lock_count == 0)
  snd_tscm_stream_lock_changed(tscm);
end:
 spin_unlock_irq(&tscm->lock);
}

Messung V0.5
C=95 H=90 G=92

¤ Dauer der Verarbeitung: 0.2 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.