/* * tascam-transaction.c - a part of driver for TASCAM FireWire series * * Copyright (c) 2015 Takashi Sakamoto * * Licensed under the terms of the GNU General Public License, version 2. */ #include "tascam.h" /* * When return minus value, given argument is not MIDI status. * When return 0, given argument is a beginning of system exclusive. * When return the others, given argument is MIDI data. */ static inline int calculate_message_bytes(u8 status) { switch (status) { case 0xf6: /* Tune request. */ case 0xf8: /* Timing clock. */ case 0xfa: /* Start. */ case 0xfb: /* Continue. */ case 0xfc: /* Stop. */ case 0xfe: /* Active sensing. */ case 0xff: /* System reset. */ return 1; case 0xf1: /* MIDI time code quarter frame. */ case 0xf3: /* Song select. */ return 2; case 0xf2: /* Song position pointer. */ return 3; case 0xf0: /* Exclusive. */ return 0; case 0xf7: /* End of exclusive. */ break; case 0xf4: /* Undefined. */ case 0xf5: /* Undefined. */ case 0xf9: /* Undefined. */ case 0xfd: /* Undefined. */ break; default: switch (status & 0xf0) { case 0x80: /* Note on. */ case 0x90: /* Note off. */ case 0xa0: /* Polyphonic key pressure. */ case 0xb0: /* Control change and Mode change. */ case 0xe0: /* Pitch bend change. */ return 3; case 0xc0: /* Program change. */ case 0xd0: /* Channel pressure. */ return 2; default: break; } break; } return -EINVAL; } static int fill_message(struct snd_rawmidi_substream *substream, u8 *buf) { struct snd_tscm *tscm = substream->rmidi->private_data; unsigned int port = substream->number; unsigned int len; unsigned int i; u8 status; int consume; buf[0] = buf[1] = buf[2] = buf[3] = 0x00; len = snd_rawmidi_transmit_peek(substream, buf + 1, 3); if (len == 0) return 0; /* On exclusive message. */ if (tscm->on_sysex[port]) { /* Seek the end of exclusives. */ for (i = 1; i < 4 || i < len; ++i) { if (buf[i] == 0xf7) { tscm->on_sysex[port] = false; break; } } /* At the end of exclusive message, use label 0x07. */ if (!tscm->on_sysex[port]) { len = i; buf[0] = (port << 4) | 0x07; /* During exclusive message, use label 0x04. */ } else if (len == 3) { buf[0] = (port << 4) | 0x04; /* We need to fill whole 3 bytes. Go to next change. */ } else { len = 0; } } else { /* The beginning of exclusives. */ if (buf[1] == 0xf0) { /* Transfer it in next chance in another condition. */ tscm->on_sysex[port] = true; return 0; } else { /* On running-status. */ if ((buf[1] & 0x80) != 0x80) status = tscm->running_status[port]; else status = buf[1]; /* Calculate consume bytes. */ consume = calculate_message_bytes(status); if (consume <= 0) return 0; /* On running-status. */ if ((buf[1] & 0x80) != 0x80) { buf[3] = buf[2]; buf[2] = buf[1]; buf[1] = tscm->running_status[port]; consume--; } else { tscm->running_status[port] = buf[1]; } /* Confirm length. */ if (len < consume) return 0; if (len > consume) len = consume; } buf[0] = (port << 4) | (buf[1] >> 4); } return len; } static void handle_midi_tx(struct fw_card *card, struct fw_request *request, int tcode, int destination, int source, int generation, unsigned long long offset, void *data, size_t length, void *callback_data) { struct snd_tscm *tscm = callback_data; u32 *buf = (u32 *)data; unsigned int messages; unsigned int i; unsigned int port; struct snd_rawmidi_substream *substream; u8 *b; int bytes; if (offset != tscm->async_handler.offset) goto end; messages = length / 8; for (i = 0; i < messages; i++) { b = (u8 *)(buf + i * 2); port = b[0] >> 4; /* TODO: support virtual MIDI ports. */ if (port > tscm->spec->midi_capture_ports) goto end; /* Assume the message length. */ bytes = calculate_message_bytes(b[1]); /* On MIDI data or exclusives. */ if (bytes <= 0) { /* Seek the end of exclusives. */ for (bytes = 1; bytes < 4; bytes++) { if (b[bytes] == 0xf7) break; } if (bytes == 4) bytes = 3; } substream = ACCESS_ONCE(tscm->tx_midi_substreams[port]); if (substream != NULL) snd_rawmidi_receive(substream, b + 1, bytes); } end: fw_send_response(card, request, RCODE_COMPLETE); } int snd_tscm_transaction_register(struct snd_tscm *tscm) { static const struct fw_address_region resp_register_region = { .start = 0xffffe0000000ull, .end = 0xffffe000ffffull, }; unsigned int i; int err; /* * Usually, two quadlets are transferred by one transaction. The first * quadlet has MIDI messages, the rest includes timestamp. * Sometimes, 8 set of the data is transferred by a block transaction. */ tscm->async_handler.length = 8 * 8; tscm->async_handler.address_callback = handle_midi_tx; tscm->async_handler.callback_data = tscm; err = fw_core_add_address_handler(&tscm->async_handler, &resp_register_region); if (err < 0) return err; err = snd_tscm_transaction_reregister(tscm); if (err < 0) goto error; for (i = 0; i < TSCM_MIDI_OUT_PORT_MAX; i++) { err = snd_fw_async_midi_port_init( &tscm->out_ports[i], tscm->unit, TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_RX_QUAD, 4, fill_message); if (err < 0) goto error; } return err; error: fw_core_remove_address_handler(&tscm->async_handler); return err; } /* At bus reset, these registers are cleared. */ int snd_tscm_transaction_reregister(struct snd_tscm *tscm) { struct fw_device *device = fw_parent_device(tscm->unit); __be32 reg; int err; /* Register messaging address. Block transaction is not allowed. */ reg = cpu_to_be32((device->card->node_id << 16) | (tscm->async_handler.offset >> 32)); err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ADDR_HI, ®, sizeof(reg), 0); if (err < 0) return err; reg = cpu_to_be32(tscm->async_handler.offset); err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ADDR_LO, ®, sizeof(reg), 0); if (err < 0) return err; /* Turn on messaging. */ reg = cpu_to_be32(0x00000001); return snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ON, ®, sizeof(reg), 0); } void snd_tscm_transaction_unregister(struct snd_tscm *tscm) { __be32 reg; unsigned int i; /* Turn off messaging. */ reg = cpu_to_be32(0x00000000); snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ON, ®, sizeof(reg), 0); /* Unregister the address. */ snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ADDR_HI, ®, sizeof(reg), 0); snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ADDR_LO, ®, sizeof(reg), 0); fw_core_remove_address_handler(&tscm->async_handler); for (i = 0; i < TSCM_MIDI_OUT_PORT_MAX; i++) snd_fw_async_midi_port_destroy(&tscm->out_ports[i]); }