1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * dice_transaction.c - a part of driver for Dice based devices
4 *
5 * Copyright (c) Clemens Ladisch
6 * Copyright (c) 2014 Takashi Sakamoto
7 */
8
9#include "dice.h"
10
11static u64 get_subaddr(struct snd_dice *dice, enum snd_dice_addr_type type,
12		       u64 offset)
13{
14	switch (type) {
15	case SND_DICE_ADDR_TYPE_TX:
16		offset += dice->tx_offset;
17		break;
18	case SND_DICE_ADDR_TYPE_RX:
19		offset += dice->rx_offset;
20		break;
21	case SND_DICE_ADDR_TYPE_SYNC:
22		offset += dice->sync_offset;
23		break;
24	case SND_DICE_ADDR_TYPE_RSRV:
25		offset += dice->rsrv_offset;
26		break;
27	case SND_DICE_ADDR_TYPE_GLOBAL:
28	default:
29		offset += dice->global_offset;
30		break;
31	}
32	offset += DICE_PRIVATE_SPACE;
33	return offset;
34}
35
36int snd_dice_transaction_write(struct snd_dice *dice,
37			       enum snd_dice_addr_type type,
38			       unsigned int offset, void *buf, unsigned int len)
39{
40	return snd_fw_transaction(dice->unit,
41				  (len == 4) ? TCODE_WRITE_QUADLET_REQUEST :
42					       TCODE_WRITE_BLOCK_REQUEST,
43				  get_subaddr(dice, type, offset), buf, len, 0);
44}
45
46int snd_dice_transaction_read(struct snd_dice *dice,
47			      enum snd_dice_addr_type type, unsigned int offset,
48			      void *buf, unsigned int len)
49{
50	return snd_fw_transaction(dice->unit,
51				  (len == 4) ? TCODE_READ_QUADLET_REQUEST :
52					       TCODE_READ_BLOCK_REQUEST,
53				  get_subaddr(dice, type, offset), buf, len, 0);
54}
55
56static unsigned int get_clock_info(struct snd_dice *dice, __be32 *info)
57{
58	return snd_dice_transaction_read_global(dice, GLOBAL_CLOCK_SELECT,
59						info, 4);
60}
61
62int snd_dice_transaction_get_clock_source(struct snd_dice *dice,
63					  unsigned int *source)
64{
65	__be32 info;
66	int err;
67
68	err = get_clock_info(dice, &info);
69	if (err >= 0)
70		*source = be32_to_cpu(info) & CLOCK_SOURCE_MASK;
71
72	return err;
73}
74
75int snd_dice_transaction_get_rate(struct snd_dice *dice, unsigned int *rate)
76{
77	__be32 info;
78	unsigned int index;
79	int err;
80
81	err = get_clock_info(dice, &info);
82	if (err < 0)
83		goto end;
84
85	index = (be32_to_cpu(info) & CLOCK_RATE_MASK) >> CLOCK_RATE_SHIFT;
86	if (index >= SND_DICE_RATES_COUNT) {
87		err = -ENOSYS;
88		goto end;
89	}
90
91	*rate = snd_dice_rates[index];
92end:
93	return err;
94}
95
96int snd_dice_transaction_set_enable(struct snd_dice *dice)
97{
98	__be32 value;
99	int err = 0;
100
101	if (dice->global_enabled)
102		goto end;
103
104	value = cpu_to_be32(1);
105	err = snd_fw_transaction(dice->unit, TCODE_WRITE_QUADLET_REQUEST,
106				 get_subaddr(dice, SND_DICE_ADDR_TYPE_GLOBAL,
107					     GLOBAL_ENABLE),
108				 &value, 4,
109				 FW_FIXED_GENERATION | dice->owner_generation);
110	if (err < 0)
111		goto end;
112
113	dice->global_enabled = true;
114end:
115	return err;
116}
117
118void snd_dice_transaction_clear_enable(struct snd_dice *dice)
119{
120	__be32 value;
121
122	value = 0;
123	snd_fw_transaction(dice->unit, TCODE_WRITE_QUADLET_REQUEST,
124			   get_subaddr(dice, SND_DICE_ADDR_TYPE_GLOBAL,
125				       GLOBAL_ENABLE),
126			   &value, 4, FW_QUIET |
127			   FW_FIXED_GENERATION | dice->owner_generation);
128
129	dice->global_enabled = false;
130}
131
132static void dice_notification(struct fw_card *card, struct fw_request *request,
133			      int tcode, int destination, int source,
134			      int generation, unsigned long long offset,
135			      void *data, size_t length, void *callback_data)
136{
137	struct snd_dice *dice = callback_data;
138	u32 bits;
139	unsigned long flags;
140
141	if (tcode != TCODE_WRITE_QUADLET_REQUEST) {
142		fw_send_response(card, request, RCODE_TYPE_ERROR);
143		return;
144	}
145	if ((offset & 3) != 0) {
146		fw_send_response(card, request, RCODE_ADDRESS_ERROR);
147		return;
148	}
149
150	bits = be32_to_cpup(data);
151
152	spin_lock_irqsave(&dice->lock, flags);
153	dice->notification_bits |= bits;
154	spin_unlock_irqrestore(&dice->lock, flags);
155
156	fw_send_response(card, request, RCODE_COMPLETE);
157
158	if (bits & NOTIFY_CLOCK_ACCEPTED)
159		complete(&dice->clock_accepted);
160	wake_up(&dice->hwdep_wait);
161}
162
163static int register_notification_address(struct snd_dice *dice, bool retry)
164{
165	struct fw_device *device = fw_parent_device(dice->unit);
166	__be64 *buffer;
167	unsigned int retries;
168	int err;
169
170	retries = (retry) ? 3 : 0;
171
172	buffer = kmalloc(2 * 8, GFP_KERNEL);
173	if (!buffer)
174		return -ENOMEM;
175
176	for (;;) {
177		buffer[0] = cpu_to_be64(OWNER_NO_OWNER);
178		buffer[1] = cpu_to_be64(
179			((u64)device->card->node_id << OWNER_NODE_SHIFT) |
180			dice->notification_handler.offset);
181
182		dice->owner_generation = device->generation;
183		smp_rmb(); /* node_id vs. generation */
184		err = snd_fw_transaction(dice->unit, TCODE_LOCK_COMPARE_SWAP,
185					 get_subaddr(dice,
186						     SND_DICE_ADDR_TYPE_GLOBAL,
187						     GLOBAL_OWNER),
188					 buffer, 2 * 8,
189					 FW_FIXED_GENERATION |
190							dice->owner_generation);
191		if (err == 0) {
192			/* success */
193			if (buffer[0] == cpu_to_be64(OWNER_NO_OWNER))
194				break;
195			/* The address seems to be already registered. */
196			if (buffer[0] == buffer[1])
197				break;
198
199			dev_err(&dice->unit->device,
200				"device is already in use\n");
201			err = -EBUSY;
202		}
203		if (err != -EAGAIN || retries-- > 0)
204			break;
205
206		msleep(20);
207	}
208
209	kfree(buffer);
210
211	if (err < 0)
212		dice->owner_generation = -1;
213
214	return err;
215}
216
217static void unregister_notification_address(struct snd_dice *dice)
218{
219	struct fw_device *device = fw_parent_device(dice->unit);
220	__be64 *buffer;
221
222	buffer = kmalloc(2 * 8, GFP_KERNEL);
223	if (buffer == NULL)
224		return;
225
226	buffer[0] = cpu_to_be64(
227		((u64)device->card->node_id << OWNER_NODE_SHIFT) |
228		dice->notification_handler.offset);
229	buffer[1] = cpu_to_be64(OWNER_NO_OWNER);
230	snd_fw_transaction(dice->unit, TCODE_LOCK_COMPARE_SWAP,
231			   get_subaddr(dice, SND_DICE_ADDR_TYPE_GLOBAL,
232				       GLOBAL_OWNER),
233			   buffer, 2 * 8, FW_QUIET |
234			   FW_FIXED_GENERATION | dice->owner_generation);
235
236	kfree(buffer);
237
238	dice->owner_generation = -1;
239}
240
241void snd_dice_transaction_destroy(struct snd_dice *dice)
242{
243	struct fw_address_handler *handler = &dice->notification_handler;
244
245	if (handler->callback_data == NULL)
246		return;
247
248	unregister_notification_address(dice);
249
250	fw_core_remove_address_handler(handler);
251	handler->callback_data = NULL;
252}
253
254int snd_dice_transaction_reinit(struct snd_dice *dice)
255{
256	struct fw_address_handler *handler = &dice->notification_handler;
257
258	if (handler->callback_data == NULL)
259		return -EINVAL;
260
261	return register_notification_address(dice, false);
262}
263
264static int get_subaddrs(struct snd_dice *dice)
265{
266	static const int min_values[10] = {
267		10, 0x60 / 4,
268		10, 0x18 / 4,
269		10, 0x18 / 4,
270		0, 0,
271		0, 0,
272	};
273	__be32 *pointers;
274	__be32 version;
275	u32 data;
276	unsigned int i;
277	int err;
278
279	pointers = kmalloc_array(ARRAY_SIZE(min_values), sizeof(__be32),
280				 GFP_KERNEL);
281	if (pointers == NULL)
282		return -ENOMEM;
283
284	/*
285	 * Check that the sub address spaces exist and are located inside the
286	 * private address space.  The minimum values are chosen so that all
287	 * minimally required registers are included.
288	 */
289	err = snd_fw_transaction(dice->unit, TCODE_READ_BLOCK_REQUEST,
290				 DICE_PRIVATE_SPACE, pointers,
291				 sizeof(__be32) * ARRAY_SIZE(min_values), 0);
292	if (err < 0)
293		goto end;
294
295	for (i = 0; i < ARRAY_SIZE(min_values); ++i) {
296		data = be32_to_cpu(pointers[i]);
297		if (data < min_values[i] || data >= 0x40000) {
298			err = -ENODEV;
299			goto end;
300		}
301	}
302
303	if (be32_to_cpu(pointers[1]) > 0x18) {
304		/*
305		 * Check that the implemented DICE driver specification major
306		 * version number matches.
307		 */
308		err = snd_fw_transaction(dice->unit, TCODE_READ_QUADLET_REQUEST,
309				DICE_PRIVATE_SPACE +
310				be32_to_cpu(pointers[0]) * 4 + GLOBAL_VERSION,
311				&version, sizeof(version), 0);
312		if (err < 0)
313			goto end;
314
315		if ((version & cpu_to_be32(0xff000000)) !=
316						cpu_to_be32(0x01000000)) {
317			dev_err(&dice->unit->device,
318				"unknown DICE version: 0x%08x\n",
319				be32_to_cpu(version));
320			err = -ENODEV;
321			goto end;
322		}
323
324		/* Set up later. */
325		dice->clock_caps = 1;
326	}
327
328	dice->global_offset = be32_to_cpu(pointers[0]) * 4;
329	dice->tx_offset = be32_to_cpu(pointers[2]) * 4;
330	dice->rx_offset = be32_to_cpu(pointers[4]) * 4;
331
332	/* Old firmware doesn't support these fields. */
333	if (pointers[7])
334		dice->sync_offset = be32_to_cpu(pointers[6]) * 4;
335	if (pointers[9])
336		dice->rsrv_offset = be32_to_cpu(pointers[8]) * 4;
337end:
338	kfree(pointers);
339	return err;
340}
341
342int snd_dice_transaction_init(struct snd_dice *dice)
343{
344	struct fw_address_handler *handler = &dice->notification_handler;
345	int err;
346
347	err = get_subaddrs(dice);
348	if (err < 0)
349		return err;
350
351	/* Allocation callback in address space over host controller */
352	handler->length = 4;
353	handler->address_callback = dice_notification;
354	handler->callback_data = dice;
355	err = fw_core_add_address_handler(handler, &fw_high_memory_region);
356	if (err < 0) {
357		handler->callback_data = NULL;
358		return err;
359	}
360
361	/* Register the address space */
362	err = register_notification_address(dice, true);
363	if (err < 0) {
364		fw_core_remove_address_handler(handler);
365		handler->callback_data = NULL;
366	}
367
368	return err;
369}
370