| /* |
| * ff-protocol-ff400.c - a part of driver for RME Fireface series |
| * |
| * Copyright (c) 2015-2017 Takashi Sakamoto |
| * |
| * Licensed under the terms of the GNU General Public License, version 2. |
| */ |
| |
| #include <linux/delay.h> |
| #include "ff.h" |
| |
| #define FF400_STF 0x000080100500ull |
| #define FF400_RX_PACKET_FORMAT 0x000080100504ull |
| #define FF400_ISOC_COMM_START 0x000080100508ull |
| #define FF400_TX_PACKET_FORMAT 0x00008010050cull |
| #define FF400_ISOC_COMM_STOP 0x000080100510ull |
| #define FF400_SYNC_STATUS 0x0000801c0000ull |
| #define FF400_FETCH_PCM_FRAMES 0x0000801c0000ull /* For block request. */ |
| #define FF400_CLOCK_CONFIG 0x0000801c0004ull |
| |
| #define FF400_MIDI_HIGH_ADDR 0x0000801003f4ull |
| #define FF400_MIDI_RX_PORT_0 0x000080180000ull |
| #define FF400_MIDI_RX_PORT_1 0x000080190000ull |
| |
| static int ff400_get_clock(struct snd_ff *ff, unsigned int *rate, |
| enum snd_ff_clock_src *src) |
| { |
| __le32 reg; |
| u32 data; |
| int err; |
| |
| err = snd_fw_transaction(ff->unit, TCODE_READ_QUADLET_REQUEST, |
| FF400_SYNC_STATUS, ®, sizeof(reg), 0); |
| if (err < 0) |
| return err; |
| data = le32_to_cpu(reg); |
| |
| /* Calculate sampling rate. */ |
| switch ((data >> 1) & 0x03) { |
| case 0x01: |
| *rate = 32000; |
| break; |
| case 0x00: |
| *rate = 44100; |
| break; |
| case 0x03: |
| *rate = 48000; |
| break; |
| case 0x02: |
| default: |
| return -EIO; |
| } |
| |
| if (data & 0x08) |
| *rate *= 2; |
| else if (data & 0x10) |
| *rate *= 4; |
| |
| /* Calculate source of clock. */ |
| if (data & 0x01) { |
| *src = SND_FF_CLOCK_SRC_INTERNAL; |
| } else { |
| /* TODO: 0x00, 0x01, 0x02, 0x06, 0x07? */ |
| switch ((data >> 10) & 0x07) { |
| case 0x03: |
| *src = SND_FF_CLOCK_SRC_SPDIF; |
| break; |
| case 0x04: |
| *src = SND_FF_CLOCK_SRC_WORD; |
| break; |
| case 0x05: |
| *src = SND_FF_CLOCK_SRC_LTC; |
| break; |
| case 0x00: |
| default: |
| *src = SND_FF_CLOCK_SRC_ADAT; |
| break; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int ff400_begin_session(struct snd_ff *ff, unsigned int rate) |
| { |
| __le32 reg; |
| int i, err; |
| |
| /* Check whether the given value is supported or not. */ |
| for (i = 0; i < CIP_SFC_COUNT; i++) { |
| if (amdtp_rate_table[i] == rate) |
| break; |
| } |
| if (i == CIP_SFC_COUNT) |
| return -EINVAL; |
| |
| /* Set the number of data blocks transferred in a second. */ |
| reg = cpu_to_le32(rate); |
| err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, |
| FF400_STF, ®, sizeof(reg), 0); |
| if (err < 0) |
| return err; |
| |
| msleep(100); |
| |
| /* |
| * Set isochronous channel and the number of quadlets of received |
| * packets. |
| */ |
| reg = cpu_to_le32(((ff->rx_stream.data_block_quadlets << 3) << 8) | |
| ff->rx_resources.channel); |
| err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, |
| FF400_RX_PACKET_FORMAT, ®, sizeof(reg), 0); |
| if (err < 0) |
| return err; |
| |
| /* |
| * Set isochronous channel and the number of quadlets of transmitted |
| * packet. |
| */ |
| /* TODO: investigate the purpose of this 0x80. */ |
| reg = cpu_to_le32((0x80 << 24) | |
| (ff->tx_resources.channel << 5) | |
| (ff->tx_stream.data_block_quadlets)); |
| err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, |
| FF400_TX_PACKET_FORMAT, ®, sizeof(reg), 0); |
| if (err < 0) |
| return err; |
| |
| /* Allow to transmit packets. */ |
| reg = cpu_to_le32(0x00000001); |
| return snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, |
| FF400_ISOC_COMM_START, ®, sizeof(reg), 0); |
| } |
| |
| static void ff400_finish_session(struct snd_ff *ff) |
| { |
| __le32 reg; |
| |
| reg = cpu_to_le32(0x80000000); |
| snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, |
| FF400_ISOC_COMM_STOP, ®, sizeof(reg), 0); |
| } |
| |
| static int ff400_switch_fetching_mode(struct snd_ff *ff, bool enable) |
| { |
| __le32 *reg; |
| int i; |
| |
| reg = kzalloc(sizeof(__le32) * 18, GFP_KERNEL); |
| if (reg == NULL) |
| return -ENOMEM; |
| |
| if (enable) { |
| /* |
| * Each quadlet is corresponding to data channels in a data |
| * blocks in reverse order. Precisely, quadlets for available |
| * data channels should be enabled. Here, I take second best |
| * to fetch PCM frames from all of data channels regardless of |
| * stf. |
| */ |
| for (i = 0; i < 18; ++i) |
| reg[i] = cpu_to_le32(0x00000001); |
| } |
| |
| return snd_fw_transaction(ff->unit, TCODE_WRITE_BLOCK_REQUEST, |
| FF400_FETCH_PCM_FRAMES, reg, |
| sizeof(__le32) * 18, 0); |
| } |
| |
| static void ff400_dump_sync_status(struct snd_ff *ff, |
| struct snd_info_buffer *buffer) |
| { |
| __le32 reg; |
| u32 data; |
| int err; |
| |
| err = snd_fw_transaction(ff->unit, TCODE_READ_QUADLET_REQUEST, |
| FF400_SYNC_STATUS, ®, sizeof(reg), 0); |
| if (err < 0) |
| return; |
| |
| data = le32_to_cpu(reg); |
| |
| snd_iprintf(buffer, "External source detection:\n"); |
| |
| snd_iprintf(buffer, "Word Clock:"); |
| if ((data >> 24) & 0x20) { |
| if ((data >> 24) & 0x40) |
| snd_iprintf(buffer, "sync\n"); |
| else |
| snd_iprintf(buffer, "lock\n"); |
| } else { |
| snd_iprintf(buffer, "none\n"); |
| } |
| |
| snd_iprintf(buffer, "S/PDIF:"); |
| if ((data >> 16) & 0x10) { |
| if ((data >> 16) & 0x04) |
| snd_iprintf(buffer, "sync\n"); |
| else |
| snd_iprintf(buffer, "lock\n"); |
| } else { |
| snd_iprintf(buffer, "none\n"); |
| } |
| |
| snd_iprintf(buffer, "ADAT:"); |
| if ((data >> 8) & 0x04) { |
| if ((data >> 8) & 0x10) |
| snd_iprintf(buffer, "sync\n"); |
| else |
| snd_iprintf(buffer, "lock\n"); |
| } else { |
| snd_iprintf(buffer, "none\n"); |
| } |
| |
| snd_iprintf(buffer, "\nUsed external source:\n"); |
| |
| if (((data >> 22) & 0x07) == 0x07) { |
| snd_iprintf(buffer, "None\n"); |
| } else { |
| switch ((data >> 22) & 0x07) { |
| case 0x00: |
| snd_iprintf(buffer, "ADAT:"); |
| break; |
| case 0x03: |
| snd_iprintf(buffer, "S/PDIF:"); |
| break; |
| case 0x04: |
| snd_iprintf(buffer, "Word:"); |
| break; |
| case 0x07: |
| snd_iprintf(buffer, "Nothing:"); |
| break; |
| case 0x01: |
| case 0x02: |
| case 0x05: |
| case 0x06: |
| default: |
| snd_iprintf(buffer, "unknown:"); |
| break; |
| } |
| |
| if ((data >> 25) & 0x07) { |
| switch ((data >> 25) & 0x07) { |
| case 0x01: |
| snd_iprintf(buffer, "32000\n"); |
| break; |
| case 0x02: |
| snd_iprintf(buffer, "44100\n"); |
| break; |
| case 0x03: |
| snd_iprintf(buffer, "48000\n"); |
| break; |
| case 0x04: |
| snd_iprintf(buffer, "64000\n"); |
| break; |
| case 0x05: |
| snd_iprintf(buffer, "88200\n"); |
| break; |
| case 0x06: |
| snd_iprintf(buffer, "96000\n"); |
| break; |
| case 0x07: |
| snd_iprintf(buffer, "128000\n"); |
| break; |
| case 0x08: |
| snd_iprintf(buffer, "176400\n"); |
| break; |
| case 0x09: |
| snd_iprintf(buffer, "192000\n"); |
| break; |
| case 0x00: |
| snd_iprintf(buffer, "unknown\n"); |
| break; |
| } |
| } |
| } |
| |
| snd_iprintf(buffer, "Multiplied:"); |
| snd_iprintf(buffer, "%d\n", (data & 0x3ff) * 250); |
| } |
| |
| static void ff400_dump_clock_config(struct snd_ff *ff, |
| struct snd_info_buffer *buffer) |
| { |
| __le32 reg; |
| u32 data; |
| unsigned int rate; |
| const char *src; |
| int err; |
| |
| err = snd_fw_transaction(ff->unit, TCODE_READ_BLOCK_REQUEST, |
| FF400_CLOCK_CONFIG, ®, sizeof(reg), 0); |
| if (err < 0) |
| return; |
| |
| data = le32_to_cpu(reg); |
| |
| snd_iprintf(buffer, "Output S/PDIF format: %s (Emphasis: %s)\n", |
| (data & 0x20) ? "Professional" : "Consumer", |
| (data & 0x40) ? "on" : "off"); |
| |
| snd_iprintf(buffer, "Optical output interface format: %s\n", |
| ((data >> 8) & 0x01) ? "S/PDIF" : "ADAT"); |
| |
| snd_iprintf(buffer, "Word output single speed: %s\n", |
| ((data >> 8) & 0x20) ? "on" : "off"); |
| |
| snd_iprintf(buffer, "S/PDIF input interface: %s\n", |
| ((data >> 8) & 0x02) ? "Optical" : "Coaxial"); |
| |
| switch ((data >> 1) & 0x03) { |
| case 0x01: |
| rate = 32000; |
| break; |
| case 0x00: |
| rate = 44100; |
| break; |
| case 0x03: |
| rate = 48000; |
| break; |
| case 0x02: |
| default: |
| return; |
| } |
| |
| if (data & 0x08) |
| rate *= 2; |
| else if (data & 0x10) |
| rate *= 4; |
| |
| snd_iprintf(buffer, "Sampling rate: %d\n", rate); |
| |
| if (data & 0x01) { |
| src = "Internal"; |
| } else { |
| switch ((data >> 10) & 0x07) { |
| case 0x00: |
| src = "ADAT"; |
| break; |
| case 0x03: |
| src = "S/PDIF"; |
| break; |
| case 0x04: |
| src = "Word"; |
| break; |
| case 0x05: |
| src = "LTC"; |
| break; |
| default: |
| return; |
| } |
| } |
| |
| snd_iprintf(buffer, "Sync to clock source: %s\n", src); |
| } |
| |
| struct snd_ff_protocol snd_ff_protocol_ff400 = { |
| .get_clock = ff400_get_clock, |
| .begin_session = ff400_begin_session, |
| .finish_session = ff400_finish_session, |
| .switch_fetching_mode = ff400_switch_fetching_mode, |
| |
| .dump_sync_status = ff400_dump_sync_status, |
| .dump_clock_config = ff400_dump_clock_config, |
| |
| .midi_high_addr_reg = FF400_MIDI_HIGH_ADDR, |
| .midi_rx_port_0_reg = FF400_MIDI_RX_PORT_0, |
| .midi_rx_port_1_reg = FF400_MIDI_RX_PORT_1, |
| }; |