Takashi Sakamoto | fd1cc9d | 2019-01-20 17:25:53 +0900 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | // ff-protocol-latter - 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 | |
Geert Uytterhoeven | 6954158 | 2019-05-28 16:24:23 +0200 | [diff] [blame] | 12 | #define LATTER_STF 0xffff00000004ULL |
| 13 | #define LATTER_ISOC_CHANNELS 0xffff00000008ULL |
| 14 | #define LATTER_ISOC_START 0xffff0000000cULL |
| 15 | #define LATTER_FETCH_MODE 0xffff00000010ULL |
| 16 | #define LATTER_SYNC_STATUS 0x0000801c0000ULL |
Takashi Sakamoto | fd1cc9d | 2019-01-20 17:25:53 +0900 | [diff] [blame] | 17 | |
| 18 | static int parse_clock_bits(u32 data, unsigned int *rate, |
Takashi Sakamoto | 062bb45 | 2020-05-10 16:43:00 +0900 | [diff] [blame] | 19 | enum snd_ff_clock_src *src, |
| 20 | enum snd_ff_unit_version unit_version) |
Takashi Sakamoto | fd1cc9d | 2019-01-20 17:25:53 +0900 | [diff] [blame] | 21 | { |
| 22 | static const struct { |
| 23 | unsigned int rate; |
| 24 | u32 flag; |
| 25 | } *rate_entry, rate_entries[] = { |
| 26 | { 32000, 0x00000000, }, |
| 27 | { 44100, 0x01000000, }, |
| 28 | { 48000, 0x02000000, }, |
| 29 | { 64000, 0x04000000, }, |
| 30 | { 88200, 0x05000000, }, |
| 31 | { 96000, 0x06000000, }, |
| 32 | { 128000, 0x08000000, }, |
| 33 | { 176400, 0x09000000, }, |
| 34 | { 192000, 0x0a000000, }, |
| 35 | }; |
| 36 | static const struct { |
| 37 | enum snd_ff_clock_src src; |
| 38 | u32 flag; |
| 39 | } *clk_entry, clk_entries[] = { |
| 40 | { SND_FF_CLOCK_SRC_SPDIF, 0x00000200, }, |
| 41 | { SND_FF_CLOCK_SRC_ADAT1, 0x00000400, }, |
| 42 | { SND_FF_CLOCK_SRC_WORD, 0x00000600, }, |
| 43 | { SND_FF_CLOCK_SRC_INTERNAL, 0x00000e00, }, |
| 44 | }; |
| 45 | int i; |
| 46 | |
Takashi Sakamoto | 062bb45 | 2020-05-10 16:43:00 +0900 | [diff] [blame] | 47 | if (unit_version != SND_FF_UNIT_VERSION_UCX) { |
| 48 | // e.g. 0x00fe0f20 but expected 0x00eff002. |
| 49 | data = ((data & 0xf0f0f0f0) >> 4) | ((data & 0x0f0f0f0f) << 4); |
| 50 | } |
| 51 | |
Takashi Sakamoto | fd1cc9d | 2019-01-20 17:25:53 +0900 | [diff] [blame] | 52 | for (i = 0; i < ARRAY_SIZE(rate_entries); ++i) { |
| 53 | rate_entry = rate_entries + i; |
| 54 | if ((data & 0x0f000000) == rate_entry->flag) { |
| 55 | *rate = rate_entry->rate; |
| 56 | break; |
| 57 | } |
| 58 | } |
| 59 | if (i == ARRAY_SIZE(rate_entries)) |
| 60 | return -EIO; |
| 61 | |
| 62 | for (i = 0; i < ARRAY_SIZE(clk_entries); ++i) { |
| 63 | clk_entry = clk_entries + i; |
| 64 | if ((data & 0x000e00) == clk_entry->flag) { |
| 65 | *src = clk_entry->src; |
| 66 | break; |
| 67 | } |
| 68 | } |
| 69 | if (i == ARRAY_SIZE(clk_entries)) |
| 70 | return -EIO; |
| 71 | |
| 72 | return 0; |
| 73 | } |
| 74 | |
| 75 | static int latter_get_clock(struct snd_ff *ff, unsigned int *rate, |
| 76 | enum snd_ff_clock_src *src) |
| 77 | { |
| 78 | __le32 reg; |
| 79 | u32 data; |
| 80 | int err; |
| 81 | |
| 82 | err = snd_fw_transaction(ff->unit, TCODE_READ_QUADLET_REQUEST, |
| 83 | LATTER_SYNC_STATUS, ®, sizeof(reg), 0); |
| 84 | if (err < 0) |
| 85 | return err; |
| 86 | data = le32_to_cpu(reg); |
| 87 | |
Takashi Sakamoto | 062bb45 | 2020-05-10 16:43:00 +0900 | [diff] [blame] | 88 | return parse_clock_bits(data, rate, src, ff->unit_version); |
Takashi Sakamoto | fd1cc9d | 2019-01-20 17:25:53 +0900 | [diff] [blame] | 89 | } |
| 90 | |
| 91 | static int latter_switch_fetching_mode(struct snd_ff *ff, bool enable) |
| 92 | { |
| 93 | u32 data; |
| 94 | __le32 reg; |
| 95 | |
| 96 | if (enable) |
| 97 | data = 0x00000000; |
| 98 | else |
| 99 | data = 0xffffffff; |
| 100 | reg = cpu_to_le32(data); |
| 101 | |
| 102 | return snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, |
| 103 | LATTER_FETCH_MODE, ®, sizeof(reg), 0); |
| 104 | } |
| 105 | |
Takashi Sakamoto | 60aec49 | 2019-06-02 16:12:55 +0900 | [diff] [blame] | 106 | static int latter_allocate_resources(struct snd_ff *ff, unsigned int rate) |
Takashi Sakamoto | fd1cc9d | 2019-01-20 17:25:53 +0900 | [diff] [blame] | 107 | { |
| 108 | enum snd_ff_stream_mode mode; |
Takashi Sakamoto | 60aec49 | 2019-06-02 16:12:55 +0900 | [diff] [blame] | 109 | unsigned int code; |
Takashi Sakamoto | fd1cc9d | 2019-01-20 17:25:53 +0900 | [diff] [blame] | 110 | __le32 reg; |
| 111 | unsigned int count; |
| 112 | int i; |
| 113 | int err; |
| 114 | |
Takashi Sakamoto | 60aec49 | 2019-06-02 16:12:55 +0900 | [diff] [blame] | 115 | // Set the number of data blocks transferred in a second. |
Takashi Sakamoto | bbd6aac | 2020-05-10 16:42:56 +0900 | [diff] [blame] | 116 | if (rate % 48000 == 0) |
| 117 | code = 0x04; |
Takashi Sakamoto | 60aec49 | 2019-06-02 16:12:55 +0900 | [diff] [blame] | 118 | else if (rate % 44100 == 0) |
| 119 | code = 0x02; |
Takashi Sakamoto | bbd6aac | 2020-05-10 16:42:56 +0900 | [diff] [blame] | 120 | else if (rate % 32000 == 0) |
| 121 | code = 0x00; |
Takashi Sakamoto | 60aec49 | 2019-06-02 16:12:55 +0900 | [diff] [blame] | 122 | else |
Takashi Sakamoto | fd1cc9d | 2019-01-20 17:25:53 +0900 | [diff] [blame] | 123 | return -EINVAL; |
| 124 | |
Takashi Sakamoto | 60aec49 | 2019-06-02 16:12:55 +0900 | [diff] [blame] | 125 | if (rate >= 64000 && rate < 128000) |
| 126 | code |= 0x08; |
Takashi Sakamoto | bbd6aac | 2020-05-10 16:42:56 +0900 | [diff] [blame] | 127 | else if (rate >= 128000) |
Takashi Sakamoto | 60aec49 | 2019-06-02 16:12:55 +0900 | [diff] [blame] | 128 | code |= 0x10; |
| 129 | |
| 130 | reg = cpu_to_le32(code); |
Takashi Sakamoto | fd1cc9d | 2019-01-20 17:25:53 +0900 | [diff] [blame] | 131 | err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, |
| 132 | LATTER_STF, ®, sizeof(reg), 0); |
| 133 | if (err < 0) |
| 134 | return err; |
| 135 | |
| 136 | // Confirm to shift transmission clock. |
| 137 | count = 0; |
| 138 | while (count++ < 10) { |
| 139 | unsigned int curr_rate; |
| 140 | enum snd_ff_clock_src src; |
| 141 | |
| 142 | err = latter_get_clock(ff, &curr_rate, &src); |
| 143 | if (err < 0) |
| 144 | return err; |
| 145 | |
| 146 | if (curr_rate == rate) |
| 147 | break; |
| 148 | } |
Takashi Sakamoto | bbd6aac | 2020-05-10 16:42:56 +0900 | [diff] [blame] | 149 | if (count > 10) |
Takashi Sakamoto | fd1cc9d | 2019-01-20 17:25:53 +0900 | [diff] [blame] | 150 | return -ETIMEDOUT; |
| 151 | |
Takashi Sakamoto | 60aec49 | 2019-06-02 16:12:55 +0900 | [diff] [blame] | 152 | for (i = 0; i < ARRAY_SIZE(amdtp_rate_table); ++i) { |
| 153 | if (rate == amdtp_rate_table[i]) |
| 154 | break; |
| 155 | } |
| 156 | if (i == ARRAY_SIZE(amdtp_rate_table)) |
| 157 | return -EINVAL; |
| 158 | |
| 159 | err = snd_ff_stream_get_multiplier_mode(i, &mode); |
Takashi Sakamoto | fd1cc9d | 2019-01-20 17:25:53 +0900 | [diff] [blame] | 160 | if (err < 0) |
| 161 | return err; |
| 162 | |
Takashi Sakamoto | 60aec49 | 2019-06-02 16:12:55 +0900 | [diff] [blame] | 163 | // Keep resources for in-stream. |
| 164 | ff->tx_resources.channels_mask = 0x00000000000000ffuLL; |
| 165 | err = fw_iso_resources_allocate(&ff->tx_resources, |
| 166 | amdtp_stream_get_max_payload(&ff->tx_stream), |
| 167 | fw_parent_device(ff->unit)->max_speed); |
| 168 | if (err < 0) |
| 169 | return err; |
| 170 | |
| 171 | // Keep resources for out-stream. |
| 172 | ff->rx_resources.channels_mask = 0x00000000000000ffuLL; |
| 173 | err = fw_iso_resources_allocate(&ff->rx_resources, |
| 174 | amdtp_stream_get_max_payload(&ff->rx_stream), |
| 175 | fw_parent_device(ff->unit)->max_speed); |
| 176 | if (err < 0) |
| 177 | fw_iso_resources_free(&ff->tx_resources); |
| 178 | |
| 179 | return err; |
| 180 | } |
| 181 | |
| 182 | static int latter_begin_session(struct snd_ff *ff, unsigned int rate) |
| 183 | { |
Takashi Sakamoto | b88f4d7 | 2019-06-02 16:12:57 +0900 | [diff] [blame] | 184 | unsigned int generation = ff->rx_resources.generation; |
Takashi Sakamoto | 60aec49 | 2019-06-02 16:12:55 +0900 | [diff] [blame] | 185 | unsigned int flag; |
| 186 | u32 data; |
| 187 | __le32 reg; |
| 188 | int err; |
| 189 | |
Takashi Sakamoto | 062bb45 | 2020-05-10 16:43:00 +0900 | [diff] [blame] | 190 | if (ff->unit_version == SND_FF_UNIT_VERSION_UCX) { |
| 191 | // For Fireface UCX. Always use the maximum number of data |
| 192 | // channels in data block of packet. |
| 193 | if (rate >= 32000 && rate <= 48000) |
| 194 | flag = 0x92; |
| 195 | else if (rate >= 64000 && rate <= 96000) |
| 196 | flag = 0x8e; |
| 197 | else if (rate >= 128000 && rate <= 192000) |
| 198 | flag = 0x8c; |
| 199 | else |
| 200 | return -EINVAL; |
| 201 | } else { |
Takashi Sakamoto | 1f65e66 | 2020-05-10 16:43:01 +0900 | [diff] [blame] | 202 | // For Fireface UFX and 802. Due to bandwidth limitation on |
Takashi Sakamoto | 062bb45 | 2020-05-10 16:43:00 +0900 | [diff] [blame] | 203 | // IEEE 1394a (400 Mbps), Analog 1-12 and AES are available |
| 204 | // without any ADAT at quadruple speed. |
| 205 | if (rate >= 32000 && rate <= 48000) |
| 206 | flag = 0x9e; |
| 207 | else if (rate >= 64000 && rate <= 96000) |
| 208 | flag = 0x96; |
| 209 | else if (rate >= 128000 && rate <= 192000) |
| 210 | flag = 0x8e; |
| 211 | else |
| 212 | return -EINVAL; |
| 213 | } |
Takashi Sakamoto | 60aec49 | 2019-06-02 16:12:55 +0900 | [diff] [blame] | 214 | |
Takashi Sakamoto | b88f4d7 | 2019-06-02 16:12:57 +0900 | [diff] [blame] | 215 | if (generation != fw_parent_device(ff->unit)->card->generation) { |
| 216 | err = fw_iso_resources_update(&ff->tx_resources); |
| 217 | if (err < 0) |
| 218 | return err; |
| 219 | |
| 220 | err = fw_iso_resources_update(&ff->rx_resources); |
| 221 | if (err < 0) |
| 222 | return err; |
| 223 | } |
| 224 | |
Takashi Sakamoto | fd1cc9d | 2019-01-20 17:25:53 +0900 | [diff] [blame] | 225 | data = (ff->tx_resources.channel << 8) | ff->rx_resources.channel; |
| 226 | reg = cpu_to_le32(data); |
| 227 | err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, |
| 228 | LATTER_ISOC_CHANNELS, ®, sizeof(reg), 0); |
| 229 | if (err < 0) |
| 230 | return err; |
| 231 | |
Takashi Sakamoto | 60aec49 | 2019-06-02 16:12:55 +0900 | [diff] [blame] | 232 | reg = cpu_to_le32(flag); |
Takashi Sakamoto | fd1cc9d | 2019-01-20 17:25:53 +0900 | [diff] [blame] | 233 | return snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, |
| 234 | LATTER_ISOC_START, ®, sizeof(reg), 0); |
| 235 | } |
| 236 | |
| 237 | static void latter_finish_session(struct snd_ff *ff) |
| 238 | { |
| 239 | __le32 reg; |
| 240 | |
| 241 | reg = cpu_to_le32(0x00000000); |
| 242 | snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, |
| 243 | LATTER_ISOC_START, ®, sizeof(reg), 0); |
| 244 | } |
| 245 | |
| 246 | static void latter_dump_status(struct snd_ff *ff, struct snd_info_buffer *buffer) |
| 247 | { |
| 248 | static const struct { |
| 249 | char *const label; |
| 250 | u32 locked_mask; |
| 251 | u32 synced_mask; |
| 252 | } *clk_entry, clk_entries[] = { |
| 253 | { "S/PDIF", 0x00000001, 0x00000010, }, |
| 254 | { "ADAT", 0x00000002, 0x00000020, }, |
| 255 | { "WDClk", 0x00000004, 0x00000040, }, |
| 256 | }; |
| 257 | __le32 reg; |
| 258 | u32 data; |
| 259 | unsigned int rate; |
| 260 | enum snd_ff_clock_src src; |
| 261 | const char *label; |
| 262 | int i; |
| 263 | int err; |
| 264 | |
| 265 | err = snd_fw_transaction(ff->unit, TCODE_READ_QUADLET_REQUEST, |
| 266 | LATTER_SYNC_STATUS, ®, sizeof(reg), 0); |
| 267 | if (err < 0) |
| 268 | return; |
| 269 | data = le32_to_cpu(reg); |
| 270 | |
| 271 | snd_iprintf(buffer, "External source detection:\n"); |
| 272 | |
| 273 | for (i = 0; i < ARRAY_SIZE(clk_entries); ++i) { |
| 274 | clk_entry = clk_entries + i; |
| 275 | snd_iprintf(buffer, "%s: ", clk_entry->label); |
| 276 | if (data & clk_entry->locked_mask) { |
| 277 | if (data & clk_entry->synced_mask) |
| 278 | snd_iprintf(buffer, "sync\n"); |
| 279 | else |
| 280 | snd_iprintf(buffer, "lock\n"); |
| 281 | } else { |
| 282 | snd_iprintf(buffer, "none\n"); |
| 283 | } |
| 284 | } |
| 285 | |
Takashi Sakamoto | 062bb45 | 2020-05-10 16:43:00 +0900 | [diff] [blame] | 286 | err = parse_clock_bits(data, &rate, &src, ff->unit_version); |
Takashi Sakamoto | fd1cc9d | 2019-01-20 17:25:53 +0900 | [diff] [blame] | 287 | if (err < 0) |
| 288 | return; |
| 289 | label = snd_ff_proc_get_clk_label(src); |
| 290 | if (!label) |
| 291 | return; |
| 292 | |
| 293 | snd_iprintf(buffer, "Referred clock: %s %d\n", label, rate); |
| 294 | } |
| 295 | |
Takashi Sakamoto | 73f5537 | 2019-01-22 22:17:02 +0900 | [diff] [blame] | 296 | // NOTE: transactions are transferred within 0x00-0x7f in allocated range of |
| 297 | // address. This seems to be for check of discontinuity in receiver side. |
Takashi Sakamoto | d800253 | 2019-01-24 18:32:03 +0900 | [diff] [blame] | 298 | // |
| 299 | // Like Fireface 400, drivers can select one of 4 options for lower 4 bytes of |
| 300 | // destination address by bit flags in quadlet register (little endian) at |
| 301 | // 0x'ffff'0000'0014: |
| 302 | // |
| 303 | // bit flags: offset of destination address |
| 304 | // - 0x00002000: 0x'....'....'0000'0000 |
| 305 | // - 0x00004000: 0x'....'....'0000'0080 |
| 306 | // - 0x00008000: 0x'....'....'0000'0100 |
| 307 | // - 0x00010000: 0x'....'....'0000'0180 |
| 308 | // |
| 309 | // Drivers can suppress the device to transfer asynchronous transactions by |
| 310 | // clear these bit flags. |
| 311 | // |
| 312 | // Actually, the register is write-only and includes the other settings such as |
| 313 | // input attenuation. This driver allocates for the first option |
| 314 | // (0x'....'....'0000'0000) and expects userspace application to configure the |
| 315 | // register for it. |
Takashi Sakamoto | 73f5537 | 2019-01-22 22:17:02 +0900 | [diff] [blame] | 316 | static void latter_handle_midi_msg(struct snd_ff *ff, unsigned int offset, |
| 317 | __le32 *buf, size_t length) |
| 318 | { |
| 319 | u32 data = le32_to_cpu(*buf); |
| 320 | unsigned int index = (data & 0x000000f0) >> 4; |
| 321 | u8 byte[3]; |
| 322 | struct snd_rawmidi_substream *substream; |
| 323 | unsigned int len; |
| 324 | |
Dan Carpenter | 2dee43e | 2019-01-25 15:44:18 +0300 | [diff] [blame] | 325 | if (index >= ff->spec->midi_in_ports) |
Takashi Sakamoto | 73f5537 | 2019-01-22 22:17:02 +0900 | [diff] [blame] | 326 | return; |
| 327 | |
| 328 | switch (data & 0x0000000f) { |
| 329 | case 0x00000008: |
| 330 | case 0x00000009: |
| 331 | case 0x0000000a: |
| 332 | case 0x0000000b: |
| 333 | case 0x0000000e: |
| 334 | len = 3; |
| 335 | break; |
| 336 | case 0x0000000c: |
| 337 | case 0x0000000d: |
| 338 | len = 2; |
| 339 | break; |
| 340 | default: |
| 341 | len = data & 0x00000003; |
| 342 | if (len == 0) |
| 343 | len = 3; |
| 344 | break; |
| 345 | } |
| 346 | |
| 347 | byte[0] = (data & 0x0000ff00) >> 8; |
| 348 | byte[1] = (data & 0x00ff0000) >> 16; |
| 349 | byte[2] = (data & 0xff000000) >> 24; |
| 350 | |
| 351 | substream = READ_ONCE(ff->tx_midi_substreams[index]); |
| 352 | if (substream) |
| 353 | snd_rawmidi_receive(substream, byte, len); |
| 354 | } |
| 355 | |
Takashi Sakamoto | f0f9f49 | 2019-01-22 22:17:05 +0900 | [diff] [blame] | 356 | /* |
| 357 | * When return minus value, given argument is not MIDI status. |
| 358 | * When return 0, given argument is a beginning of system exclusive. |
| 359 | * When return the others, given argument is MIDI data. |
| 360 | */ |
| 361 | static inline int calculate_message_bytes(u8 status) |
| 362 | { |
| 363 | switch (status) { |
| 364 | case 0xf6: /* Tune request. */ |
| 365 | case 0xf8: /* Timing clock. */ |
| 366 | case 0xfa: /* Start. */ |
| 367 | case 0xfb: /* Continue. */ |
| 368 | case 0xfc: /* Stop. */ |
| 369 | case 0xfe: /* Active sensing. */ |
| 370 | case 0xff: /* System reset. */ |
| 371 | return 1; |
| 372 | case 0xf1: /* MIDI time code quarter frame. */ |
| 373 | case 0xf3: /* Song select. */ |
| 374 | return 2; |
| 375 | case 0xf2: /* Song position pointer. */ |
| 376 | return 3; |
| 377 | case 0xf0: /* Exclusive. */ |
| 378 | return 0; |
| 379 | case 0xf7: /* End of exclusive. */ |
| 380 | break; |
| 381 | case 0xf4: /* Undefined. */ |
| 382 | case 0xf5: /* Undefined. */ |
| 383 | case 0xf9: /* Undefined. */ |
| 384 | case 0xfd: /* Undefined. */ |
| 385 | break; |
| 386 | default: |
| 387 | switch (status & 0xf0) { |
| 388 | case 0x80: /* Note on. */ |
| 389 | case 0x90: /* Note off. */ |
| 390 | case 0xa0: /* Polyphonic key pressure. */ |
| 391 | case 0xb0: /* Control change and Mode change. */ |
| 392 | case 0xe0: /* Pitch bend change. */ |
| 393 | return 3; |
| 394 | case 0xc0: /* Program change. */ |
| 395 | case 0xd0: /* Channel pressure. */ |
| 396 | return 2; |
| 397 | default: |
| 398 | break; |
| 399 | } |
| 400 | break; |
| 401 | } |
| 402 | |
| 403 | return -EINVAL; |
| 404 | } |
| 405 | |
| 406 | static int latter_fill_midi_msg(struct snd_ff *ff, |
| 407 | struct snd_rawmidi_substream *substream, |
| 408 | unsigned int port) |
| 409 | { |
| 410 | u32 data = {0}; |
| 411 | u8 *buf = (u8 *)&data; |
| 412 | int consumed; |
| 413 | |
| 414 | buf[0] = port << 4; |
| 415 | consumed = snd_rawmidi_transmit_peek(substream, buf + 1, 3); |
| 416 | if (consumed <= 0) |
| 417 | return consumed; |
| 418 | |
| 419 | if (!ff->on_sysex[port]) { |
| 420 | if (buf[1] != 0xf0) { |
| 421 | if (consumed < calculate_message_bytes(buf[1])) |
| 422 | return 0; |
| 423 | } else { |
| 424 | // The beginning of exclusives. |
| 425 | ff->on_sysex[port] = true; |
| 426 | } |
| 427 | |
| 428 | buf[0] |= consumed; |
| 429 | } else { |
| 430 | if (buf[1] != 0xf7) { |
| 431 | if (buf[2] == 0xf7 || buf[3] == 0xf7) { |
| 432 | // Transfer end code at next time. |
| 433 | consumed -= 1; |
| 434 | } |
| 435 | |
| 436 | buf[0] |= consumed; |
| 437 | } else { |
| 438 | // The end of exclusives. |
| 439 | ff->on_sysex[port] = false; |
| 440 | consumed = 1; |
| 441 | buf[0] |= 0x0f; |
| 442 | } |
| 443 | } |
| 444 | |
| 445 | ff->msg_buf[port][0] = cpu_to_le32(data); |
| 446 | ff->rx_bytes[port] = consumed; |
| 447 | |
| 448 | return 1; |
| 449 | } |
| 450 | |
Takashi Sakamoto | fd1cc9d | 2019-01-20 17:25:53 +0900 | [diff] [blame] | 451 | const struct snd_ff_protocol snd_ff_protocol_latter = { |
Takashi Sakamoto | 73f5537 | 2019-01-22 22:17:02 +0900 | [diff] [blame] | 452 | .handle_midi_msg = latter_handle_midi_msg, |
Takashi Sakamoto | f0f9f49 | 2019-01-22 22:17:05 +0900 | [diff] [blame] | 453 | .fill_midi_msg = latter_fill_midi_msg, |
Takashi Sakamoto | fd1cc9d | 2019-01-20 17:25:53 +0900 | [diff] [blame] | 454 | .get_clock = latter_get_clock, |
| 455 | .switch_fetching_mode = latter_switch_fetching_mode, |
Takashi Sakamoto | 60aec49 | 2019-06-02 16:12:55 +0900 | [diff] [blame] | 456 | .allocate_resources = latter_allocate_resources, |
Takashi Sakamoto | fd1cc9d | 2019-01-20 17:25:53 +0900 | [diff] [blame] | 457 | .begin_session = latter_begin_session, |
| 458 | .finish_session = latter_finish_session, |
| 459 | .dump_status = latter_dump_status, |
| 460 | }; |