162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * tascam-transaction.c - a part of driver for TASCAM FireWire series 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) 2015 Takashi Sakamoto 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include "tascam.h" 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci/* 1162306a36Sopenharmony_ci * When return minus value, given argument is not MIDI status. 1262306a36Sopenharmony_ci * When return 0, given argument is a beginning of system exclusive. 1362306a36Sopenharmony_ci * When return the others, given argument is MIDI data. 1462306a36Sopenharmony_ci */ 1562306a36Sopenharmony_cistatic inline int calculate_message_bytes(u8 status) 1662306a36Sopenharmony_ci{ 1762306a36Sopenharmony_ci switch (status) { 1862306a36Sopenharmony_ci case 0xf6: /* Tune request. */ 1962306a36Sopenharmony_ci case 0xf8: /* Timing clock. */ 2062306a36Sopenharmony_ci case 0xfa: /* Start. */ 2162306a36Sopenharmony_ci case 0xfb: /* Continue. */ 2262306a36Sopenharmony_ci case 0xfc: /* Stop. */ 2362306a36Sopenharmony_ci case 0xfe: /* Active sensing. */ 2462306a36Sopenharmony_ci case 0xff: /* System reset. */ 2562306a36Sopenharmony_ci return 1; 2662306a36Sopenharmony_ci case 0xf1: /* MIDI time code quarter frame. */ 2762306a36Sopenharmony_ci case 0xf3: /* Song select. */ 2862306a36Sopenharmony_ci return 2; 2962306a36Sopenharmony_ci case 0xf2: /* Song position pointer. */ 3062306a36Sopenharmony_ci return 3; 3162306a36Sopenharmony_ci case 0xf0: /* Exclusive. */ 3262306a36Sopenharmony_ci return 0; 3362306a36Sopenharmony_ci case 0xf7: /* End of exclusive. */ 3462306a36Sopenharmony_ci break; 3562306a36Sopenharmony_ci case 0xf4: /* Undefined. */ 3662306a36Sopenharmony_ci case 0xf5: /* Undefined. */ 3762306a36Sopenharmony_ci case 0xf9: /* Undefined. */ 3862306a36Sopenharmony_ci case 0xfd: /* Undefined. */ 3962306a36Sopenharmony_ci break; 4062306a36Sopenharmony_ci default: 4162306a36Sopenharmony_ci switch (status & 0xf0) { 4262306a36Sopenharmony_ci case 0x80: /* Note on. */ 4362306a36Sopenharmony_ci case 0x90: /* Note off. */ 4462306a36Sopenharmony_ci case 0xa0: /* Polyphonic key pressure. */ 4562306a36Sopenharmony_ci case 0xb0: /* Control change and Mode change. */ 4662306a36Sopenharmony_ci case 0xe0: /* Pitch bend change. */ 4762306a36Sopenharmony_ci return 3; 4862306a36Sopenharmony_ci case 0xc0: /* Program change. */ 4962306a36Sopenharmony_ci case 0xd0: /* Channel pressure. */ 5062306a36Sopenharmony_ci return 2; 5162306a36Sopenharmony_ci default: 5262306a36Sopenharmony_ci break; 5362306a36Sopenharmony_ci } 5462306a36Sopenharmony_ci break; 5562306a36Sopenharmony_ci } 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci return -EINVAL; 5862306a36Sopenharmony_ci} 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_cistatic int fill_message(struct snd_fw_async_midi_port *port, 6162306a36Sopenharmony_ci struct snd_rawmidi_substream *substream) 6262306a36Sopenharmony_ci{ 6362306a36Sopenharmony_ci int i, len, consume; 6462306a36Sopenharmony_ci u8 *label, *msg; 6562306a36Sopenharmony_ci u8 status; 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci /* The first byte is used for label, the rest for MIDI bytes. */ 6862306a36Sopenharmony_ci label = port->buf; 6962306a36Sopenharmony_ci msg = port->buf + 1; 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci consume = snd_rawmidi_transmit_peek(substream, msg, 3); 7262306a36Sopenharmony_ci if (consume == 0) 7362306a36Sopenharmony_ci return 0; 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci /* On exclusive message. */ 7662306a36Sopenharmony_ci if (port->on_sysex) { 7762306a36Sopenharmony_ci /* Seek the end of exclusives. */ 7862306a36Sopenharmony_ci for (i = 0; i < consume; ++i) { 7962306a36Sopenharmony_ci if (msg[i] == 0xf7) { 8062306a36Sopenharmony_ci port->on_sysex = false; 8162306a36Sopenharmony_ci break; 8262306a36Sopenharmony_ci } 8362306a36Sopenharmony_ci } 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci /* At the end of exclusive message, use label 0x07. */ 8662306a36Sopenharmony_ci if (!port->on_sysex) { 8762306a36Sopenharmony_ci consume = i + 1; 8862306a36Sopenharmony_ci *label = (substream->number << 4) | 0x07; 8962306a36Sopenharmony_ci /* During exclusive message, use label 0x04. */ 9062306a36Sopenharmony_ci } else if (consume == 3) { 9162306a36Sopenharmony_ci *label = (substream->number << 4) | 0x04; 9262306a36Sopenharmony_ci /* We need to fill whole 3 bytes. Go to next change. */ 9362306a36Sopenharmony_ci } else { 9462306a36Sopenharmony_ci return 0; 9562306a36Sopenharmony_ci } 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci len = consume; 9862306a36Sopenharmony_ci } else { 9962306a36Sopenharmony_ci /* The beginning of exclusives. */ 10062306a36Sopenharmony_ci if (msg[0] == 0xf0) { 10162306a36Sopenharmony_ci /* Transfer it in next chance in another condition. */ 10262306a36Sopenharmony_ci port->on_sysex = true; 10362306a36Sopenharmony_ci return 0; 10462306a36Sopenharmony_ci } else { 10562306a36Sopenharmony_ci /* On running-status. */ 10662306a36Sopenharmony_ci if ((msg[0] & 0x80) != 0x80) 10762306a36Sopenharmony_ci status = port->running_status; 10862306a36Sopenharmony_ci else 10962306a36Sopenharmony_ci status = msg[0]; 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci /* Calculate consume bytes. */ 11262306a36Sopenharmony_ci len = calculate_message_bytes(status); 11362306a36Sopenharmony_ci if (len <= 0) 11462306a36Sopenharmony_ci return 0; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci /* On running-status. */ 11762306a36Sopenharmony_ci if ((msg[0] & 0x80) != 0x80) { 11862306a36Sopenharmony_ci /* Enough MIDI bytes were not retrieved. */ 11962306a36Sopenharmony_ci if (consume < len - 1) 12062306a36Sopenharmony_ci return 0; 12162306a36Sopenharmony_ci consume = len - 1; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci msg[2] = msg[1]; 12462306a36Sopenharmony_ci msg[1] = msg[0]; 12562306a36Sopenharmony_ci msg[0] = port->running_status; 12662306a36Sopenharmony_ci } else { 12762306a36Sopenharmony_ci /* Enough MIDI bytes were not retrieved. */ 12862306a36Sopenharmony_ci if (consume < len) 12962306a36Sopenharmony_ci return 0; 13062306a36Sopenharmony_ci consume = len; 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci port->running_status = msg[0]; 13362306a36Sopenharmony_ci } 13462306a36Sopenharmony_ci } 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci *label = (substream->number << 4) | (msg[0] >> 4); 13762306a36Sopenharmony_ci } 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci if (len > 0 && len < 3) 14062306a36Sopenharmony_ci memset(msg + len, 0, 3 - len); 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci return consume; 14362306a36Sopenharmony_ci} 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_cistatic void async_midi_port_callback(struct fw_card *card, int rcode, 14662306a36Sopenharmony_ci void *data, size_t length, 14762306a36Sopenharmony_ci void *callback_data) 14862306a36Sopenharmony_ci{ 14962306a36Sopenharmony_ci struct snd_fw_async_midi_port *port = callback_data; 15062306a36Sopenharmony_ci struct snd_rawmidi_substream *substream = READ_ONCE(port->substream); 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci /* This port is closed. */ 15362306a36Sopenharmony_ci if (substream == NULL) 15462306a36Sopenharmony_ci return; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci if (rcode == RCODE_COMPLETE) 15762306a36Sopenharmony_ci snd_rawmidi_transmit_ack(substream, port->consume_bytes); 15862306a36Sopenharmony_ci else if (!rcode_is_permanent_error(rcode)) 15962306a36Sopenharmony_ci /* To start next transaction immediately for recovery. */ 16062306a36Sopenharmony_ci port->next_ktime = 0; 16162306a36Sopenharmony_ci else 16262306a36Sopenharmony_ci /* Don't continue processing. */ 16362306a36Sopenharmony_ci port->error = true; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci port->idling = true; 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci if (!snd_rawmidi_transmit_empty(substream)) 16862306a36Sopenharmony_ci schedule_work(&port->work); 16962306a36Sopenharmony_ci} 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_cistatic void midi_port_work(struct work_struct *work) 17262306a36Sopenharmony_ci{ 17362306a36Sopenharmony_ci struct snd_fw_async_midi_port *port = 17462306a36Sopenharmony_ci container_of(work, struct snd_fw_async_midi_port, work); 17562306a36Sopenharmony_ci struct snd_rawmidi_substream *substream = READ_ONCE(port->substream); 17662306a36Sopenharmony_ci int generation; 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci /* Under transacting or error state. */ 17962306a36Sopenharmony_ci if (!port->idling || port->error) 18062306a36Sopenharmony_ci return; 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci /* Nothing to do. */ 18362306a36Sopenharmony_ci if (substream == NULL || snd_rawmidi_transmit_empty(substream)) 18462306a36Sopenharmony_ci return; 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci /* Do it in next chance. */ 18762306a36Sopenharmony_ci if (ktime_after(port->next_ktime, ktime_get())) { 18862306a36Sopenharmony_ci schedule_work(&port->work); 18962306a36Sopenharmony_ci return; 19062306a36Sopenharmony_ci } 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci /* 19362306a36Sopenharmony_ci * Fill the buffer. The callee must use snd_rawmidi_transmit_peek(). 19462306a36Sopenharmony_ci * Later, snd_rawmidi_transmit_ack() is called. 19562306a36Sopenharmony_ci */ 19662306a36Sopenharmony_ci memset(port->buf, 0, 4); 19762306a36Sopenharmony_ci port->consume_bytes = fill_message(port, substream); 19862306a36Sopenharmony_ci if (port->consume_bytes <= 0) { 19962306a36Sopenharmony_ci /* Do it in next chance, immediately. */ 20062306a36Sopenharmony_ci if (port->consume_bytes == 0) { 20162306a36Sopenharmony_ci port->next_ktime = 0; 20262306a36Sopenharmony_ci schedule_work(&port->work); 20362306a36Sopenharmony_ci } else { 20462306a36Sopenharmony_ci /* Fatal error. */ 20562306a36Sopenharmony_ci port->error = true; 20662306a36Sopenharmony_ci } 20762306a36Sopenharmony_ci return; 20862306a36Sopenharmony_ci } 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci /* Set interval to next transaction. */ 21162306a36Sopenharmony_ci port->next_ktime = ktime_add_ns(ktime_get(), 21262306a36Sopenharmony_ci port->consume_bytes * 8 * (NSEC_PER_SEC / 31250)); 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci /* Start this transaction. */ 21562306a36Sopenharmony_ci port->idling = false; 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci /* 21862306a36Sopenharmony_ci * In Linux FireWire core, when generation is updated with memory 21962306a36Sopenharmony_ci * barrier, node id has already been updated. In this module, After 22062306a36Sopenharmony_ci * this smp_rmb(), load/store instructions to memory are completed. 22162306a36Sopenharmony_ci * Thus, both of generation and node id are available with recent 22262306a36Sopenharmony_ci * values. This is a light-serialization solution to handle bus reset 22362306a36Sopenharmony_ci * events on IEEE 1394 bus. 22462306a36Sopenharmony_ci */ 22562306a36Sopenharmony_ci generation = port->parent->generation; 22662306a36Sopenharmony_ci smp_rmb(); 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci fw_send_request(port->parent->card, &port->transaction, 22962306a36Sopenharmony_ci TCODE_WRITE_QUADLET_REQUEST, 23062306a36Sopenharmony_ci port->parent->node_id, generation, 23162306a36Sopenharmony_ci port->parent->max_speed, 23262306a36Sopenharmony_ci TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_RX_QUAD, 23362306a36Sopenharmony_ci port->buf, 4, async_midi_port_callback, 23462306a36Sopenharmony_ci port); 23562306a36Sopenharmony_ci} 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_civoid snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port) 23862306a36Sopenharmony_ci{ 23962306a36Sopenharmony_ci port->idling = true; 24062306a36Sopenharmony_ci port->error = false; 24162306a36Sopenharmony_ci port->running_status = 0; 24262306a36Sopenharmony_ci port->on_sysex = false; 24362306a36Sopenharmony_ci} 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_cistatic void handle_midi_tx(struct fw_card *card, struct fw_request *request, 24662306a36Sopenharmony_ci int tcode, int destination, int source, 24762306a36Sopenharmony_ci int generation, unsigned long long offset, 24862306a36Sopenharmony_ci void *data, size_t length, void *callback_data) 24962306a36Sopenharmony_ci{ 25062306a36Sopenharmony_ci struct snd_tscm *tscm = callback_data; 25162306a36Sopenharmony_ci u32 *buf = (u32 *)data; 25262306a36Sopenharmony_ci unsigned int messages; 25362306a36Sopenharmony_ci unsigned int i; 25462306a36Sopenharmony_ci unsigned int port; 25562306a36Sopenharmony_ci struct snd_rawmidi_substream *substream; 25662306a36Sopenharmony_ci u8 *b; 25762306a36Sopenharmony_ci int bytes; 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci if (offset != tscm->async_handler.offset) 26062306a36Sopenharmony_ci goto end; 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci messages = length / 8; 26362306a36Sopenharmony_ci for (i = 0; i < messages; i++) { 26462306a36Sopenharmony_ci b = (u8 *)(buf + i * 2); 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci port = b[0] >> 4; 26762306a36Sopenharmony_ci /* TODO: support virtual MIDI ports. */ 26862306a36Sopenharmony_ci if (port >= tscm->spec->midi_capture_ports) 26962306a36Sopenharmony_ci goto end; 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci /* Assume the message length. */ 27262306a36Sopenharmony_ci bytes = calculate_message_bytes(b[1]); 27362306a36Sopenharmony_ci /* On MIDI data or exclusives. */ 27462306a36Sopenharmony_ci if (bytes <= 0) { 27562306a36Sopenharmony_ci /* Seek the end of exclusives. */ 27662306a36Sopenharmony_ci for (bytes = 1; bytes < 4; bytes++) { 27762306a36Sopenharmony_ci if (b[bytes] == 0xf7) 27862306a36Sopenharmony_ci break; 27962306a36Sopenharmony_ci } 28062306a36Sopenharmony_ci if (bytes == 4) 28162306a36Sopenharmony_ci bytes = 3; 28262306a36Sopenharmony_ci } 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_ci substream = READ_ONCE(tscm->tx_midi_substreams[port]); 28562306a36Sopenharmony_ci if (substream != NULL) 28662306a36Sopenharmony_ci snd_rawmidi_receive(substream, b + 1, bytes); 28762306a36Sopenharmony_ci } 28862306a36Sopenharmony_ciend: 28962306a36Sopenharmony_ci fw_send_response(card, request, RCODE_COMPLETE); 29062306a36Sopenharmony_ci} 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ciint snd_tscm_transaction_register(struct snd_tscm *tscm) 29362306a36Sopenharmony_ci{ 29462306a36Sopenharmony_ci static const struct fw_address_region resp_register_region = { 29562306a36Sopenharmony_ci .start = 0xffffe0000000ull, 29662306a36Sopenharmony_ci .end = 0xffffe000ffffull, 29762306a36Sopenharmony_ci }; 29862306a36Sopenharmony_ci unsigned int i; 29962306a36Sopenharmony_ci int err; 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_ci /* 30262306a36Sopenharmony_ci * Usually, two quadlets are transferred by one transaction. The first 30362306a36Sopenharmony_ci * quadlet has MIDI messages, the rest includes timestamp. 30462306a36Sopenharmony_ci * Sometimes, 8 set of the data is transferred by a block transaction. 30562306a36Sopenharmony_ci */ 30662306a36Sopenharmony_ci tscm->async_handler.length = 8 * 8; 30762306a36Sopenharmony_ci tscm->async_handler.address_callback = handle_midi_tx; 30862306a36Sopenharmony_ci tscm->async_handler.callback_data = tscm; 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_ci err = fw_core_add_address_handler(&tscm->async_handler, 31162306a36Sopenharmony_ci &resp_register_region); 31262306a36Sopenharmony_ci if (err < 0) 31362306a36Sopenharmony_ci return err; 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci err = snd_tscm_transaction_reregister(tscm); 31662306a36Sopenharmony_ci if (err < 0) 31762306a36Sopenharmony_ci goto error; 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_ci for (i = 0; i < TSCM_MIDI_OUT_PORT_MAX; i++) { 32062306a36Sopenharmony_ci tscm->out_ports[i].parent = fw_parent_device(tscm->unit); 32162306a36Sopenharmony_ci tscm->out_ports[i].next_ktime = 0; 32262306a36Sopenharmony_ci INIT_WORK(&tscm->out_ports[i].work, midi_port_work); 32362306a36Sopenharmony_ci } 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ci return err; 32662306a36Sopenharmony_cierror: 32762306a36Sopenharmony_ci fw_core_remove_address_handler(&tscm->async_handler); 32862306a36Sopenharmony_ci tscm->async_handler.callback_data = NULL; 32962306a36Sopenharmony_ci return err; 33062306a36Sopenharmony_ci} 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_ci/* At bus reset, these registers are cleared. */ 33362306a36Sopenharmony_ciint snd_tscm_transaction_reregister(struct snd_tscm *tscm) 33462306a36Sopenharmony_ci{ 33562306a36Sopenharmony_ci struct fw_device *device = fw_parent_device(tscm->unit); 33662306a36Sopenharmony_ci __be32 reg; 33762306a36Sopenharmony_ci int err; 33862306a36Sopenharmony_ci 33962306a36Sopenharmony_ci /* Register messaging address. Block transaction is not allowed. */ 34062306a36Sopenharmony_ci reg = cpu_to_be32((device->card->node_id << 16) | 34162306a36Sopenharmony_ci (tscm->async_handler.offset >> 32)); 34262306a36Sopenharmony_ci err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, 34362306a36Sopenharmony_ci TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ADDR_HI, 34462306a36Sopenharmony_ci ®, sizeof(reg), 0); 34562306a36Sopenharmony_ci if (err < 0) 34662306a36Sopenharmony_ci return err; 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_ci reg = cpu_to_be32(tscm->async_handler.offset); 34962306a36Sopenharmony_ci err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, 35062306a36Sopenharmony_ci TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ADDR_LO, 35162306a36Sopenharmony_ci ®, sizeof(reg), 0); 35262306a36Sopenharmony_ci if (err < 0) 35362306a36Sopenharmony_ci return err; 35462306a36Sopenharmony_ci 35562306a36Sopenharmony_ci /* Turn on messaging. */ 35662306a36Sopenharmony_ci reg = cpu_to_be32(0x00000001); 35762306a36Sopenharmony_ci err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, 35862306a36Sopenharmony_ci TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ON, 35962306a36Sopenharmony_ci ®, sizeof(reg), 0); 36062306a36Sopenharmony_ci if (err < 0) 36162306a36Sopenharmony_ci return err; 36262306a36Sopenharmony_ci 36362306a36Sopenharmony_ci /* Turn on FireWire LED. */ 36462306a36Sopenharmony_ci reg = cpu_to_be32(0x0001008e); 36562306a36Sopenharmony_ci return snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, 36662306a36Sopenharmony_ci TSCM_ADDR_BASE + TSCM_OFFSET_LED_POWER, 36762306a36Sopenharmony_ci ®, sizeof(reg), 0); 36862306a36Sopenharmony_ci} 36962306a36Sopenharmony_ci 37062306a36Sopenharmony_civoid snd_tscm_transaction_unregister(struct snd_tscm *tscm) 37162306a36Sopenharmony_ci{ 37262306a36Sopenharmony_ci __be32 reg; 37362306a36Sopenharmony_ci 37462306a36Sopenharmony_ci if (tscm->async_handler.callback_data == NULL) 37562306a36Sopenharmony_ci return; 37662306a36Sopenharmony_ci 37762306a36Sopenharmony_ci /* Turn off FireWire LED. */ 37862306a36Sopenharmony_ci reg = cpu_to_be32(0x0000008e); 37962306a36Sopenharmony_ci snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, 38062306a36Sopenharmony_ci TSCM_ADDR_BASE + TSCM_OFFSET_LED_POWER, 38162306a36Sopenharmony_ci ®, sizeof(reg), 0); 38262306a36Sopenharmony_ci 38362306a36Sopenharmony_ci /* Turn off messaging. */ 38462306a36Sopenharmony_ci reg = cpu_to_be32(0x00000000); 38562306a36Sopenharmony_ci snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, 38662306a36Sopenharmony_ci TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ON, 38762306a36Sopenharmony_ci ®, sizeof(reg), 0); 38862306a36Sopenharmony_ci 38962306a36Sopenharmony_ci /* Unregister the address. */ 39062306a36Sopenharmony_ci snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, 39162306a36Sopenharmony_ci TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ADDR_HI, 39262306a36Sopenharmony_ci ®, sizeof(reg), 0); 39362306a36Sopenharmony_ci snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, 39462306a36Sopenharmony_ci TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ADDR_LO, 39562306a36Sopenharmony_ci ®, sizeof(reg), 0); 39662306a36Sopenharmony_ci 39762306a36Sopenharmony_ci fw_core_remove_address_handler(&tscm->async_handler); 39862306a36Sopenharmony_ci tscm->async_handler.callback_data = NULL; 39962306a36Sopenharmony_ci} 400