1 // SPDX-License-Identifier: GPL-2.0
2 // ff-protocol-former.c - a part of driver for RME Fireface series
3 //
4 // Copyright (c) 2019 Takashi Sakamoto
5 //
6 // Licensed under the terms of the GNU General Public License, version 2.
7 
8 #include <linux/delay.h>
9 
10 #include "ff.h"
11 
12 #define FF800_STF		0x0000fc88f000
13 #define FF800_RX_PACKET_FORMAT	0x0000fc88f004
14 #define FF800_ALLOC_TX_STREAM	0x0000fc88f008
15 #define FF800_ISOC_COMM_START	0x0000fc88f00c
16 #define   FF800_TX_S800_FLAG	0x00000800
17 #define FF800_ISOC_COMM_STOP	0x0000fc88f010
18 
19 #define FF800_TX_PACKET_ISOC_CH	0x0000801c0008
20 
21 static int allocate_rx_resources(struct snd_ff *ff)
22 {
23 	u32 data;
24 	__le32 reg;
25 	int err;
26 
27 	// Controllers should allocate isochronous resources for rx stream.
28 	err = fw_iso_resources_allocate(&ff->rx_resources,
29 				amdtp_stream_get_max_payload(&ff->rx_stream),
30 				fw_parent_device(ff->unit)->max_speed);
31 	if (err < 0)
32 		return err;
33 
34 	// Set isochronous channel and the number of quadlets of rx packets.
35 	data = ff->rx_stream.data_block_quadlets << 3;
36 	data = (data << 8) | ff->rx_resources.channel;
37 	reg = cpu_to_le32(data);
38 	return snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
39 				FF800_RX_PACKET_FORMAT, &reg, sizeof(reg), 0);
40 }
41 
42 static int allocate_tx_resources(struct snd_ff *ff)
43 {
44 	__le32 reg;
45 	unsigned int count;
46 	unsigned int tx_isoc_channel;
47 	int err;
48 
49 	reg = cpu_to_le32(ff->tx_stream.data_block_quadlets);
50 	err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
51 				 FF800_ALLOC_TX_STREAM, &reg, sizeof(reg), 0);
52 	if (err < 0)
53 		return err;
54 
55 	// Wait till the format of tx packet is available.
56 	count = 0;
57 	while (count++ < 10) {
58 		u32 data;
59 		err = snd_fw_transaction(ff->unit, TCODE_READ_QUADLET_REQUEST,
60 				FF800_TX_PACKET_ISOC_CH, &reg, sizeof(reg), 0);
61 		if (err < 0)
62 			return err;
63 
64 		data = le32_to_cpu(reg);
65 		if (data != 0xffffffff) {
66 			tx_isoc_channel = data;
67 			break;
68 		}
69 
70 		msleep(50);
71 	}
72 	if (count >= 10)
73 		return -ETIMEDOUT;
74 
75 	// NOTE: this is a makeshift to start OHCI 1394 IR context in the
76 	// channel. On the other hand, 'struct fw_iso_resources.allocated' is
77 	// not true and it's not deallocated at stop.
78 	ff->tx_resources.channel = tx_isoc_channel;
79 
80 	return 0;
81 }
82 
83 static int ff800_begin_session(struct snd_ff *ff, unsigned int rate)
84 {
85 	__le32 reg;
86 	int err;
87 
88 	reg = cpu_to_le32(rate);
89 	err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
90 				 FF800_STF, &reg, sizeof(reg), 0);
91 	if (err < 0)
92 		return err;
93 
94 	// If starting isochronous communication immediately, change of STF has
95 	// no effect. In this case, the communication runs based on former STF.
96 	// Let's sleep for a bit.
97 	msleep(100);
98 
99 	err = allocate_rx_resources(ff);
100 	if (err < 0)
101 		return err;
102 
103 	err = allocate_tx_resources(ff);
104 	if (err < 0)
105 		return err;
106 
107 	reg = cpu_to_le32(0x80000000);
108 	reg |= cpu_to_le32(ff->tx_stream.data_block_quadlets);
109 	if (fw_parent_device(ff->unit)->max_speed == SCODE_800)
110 		reg |= cpu_to_le32(FF800_TX_S800_FLAG);
111 	return snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
112 				 FF800_ISOC_COMM_START, &reg, sizeof(reg), 0);
113 }
114 
115 static void ff800_finish_session(struct snd_ff *ff)
116 {
117 	__le32 reg;
118 
119 	reg = cpu_to_le32(0x80000000);
120 	snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
121 			   FF800_ISOC_COMM_STOP, &reg, sizeof(reg), 0);
122 }
123 
124 static void ff800_handle_midi_msg(struct snd_ff *ff, __le32 *buf, size_t length)
125 {
126 	int i;
127 
128 	for (i = 0; i < length / 4; i++) {
129 		u8 byte = le32_to_cpu(buf[i]) & 0xff;
130 		struct snd_rawmidi_substream *substream;
131 
132 		substream = READ_ONCE(ff->tx_midi_substreams[0]);
133 		if (substream)
134 			snd_rawmidi_receive(substream, &byte, 1);
135 	}
136 }
137 
138 const struct snd_ff_protocol snd_ff_protocol_ff800 = {
139 	.handle_midi_msg	= ff800_handle_midi_msg,
140 	.begin_session		= ff800_begin_session,
141 	.finish_session		= ff800_finish_session,
142 };
143