|  | /* | 
|  | * tascam-stream.c - a part of driver for TASCAM FireWire series | 
|  | * | 
|  | * Copyright (c) 2015 Takashi Sakamoto | 
|  | * | 
|  | * Licensed under the terms of the GNU General Public License, version 2. | 
|  | */ | 
|  |  | 
|  | #include <linux/delay.h> | 
|  | #include "tascam.h" | 
|  |  | 
|  | #define CLOCK_STATUS_MASK      0xffff0000 | 
|  | #define CLOCK_CONFIG_MASK      0x0000ffff | 
|  |  | 
|  | #define CALLBACK_TIMEOUT 500 | 
|  |  | 
|  | 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; | 
|  |  | 
|  | err = enable_data_channels(tscm); | 
|  | if (err < 0) | 
|  | return err; | 
|  |  | 
|  | return set_clock(tscm, rate, INT_MAX); | 
|  | } | 
|  |  | 
|  | 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); | 
|  |  | 
|  | } | 
|  |  | 
|  | static int begin_session(struct snd_tscm *tscm) | 
|  | { | 
|  | __be32 reg; | 
|  | int 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 void release_resources(struct snd_tscm *tscm) | 
|  | { | 
|  | __be32 reg; | 
|  |  | 
|  | /* 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); | 
|  |  | 
|  | /* Release isochronous resources. */ | 
|  | fw_iso_resources_free(&tscm->tx_resources); | 
|  | fw_iso_resources_free(&tscm->rx_resources); | 
|  | } | 
|  |  | 
|  | static int keep_resources(struct snd_tscm *tscm, unsigned int rate) | 
|  | { | 
|  | __be32 reg; | 
|  | int err; | 
|  |  | 
|  | /* Keep resources for in-stream. */ | 
|  | err = amdtp_tscm_set_parameters(&tscm->tx_stream, rate); | 
|  | if (err < 0) | 
|  | return err; | 
|  | err = fw_iso_resources_allocate(&tscm->tx_resources, | 
|  | amdtp_stream_get_max_payload(&tscm->tx_stream), | 
|  | fw_parent_device(tscm->unit)->max_speed); | 
|  | if (err < 0) | 
|  | goto error; | 
|  |  | 
|  | /* Keep resources for out-stream. */ | 
|  | err = amdtp_tscm_set_parameters(&tscm->rx_stream, rate); | 
|  | if (err < 0) | 
|  | return err; | 
|  | err = fw_iso_resources_allocate(&tscm->rx_resources, | 
|  | amdtp_stream_get_max_payload(&tscm->rx_stream), | 
|  | fw_parent_device(tscm->unit)->max_speed); | 
|  | if (err < 0) | 
|  | return 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) | 
|  | goto error; | 
|  |  | 
|  | /* 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) | 
|  | goto error; | 
|  |  | 
|  | /* 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) | 
|  | goto error; | 
|  |  | 
|  | return 0; | 
|  | error: | 
|  | release_resources(tscm); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | int snd_tscm_stream_init_duplex(struct snd_tscm *tscm) | 
|  | { | 
|  | unsigned int pcm_channels; | 
|  | int err; | 
|  |  | 
|  | /* For out-stream. */ | 
|  | err = fw_iso_resources_init(&tscm->rx_resources, tscm->unit); | 
|  | if (err < 0) | 
|  | return err; | 
|  | 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 = amdtp_tscm_init(&tscm->rx_stream, tscm->unit, AMDTP_OUT_STREAM, | 
|  | pcm_channels); | 
|  | if (err < 0) | 
|  | return err; | 
|  |  | 
|  | /* For in-stream. */ | 
|  | err = fw_iso_resources_init(&tscm->tx_resources, tscm->unit); | 
|  | if (err < 0) | 
|  | return err; | 
|  | pcm_channels = tscm->spec->pcm_capture_analog_channels; | 
|  | if (tscm->spec->has_adat) | 
|  | pcm_channels += 8; | 
|  | if (tscm->spec->has_spdif) | 
|  | pcm_channels += 2; | 
|  | err = amdtp_tscm_init(&tscm->tx_stream, tscm->unit, AMDTP_IN_STREAM, | 
|  | pcm_channels); | 
|  | if (err < 0) | 
|  | amdtp_stream_destroy(&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_stream_pcm_abort(&tscm->tx_stream); | 
|  | amdtp_stream_stop(&tscm->tx_stream); | 
|  |  | 
|  | amdtp_stream_pcm_abort(&tscm->rx_stream); | 
|  | amdtp_stream_stop(&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_stream_destroy(&tscm->rx_stream); | 
|  | amdtp_stream_destroy(&tscm->tx_stream); | 
|  |  | 
|  | fw_iso_resources_destroy(&tscm->rx_resources); | 
|  | fw_iso_resources_destroy(&tscm->tx_resources); | 
|  | } | 
|  |  | 
|  | int snd_tscm_stream_start_duplex(struct snd_tscm *tscm, unsigned int rate) | 
|  | { | 
|  | unsigned int curr_rate; | 
|  | int err; | 
|  |  | 
|  | if (tscm->substreams_counter == 0) | 
|  | return 0; | 
|  |  | 
|  | err = snd_tscm_stream_get_rate(tscm, &curr_rate); | 
|  | if (err < 0) | 
|  | return err; | 
|  | if (curr_rate != rate || | 
|  | amdtp_streaming_error(&tscm->rx_stream) || | 
|  | amdtp_streaming_error(&tscm->tx_stream)) { | 
|  | finish_session(tscm); | 
|  |  | 
|  | amdtp_stream_stop(&tscm->rx_stream); | 
|  | amdtp_stream_stop(&tscm->tx_stream); | 
|  |  | 
|  | release_resources(tscm); | 
|  | } | 
|  |  | 
|  | if (!amdtp_stream_running(&tscm->rx_stream)) { | 
|  | err = keep_resources(tscm, rate); | 
|  | if (err < 0) | 
|  | goto error; | 
|  |  | 
|  | err = set_stream_formats(tscm, rate); | 
|  | if (err < 0) | 
|  | goto error; | 
|  |  | 
|  | err = begin_session(tscm); | 
|  | if (err < 0) | 
|  | goto error; | 
|  |  | 
|  | err = amdtp_stream_start(&tscm->rx_stream, | 
|  | tscm->rx_resources.channel, | 
|  | fw_parent_device(tscm->unit)->max_speed); | 
|  | if (err < 0) | 
|  | goto error; | 
|  |  | 
|  | if (!amdtp_stream_wait_callback(&tscm->rx_stream, | 
|  | CALLBACK_TIMEOUT)) { | 
|  | err = -ETIMEDOUT; | 
|  | goto error; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!amdtp_stream_running(&tscm->tx_stream)) { | 
|  | err = amdtp_stream_start(&tscm->tx_stream, | 
|  | tscm->tx_resources.channel, | 
|  | fw_parent_device(tscm->unit)->max_speed); | 
|  | if (err < 0) | 
|  | goto error; | 
|  |  | 
|  | if (!amdtp_stream_wait_callback(&tscm->tx_stream, | 
|  | CALLBACK_TIMEOUT)) { | 
|  | err = -ETIMEDOUT; | 
|  | goto error; | 
|  | } | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | error: | 
|  | amdtp_stream_stop(&tscm->rx_stream); | 
|  | amdtp_stream_stop(&tscm->tx_stream); | 
|  |  | 
|  | finish_session(tscm); | 
|  | release_resources(tscm); | 
|  |  | 
|  | return err; | 
|  | } | 
|  |  | 
|  | void snd_tscm_stream_stop_duplex(struct snd_tscm *tscm) | 
|  | { | 
|  | if (tscm->substreams_counter > 0) | 
|  | return; | 
|  |  | 
|  | amdtp_stream_stop(&tscm->tx_stream); | 
|  | amdtp_stream_stop(&tscm->rx_stream); | 
|  |  | 
|  | finish_session(tscm); | 
|  | release_resources(tscm); | 
|  | } | 
|  |  | 
|  | 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); | 
|  | } |