| /* | |
| * 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 CALLBACK_TIMEOUT 500 | |
| static int get_clock(struct snd_tscm *tscm, u32 *data) | |
| { | |
| __be32 reg; | |
| int err; | |
| err = snd_fw_transaction(tscm->unit, TCODE_READ_QUADLET_REQUEST, | |
| TSCM_ADDR_BASE + TSCM_OFFSET_CLOCK_STATUS, | |
| ®, sizeof(reg), 0); | |
| if (err >= 0) | |
| *data = be32_to_cpu(reg); | |
| return err; | |
| } | |
| 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 &= 0x0000ffff; | |
| 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 = 0x0; | |
| unsigned int trials = 0; | |
| int err; | |
| while (data == 0x0 || trials++ < 5) { | |
| 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 0; | |
| } | |
| /* 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->tx_stream) || | |
| amdtp_streaming_error(&tscm->rx_stream)) { | |
| finish_session(tscm); | |
| amdtp_stream_stop(&tscm->tx_stream); | |
| amdtp_stream_stop(&tscm->rx_stream); | |
| release_resources(tscm); | |
| } | |
| if (!amdtp_stream_running(&tscm->tx_stream)) { | |
| amdtp_stream_set_sync(CIP_SYNC_TO_DEVICE, | |
| &tscm->tx_stream, &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->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; | |
| } | |
| } | |
| if (!amdtp_stream_running(&tscm->rx_stream)) { | |
| 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; | |
| } | |
| } | |
| return 0; | |
| error: | |
| amdtp_stream_stop(&tscm->tx_stream); | |
| amdtp_stream_stop(&tscm->rx_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); | |
| } |