summaryrefslogtreecommitdiff
path: root/sound/firewire/digi00x/digi00x-transaction.c
blob: 554324d8c602189e58efc9aebeac1707c3f5cf97 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
/*
 * digi00x-transaction.c - a part of driver for Digidesign Digi 002/003 family
 *
 * Copyright (c) 2014-2015 Takashi Sakamoto
 *
 * Licensed under the terms of the GNU General Public License, version 2.
 */

#include <sound/asound.h>
#include "digi00x.h"

static int fill_midi_message(struct snd_rawmidi_substream *substream, u8 *buf)
{
	int bytes;

	buf[0] = 0x80;
	bytes = snd_rawmidi_transmit_peek(substream, buf + 1, 2);
	if (bytes >= 0)
		buf[3] = 0xc0 | bytes;

	return bytes;
}

static void handle_midi_control(struct snd_dg00x *dg00x, __be32 *buf,
				unsigned int length)
{
	struct snd_rawmidi_substream *substream;
	unsigned int i;
	unsigned int len;
	u8 *b;

	substream = ACCESS_ONCE(dg00x->in_control);
	if (substream == NULL)
		return;

	length /= 4;

	for (i = 0; i < length; i++) {
		b = (u8 *)&buf[i];
		len = b[3] & 0xf;
		if (len > 0)
			snd_rawmidi_receive(dg00x->in_control, b + 1, len);
	}
}

static void handle_unknown_message(struct snd_dg00x *dg00x,
				   unsigned long long offset, __be32 *buf)
{
	unsigned long flags;

	spin_lock_irqsave(&dg00x->lock, flags);
	dg00x->msg = be32_to_cpu(*buf);
	spin_unlock_irqrestore(&dg00x->lock, flags);

	wake_up(&dg00x->hwdep_wait);
}

static void handle_message(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_dg00x *dg00x = callback_data;
	__be32 *buf = (__be32 *)data;

	if (offset == dg00x->async_handler.offset)
		handle_unknown_message(dg00x, offset, buf);
	else if (offset == dg00x->async_handler.offset + 4)
		handle_midi_control(dg00x, buf, length);

	fw_send_response(card, request, RCODE_COMPLETE);
}

int snd_dg00x_transaction_reregister(struct snd_dg00x *dg00x)
{
	struct fw_device *device = fw_parent_device(dg00x->unit);
	__be32 data[2];
	int err;

	/* Unknown. 4bytes. */
	data[0] = cpu_to_be32((device->card->node_id << 16) |
			      (dg00x->async_handler.offset >> 32));
	data[1] = cpu_to_be32(dg00x->async_handler.offset);
	err = snd_fw_transaction(dg00x->unit, TCODE_WRITE_BLOCK_REQUEST,
				 DG00X_ADDR_BASE + DG00X_OFFSET_MESSAGE_ADDR,
				 &data, sizeof(data), 0);
	if (err < 0)
		return err;

	/* Asynchronous transactions for MIDI control message. */
	data[0] = cpu_to_be32((device->card->node_id << 16) |
			      (dg00x->async_handler.offset >> 32));
	data[1] = cpu_to_be32(dg00x->async_handler.offset + 4);
	return snd_fw_transaction(dg00x->unit, TCODE_WRITE_BLOCK_REQUEST,
				  DG00X_ADDR_BASE + DG00X_OFFSET_MIDI_CTL_ADDR,
				  &data, sizeof(data), 0);
}

int snd_dg00x_transaction_register(struct snd_dg00x *dg00x)
{
	static const struct fw_address_region resp_register_region = {
		.start	= 0xffffe0000000ull,
		.end	= 0xffffe000ffffull,
	};
	int err;

	dg00x->async_handler.length = 12;
	dg00x->async_handler.address_callback = handle_message;
	dg00x->async_handler.callback_data = dg00x;

	err = fw_core_add_address_handler(&dg00x->async_handler,
					  &resp_register_region);
	if (err < 0)
		return err;

	err = snd_dg00x_transaction_reregister(dg00x);
	if (err < 0)
		goto error;

	err = snd_fw_async_midi_port_init(&dg00x->out_control, dg00x->unit,
					  DG00X_ADDR_BASE + DG00X_OFFSET_MMC,
					  4, fill_midi_message);
	if (err < 0)
		goto error;

	return err;
error:
	fw_core_remove_address_handler(&dg00x->async_handler);
	dg00x->async_handler.address_callback = NULL;
	return err;
}

void snd_dg00x_transaction_unregister(struct snd_dg00x *dg00x)
{
	snd_fw_async_midi_port_destroy(&dg00x->out_control);
	fw_core_remove_address_handler(&dg00x->async_handler);
}