Commit | Line | Data |
---|---|---|
da607e19 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
5992e300 TS |
2 | /* |
3 | * motu-protocol-v3.c - a part of driver for MOTU FireWire series | |
4 | * | |
5 | * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> | |
5992e300 TS |
6 | */ |
7 | ||
8 | #include <linux/delay.h> | |
9 | #include "motu.h" | |
10 | ||
11 | #define V3_CLOCK_STATUS_OFFSET 0x0b14 | |
12 | #define V3_FETCH_PCM_FRAMES 0x02000000 | |
13 | #define V3_CLOCK_RATE_MASK 0x0000ff00 | |
14 | #define V3_CLOCK_RATE_SHIFT 8 | |
15 | #define V3_CLOCK_SOURCE_MASK 0x000000ff | |
5992e300 TS |
16 | |
17 | #define V3_OPT_IFACE_MODE_OFFSET 0x0c94 | |
18 | #define V3_ENABLE_OPT_IN_IFACE_A 0x00000001 | |
19 | #define V3_ENABLE_OPT_IN_IFACE_B 0x00000002 | |
20 | #define V3_ENABLE_OPT_OUT_IFACE_A 0x00000100 | |
21 | #define V3_ENABLE_OPT_OUT_IFACE_B 0x00000200 | |
22 | #define V3_NO_ADAT_OPT_IN_IFACE_A 0x00010000 | |
23 | #define V3_NO_ADAT_OPT_IN_IFACE_B 0x00100000 | |
24 | #define V3_NO_ADAT_OPT_OUT_IFACE_A 0x00040000 | |
25 | #define V3_NO_ADAT_OPT_OUT_IFACE_B 0x00400000 | |
26 | ||
27 | static int v3_get_clock_rate(struct snd_motu *motu, unsigned int *rate) | |
28 | { | |
29 | __be32 reg; | |
30 | u32 data; | |
31 | int err; | |
32 | ||
33 | err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, ®, | |
34 | sizeof(reg)); | |
35 | if (err < 0) | |
36 | return err; | |
37 | data = be32_to_cpu(reg); | |
38 | ||
39 | data = (data & V3_CLOCK_RATE_MASK) >> V3_CLOCK_RATE_SHIFT; | |
40 | if (data >= ARRAY_SIZE(snd_motu_clock_rates)) | |
41 | return -EIO; | |
42 | ||
43 | *rate = snd_motu_clock_rates[data]; | |
44 | ||
45 | return 0; | |
46 | } | |
47 | ||
48 | static int v3_set_clock_rate(struct snd_motu *motu, unsigned int rate) | |
49 | { | |
50 | __be32 reg; | |
51 | u32 data; | |
52 | bool need_to_wait; | |
53 | int i, err; | |
54 | ||
55 | for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { | |
56 | if (snd_motu_clock_rates[i] == rate) | |
57 | break; | |
58 | } | |
59 | if (i == ARRAY_SIZE(snd_motu_clock_rates)) | |
60 | return -EINVAL; | |
61 | ||
62 | err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, ®, | |
63 | sizeof(reg)); | |
64 | if (err < 0) | |
65 | return err; | |
66 | data = be32_to_cpu(reg); | |
67 | ||
68 | data &= ~(V3_CLOCK_RATE_MASK | V3_FETCH_PCM_FRAMES); | |
69 | data |= i << V3_CLOCK_RATE_SHIFT; | |
70 | ||
71 | need_to_wait = data != be32_to_cpu(reg); | |
72 | ||
73 | reg = cpu_to_be32(data); | |
74 | err = snd_motu_transaction_write(motu, V3_CLOCK_STATUS_OFFSET, ®, | |
75 | sizeof(reg)); | |
76 | if (err < 0) | |
77 | return err; | |
78 | ||
79 | if (need_to_wait) { | |
80 | /* Cost expensive. */ | |
81 | if (msleep_interruptible(4000) > 0) | |
82 | return -EINTR; | |
83 | } | |
84 | ||
85 | return 0; | |
86 | } | |
87 | ||
88 | static int v3_get_clock_source(struct snd_motu *motu, | |
89 | enum snd_motu_clock_source *src) | |
90 | { | |
91 | __be32 reg; | |
92 | u32 data; | |
93 | unsigned int val; | |
94 | int err; | |
95 | ||
96 | err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, ®, | |
97 | sizeof(reg)); | |
98 | if (err < 0) | |
99 | return err; | |
100 | data = be32_to_cpu(reg); | |
101 | ||
5b33504b | 102 | val = data & V3_CLOCK_SOURCE_MASK; |
5992e300 TS |
103 | if (val == 0x00) { |
104 | *src = SND_MOTU_CLOCK_SOURCE_INTERNAL; | |
105 | } else if (val == 0x01) { | |
106 | *src = SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC; | |
107 | } else if (val == 0x10) { | |
108 | *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX; | |
109 | } else if (val == 0x18 || val == 0x19) { | |
110 | err = snd_motu_transaction_read(motu, V3_OPT_IFACE_MODE_OFFSET, | |
111 | ®, sizeof(reg)); | |
112 | if (err < 0) | |
113 | return err; | |
114 | data = be32_to_cpu(reg); | |
115 | ||
116 | if (val == 0x18) { | |
117 | if (data & V3_NO_ADAT_OPT_IN_IFACE_A) | |
118 | *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_A; | |
119 | else | |
120 | *src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_A; | |
121 | } else { | |
122 | if (data & V3_NO_ADAT_OPT_IN_IFACE_B) | |
123 | *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_B; | |
124 | else | |
125 | *src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_B; | |
126 | } | |
127 | } else { | |
128 | *src = SND_MOTU_CLOCK_SOURCE_UNKNOWN; | |
129 | } | |
130 | ||
131 | return 0; | |
132 | } | |
133 | ||
134 | static int v3_switch_fetching_mode(struct snd_motu *motu, bool enable) | |
135 | { | |
136 | __be32 reg; | |
137 | u32 data; | |
138 | int err; | |
139 | ||
140 | err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, ®, | |
141 | sizeof(reg)); | |
142 | if (err < 0) | |
143 | return 0; | |
144 | data = be32_to_cpu(reg); | |
145 | ||
146 | if (enable) | |
147 | data |= V3_FETCH_PCM_FRAMES; | |
148 | else | |
149 | data &= ~V3_FETCH_PCM_FRAMES; | |
150 | ||
151 | reg = cpu_to_be32(data); | |
152 | return snd_motu_transaction_write(motu, V3_CLOCK_STATUS_OFFSET, ®, | |
153 | sizeof(reg)); | |
154 | } | |
155 | ||
156 | static void calculate_fixed_part(struct snd_motu_packet_format *formats, | |
157 | enum amdtp_stream_direction dir, | |
158 | enum snd_motu_spec_flags flags, | |
159 | unsigned char analog_ports) | |
160 | { | |
161 | unsigned char pcm_chunks[3] = {0, 0, 0}; | |
162 | ||
163 | formats->msg_chunks = 2; | |
164 | ||
165 | pcm_chunks[0] = analog_ports; | |
166 | pcm_chunks[1] = analog_ports; | |
167 | if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4) | |
168 | pcm_chunks[2] = analog_ports; | |
169 | ||
170 | if (dir == AMDTP_IN_STREAM) { | |
171 | if (flags & SND_MOTU_SPEC_TX_MICINST_CHUNK) { | |
172 | pcm_chunks[0] += 2; | |
173 | pcm_chunks[1] += 2; | |
174 | if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4) | |
175 | pcm_chunks[2] += 2; | |
176 | } | |
177 | ||
178 | if (flags & SND_MOTU_SPEC_TX_RETURN_CHUNK) { | |
179 | pcm_chunks[0] += 2; | |
180 | pcm_chunks[1] += 2; | |
181 | if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4) | |
182 | pcm_chunks[2] += 2; | |
183 | } | |
184 | ||
185 | if (flags & SND_MOTU_SPEC_TX_REVERB_CHUNK) { | |
186 | pcm_chunks[0] += 2; | |
187 | pcm_chunks[1] += 2; | |
188 | } | |
189 | } else { | |
81720c6d TS |
190 | if (flags & SND_MOTU_SPEC_RX_SEPARETED_MAIN) { |
191 | pcm_chunks[0] += 2; | |
192 | pcm_chunks[1] += 2; | |
193 | } | |
194 | ||
195 | // Packets to v3 units include 2 chunks for phone 1/2, except | |
196 | // for 176.4/192.0 kHz. | |
197 | pcm_chunks[0] += 2; | |
198 | pcm_chunks[1] += 2; | |
5992e300 TS |
199 | } |
200 | ||
06ac0b6f TS |
201 | if (flags & SND_MOTU_SPEC_HAS_AESEBU_IFACE) { |
202 | pcm_chunks[0] += 2; | |
203 | pcm_chunks[1] += 2; | |
204 | } | |
205 | ||
5992e300 TS |
206 | /* |
207 | * At least, packets have two data chunks for S/PDIF on coaxial | |
208 | * interface. | |
209 | */ | |
210 | pcm_chunks[0] += 2; | |
211 | pcm_chunks[1] += 2; | |
212 | ||
213 | /* | |
214 | * Fixed part consists of PCM chunks multiple of 4, with msg chunks. As | |
215 | * a result, this part can includes empty data chunks. | |
216 | */ | |
217 | formats->fixed_part_pcm_chunks[0] = round_up(2 + pcm_chunks[0], 4) - 2; | |
218 | formats->fixed_part_pcm_chunks[1] = round_up(2 + pcm_chunks[1], 4) - 2; | |
219 | if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4) | |
220 | formats->fixed_part_pcm_chunks[2] = | |
221 | round_up(2 + pcm_chunks[2], 4) - 2; | |
222 | } | |
223 | ||
224 | static void calculate_differed_part(struct snd_motu_packet_format *formats, | |
225 | enum snd_motu_spec_flags flags, u32 data, | |
226 | u32 a_enable_mask, u32 a_no_adat_mask, | |
227 | u32 b_enable_mask, u32 b_no_adat_mask) | |
228 | { | |
229 | unsigned char pcm_chunks[3] = {0, 0, 0}; | |
230 | int i; | |
231 | ||
232 | if ((flags & SND_MOTU_SPEC_HAS_OPT_IFACE_A) && (data & a_enable_mask)) { | |
233 | if (data & a_no_adat_mask) { | |
234 | /* | |
235 | * Additional two data chunks for S/PDIF on optical | |
236 | * interface A. This includes empty data chunks. | |
237 | */ | |
238 | pcm_chunks[0] += 4; | |
239 | pcm_chunks[1] += 4; | |
240 | } else { | |
241 | /* | |
242 | * Additional data chunks for ADAT on optical interface | |
243 | * A. | |
244 | */ | |
245 | pcm_chunks[0] += 8; | |
246 | pcm_chunks[1] += 4; | |
247 | } | |
248 | } | |
249 | ||
250 | if ((flags & SND_MOTU_SPEC_HAS_OPT_IFACE_B) && (data & b_enable_mask)) { | |
251 | if (data & b_no_adat_mask) { | |
252 | /* | |
253 | * Additional two data chunks for S/PDIF on optical | |
254 | * interface B. This includes empty data chunks. | |
255 | */ | |
256 | pcm_chunks[0] += 4; | |
257 | pcm_chunks[1] += 4; | |
258 | } else { | |
259 | /* | |
260 | * Additional data chunks for ADAT on optical interface | |
261 | * B. | |
262 | */ | |
263 | pcm_chunks[0] += 8; | |
264 | pcm_chunks[1] += 4; | |
265 | } | |
266 | } | |
267 | ||
268 | for (i = 0; i < 3; ++i) { | |
269 | if (pcm_chunks[i] > 0) | |
270 | pcm_chunks[i] = round_up(pcm_chunks[i], 4); | |
271 | ||
272 | formats->differed_part_pcm_chunks[i] = pcm_chunks[i]; | |
273 | } | |
274 | } | |
275 | ||
276 | static int v3_cache_packet_formats(struct snd_motu *motu) | |
277 | { | |
278 | __be32 reg; | |
279 | u32 data; | |
280 | int err; | |
281 | ||
282 | err = snd_motu_transaction_read(motu, V3_OPT_IFACE_MODE_OFFSET, ®, | |
283 | sizeof(reg)); | |
284 | if (err < 0) | |
285 | return err; | |
286 | data = be32_to_cpu(reg); | |
287 | ||
288 | calculate_fixed_part(&motu->tx_packet_formats, AMDTP_IN_STREAM, | |
289 | motu->spec->flags, motu->spec->analog_in_ports); | |
290 | calculate_differed_part(&motu->tx_packet_formats, | |
291 | motu->spec->flags, data, | |
292 | V3_ENABLE_OPT_IN_IFACE_A, V3_NO_ADAT_OPT_IN_IFACE_A, | |
293 | V3_ENABLE_OPT_IN_IFACE_B, V3_NO_ADAT_OPT_IN_IFACE_B); | |
294 | ||
295 | calculate_fixed_part(&motu->rx_packet_formats, AMDTP_OUT_STREAM, | |
296 | motu->spec->flags, motu->spec->analog_out_ports); | |
297 | calculate_differed_part(&motu->rx_packet_formats, | |
298 | motu->spec->flags, data, | |
299 | V3_ENABLE_OPT_OUT_IFACE_A, V3_NO_ADAT_OPT_OUT_IFACE_A, | |
300 | V3_ENABLE_OPT_OUT_IFACE_B, V3_NO_ADAT_OPT_OUT_IFACE_B); | |
301 | ||
5992e300 | 302 | motu->tx_packet_formats.pcm_byte_offset = 10; |
5992e300 TS |
303 | motu->rx_packet_formats.pcm_byte_offset = 10; |
304 | ||
305 | return 0; | |
306 | } | |
307 | ||
308 | const struct snd_motu_protocol snd_motu_protocol_v3 = { | |
309 | .get_clock_rate = v3_get_clock_rate, | |
310 | .set_clock_rate = v3_set_clock_rate, | |
311 | .get_clock_source = v3_get_clock_source, | |
312 | .switch_fetching_mode = v3_switch_fetching_mode, | |
313 | .cache_packet_formats = v3_cache_packet_formats, | |
314 | }; |