brintos

brintos / linux-shallow public Read only

0
0
Text · 10.2 KiB · a073cec Raw
400 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * tascam-transaction.c - a part of driver for TASCAM FireWire series4 *5 * Copyright (c) 2015 Takashi Sakamoto6 */7 8#include "tascam.h"9 10/*11 * When return minus value, given argument is not MIDI status.12 * When return 0, given argument is a beginning of system exclusive.13 * When return the others, given argument is MIDI data.14 */15static inline int calculate_message_bytes(u8 status)16{17	switch (status) {18	case 0xf6:	/* Tune request. */19	case 0xf8:	/* Timing clock. */20	case 0xfa:	/* Start. */21	case 0xfb:	/* Continue. */22	case 0xfc:	/* Stop. */23	case 0xfe:	/* Active sensing. */24	case 0xff:	/* System reset. */25		return 1;26	case 0xf1:	/* MIDI time code quarter frame. */27	case 0xf3:	/* Song select. */28		return 2;29	case 0xf2:	/* Song position pointer. */30		return 3;31	case 0xf0:	/* Exclusive. */32		return 0;33	case 0xf7:	/* End of exclusive. */34		break;35	case 0xf4:	/* Undefined. */36	case 0xf5:	/* Undefined. */37	case 0xf9:	/* Undefined. */38	case 0xfd:	/* Undefined. */39		break;40	default:41		switch (status & 0xf0) {42		case 0x80:	/* Note on. */43		case 0x90:	/* Note off. */44		case 0xa0:	/* Polyphonic key pressure. */45		case 0xb0:	/* Control change and Mode change. */46		case 0xe0:	/* Pitch bend change. */47			return 3;48		case 0xc0:	/* Program change. */49		case 0xd0:	/* Channel pressure. */50			return 2;51		default:52		break;53		}54	break;55	}56 57	return -EINVAL;58}59 60static int fill_message(struct snd_fw_async_midi_port *port,61			struct snd_rawmidi_substream *substream)62{63	int i, len, consume;64	u8 *label, *msg;65	u8 status;66 67	/* The first byte is used for label, the rest for MIDI bytes. */68	label = port->buf;69	msg = port->buf + 1;70 71	consume = snd_rawmidi_transmit_peek(substream, msg, 3);72	if (consume == 0)73		return 0;74 75	/* On exclusive message. */76	if (port->on_sysex) {77		/* Seek the end of exclusives. */78		for (i = 0; i < consume; ++i) {79			if (msg[i] == 0xf7) {80				port->on_sysex = false;81				break;82			}83		}84 85		/* At the end of exclusive message, use label 0x07. */86		if (!port->on_sysex) {87			consume = i + 1;88			*label = (substream->number << 4) | 0x07;89		/* During exclusive message, use label 0x04. */90		} else if (consume == 3) {91			*label = (substream->number << 4) | 0x04;92		/* We need to fill whole 3 bytes. Go to next change. */93		} else {94			return 0;95		}96 97		len = consume;98	} else {99		/* The beginning of exclusives. */100		if (msg[0] == 0xf0) {101			/* Transfer it in next chance in another condition. */102			port->on_sysex = true;103			return 0;104		} else {105			/* On running-status. */106			if ((msg[0] & 0x80) != 0x80)107				status = port->running_status;108			else109				status = msg[0];110 111			/* Calculate consume bytes. */112			len = calculate_message_bytes(status);113			if (len <= 0)114				return 0;115 116			/* On running-status. */117			if ((msg[0] & 0x80) != 0x80) {118				/* Enough MIDI bytes were not retrieved. */119				if (consume < len - 1)120					return 0;121				consume = len - 1;122 123				msg[2] = msg[1];124				msg[1] = msg[0];125				msg[0] = port->running_status;126			} else {127				/* Enough MIDI bytes were not retrieved. */128				if (consume < len)129					return 0;130				consume = len;131 132				port->running_status = msg[0];133			}134		}135 136		*label = (substream->number << 4) | (msg[0] >> 4);137	}138 139	if (len > 0 && len < 3)140		memset(msg + len, 0, 3 - len);141 142	return consume;143}144 145static void async_midi_port_callback(struct fw_card *card, int rcode,146				     void *data, size_t length,147				     void *callback_data)148{149	struct snd_fw_async_midi_port *port = callback_data;150	struct snd_rawmidi_substream *substream = READ_ONCE(port->substream);151 152	/* This port is closed. */153	if (substream == NULL)154		return;155 156	if (rcode == RCODE_COMPLETE)157		snd_rawmidi_transmit_ack(substream, port->consume_bytes);158	else if (!rcode_is_permanent_error(rcode))159		/* To start next transaction immediately for recovery. */160		port->next_ktime = 0;161	else162		/* Don't continue processing. */163		port->error = true;164 165	port->idling = true;166 167	if (!snd_rawmidi_transmit_empty(substream))168		schedule_work(&port->work);169}170 171static void midi_port_work(struct work_struct *work)172{173	struct snd_fw_async_midi_port *port =174			container_of(work, struct snd_fw_async_midi_port, work);175	struct snd_rawmidi_substream *substream = READ_ONCE(port->substream);176	int generation;177 178	/* Under transacting or error state. */179	if (!port->idling || port->error)180		return;181 182	/* Nothing to do. */183	if (substream == NULL || snd_rawmidi_transmit_empty(substream))184		return;185 186	/* Do it in next chance. */187	if (ktime_after(port->next_ktime, ktime_get())) {188		schedule_work(&port->work);189		return;190	}191 192	/*193	 * Fill the buffer. The callee must use snd_rawmidi_transmit_peek().194	 * Later, snd_rawmidi_transmit_ack() is called.195	 */196	memset(port->buf, 0, 4);197	port->consume_bytes = fill_message(port, substream);198	if (port->consume_bytes <= 0) {199		/* Do it in next chance, immediately. */200		if (port->consume_bytes == 0) {201			port->next_ktime = 0;202			schedule_work(&port->work);203		} else {204			/* Fatal error. */205			port->error = true;206		}207		return;208	}209 210	/* Set interval to next transaction. */211	port->next_ktime = ktime_add_ns(ktime_get(),212			port->consume_bytes * 8 * (NSEC_PER_SEC / 31250));213 214	/* Start this transaction. */215	port->idling = false;216 217	/*218	 * In Linux FireWire core, when generation is updated with memory219	 * barrier, node id has already been updated. In this module, After220	 * this smp_rmb(), load/store instructions to memory are completed.221	 * Thus, both of generation and node id are available with recent222	 * values. This is a light-serialization solution to handle bus reset223	 * events on IEEE 1394 bus.224	 */225	generation = port->parent->generation;226	smp_rmb();227 228	fw_send_request(port->parent->card, &port->transaction,229			TCODE_WRITE_QUADLET_REQUEST,230			port->parent->node_id, generation,231			port->parent->max_speed,232			TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_RX_QUAD,233			port->buf, 4, async_midi_port_callback,234			port);235}236 237void snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port)238{239	port->idling = true;240	port->error = false;241	port->running_status = 0;242	port->on_sysex = false;243}244 245static void handle_midi_tx(struct fw_card *card, struct fw_request *request,246			   int tcode, int destination, int source,247			   int generation, unsigned long long offset,248			   void *data, size_t length, void *callback_data)249{250	struct snd_tscm *tscm = callback_data;251	u32 *buf = (u32 *)data;252	unsigned int messages;253	unsigned int i;254	unsigned int port;255	struct snd_rawmidi_substream *substream;256	u8 *b;257	int bytes;258 259	if (offset != tscm->async_handler.offset)260		goto end;261 262	messages = length / 8;263	for (i = 0; i < messages; i++) {264		b = (u8 *)(buf + i * 2);265 266		port = b[0] >> 4;267		/* TODO: support virtual MIDI ports. */268		if (port >= tscm->spec->midi_capture_ports)269			goto end;270 271		/* Assume the message length. */272		bytes = calculate_message_bytes(b[1]);273		/* On MIDI data or exclusives. */274		if (bytes <= 0) {275			/* Seek the end of exclusives. */276			for (bytes = 1; bytes < 4; bytes++) {277				if (b[bytes] == 0xf7)278					break;279			}280			if (bytes == 4)281				bytes = 3;282		}283 284		substream = READ_ONCE(tscm->tx_midi_substreams[port]);285		if (substream != NULL)286			snd_rawmidi_receive(substream, b + 1, bytes);287	}288end:289	fw_send_response(card, request, RCODE_COMPLETE);290}291 292int snd_tscm_transaction_register(struct snd_tscm *tscm)293{294	static const struct fw_address_region resp_register_region = {295		.start	= 0xffffe0000000ull,296		.end	= 0xffffe000ffffull,297	};298	unsigned int i;299	int err;300 301	/*302	 * Usually, two quadlets are transferred by one transaction. The first303	 * quadlet has MIDI messages, the rest includes timestamp.304	 * Sometimes, 8 set of the data is transferred by a block transaction.305	 */306	tscm->async_handler.length = 8 * 8;307	tscm->async_handler.address_callback = handle_midi_tx;308	tscm->async_handler.callback_data = tscm;309 310	err = fw_core_add_address_handler(&tscm->async_handler,311					  &resp_register_region);312	if (err < 0)313		return err;314 315	err = snd_tscm_transaction_reregister(tscm);316	if (err < 0)317		goto error;318 319	for (i = 0; i < TSCM_MIDI_OUT_PORT_MAX; i++) {320		tscm->out_ports[i].parent = fw_parent_device(tscm->unit);321		tscm->out_ports[i].next_ktime = 0;322		INIT_WORK(&tscm->out_ports[i].work, midi_port_work);323	}324 325	return err;326error:327	fw_core_remove_address_handler(&tscm->async_handler);328	tscm->async_handler.callback_data = NULL;329	return err;330}331 332/* At bus reset, these registers are cleared. */333int snd_tscm_transaction_reregister(struct snd_tscm *tscm)334{335	struct fw_device *device = fw_parent_device(tscm->unit);336	__be32 reg;337	int err;338 339	/* Register messaging address. Block transaction is not allowed. */340	reg = cpu_to_be32((device->card->node_id << 16) |341			  (tscm->async_handler.offset >> 32));342	err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,343				 TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ADDR_HI,344				 &reg, sizeof(reg), 0);345	if (err < 0)346		return err;347 348	reg = cpu_to_be32(tscm->async_handler.offset);349	err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,350				 TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ADDR_LO,351				 &reg, sizeof(reg), 0);352	if (err < 0)353		return err;354 355	/* Turn on messaging. */356	reg = cpu_to_be32(0x00000001);357	err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,358				  TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ON,359				  &reg, sizeof(reg), 0);360	if (err < 0)361		return err;362 363	/* Turn on FireWire LED. */364	reg = cpu_to_be32(0x0001008e);365	return snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,366				  TSCM_ADDR_BASE + TSCM_OFFSET_LED_POWER,367				  &reg, sizeof(reg), 0);368}369 370void snd_tscm_transaction_unregister(struct snd_tscm *tscm)371{372	__be32 reg;373 374	if (tscm->async_handler.callback_data == NULL)375		return;376 377	/* Turn off FireWire LED. */378	reg = cpu_to_be32(0x0000008e);379	snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,380			   TSCM_ADDR_BASE + TSCM_OFFSET_LED_POWER,381			   &reg, sizeof(reg), 0);382 383	/* Turn off messaging. */384	reg = cpu_to_be32(0x00000000);385	snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,386			   TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ON,387			   &reg, sizeof(reg), 0);388 389	/* Unregister the address. */390	snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,391			   TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ADDR_HI,392			   &reg, sizeof(reg), 0);393	snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,394			   TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ADDR_LO,395			   &reg, sizeof(reg), 0);396 397	fw_core_remove_address_handler(&tscm->async_handler);398	tscm->async_handler.callback_data = NULL;399}400