| /* |
| * 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_MIDI_HIGH_ADDR 0x0000801003f4ull |
| |
| 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; |
| int err; |
| |
| reg = kcalloc(18, sizeof(__le32), 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); |
| } |
| |
| err = snd_fw_transaction(ff->unit, TCODE_WRITE_BLOCK_REQUEST, |
| SND_FF_REG_FETCH_PCM_FRAMES, reg, |
| sizeof(__le32) * 18, 0); |
| kfree(reg); |
| return err; |
| } |
| |
| const struct snd_ff_protocol snd_ff_protocol_ff400 = { |
| .begin_session = ff400_begin_session, |
| .finish_session = ff400_finish_session, |
| .switch_fetching_mode = ff400_switch_fetching_mode, |
| |
| .midi_high_addr_reg = FF400_MIDI_HIGH_ADDR, |
| }; |