2 * dice_transaction.c - a part of driver for Dice based devices
4 * Copyright (c) Clemens Ladisch
5 * Copyright (c) 2014 Takashi Sakamoto
7 * Licensed under the terms of the GNU General Public License, version 2.
12 #define NOTIFICATION_TIMEOUT_MS (2 * MSEC_PER_SEC)
14 static u64 get_subaddr(struct snd_dice *dice, enum snd_dice_addr_type type,
18 case SND_DICE_ADDR_TYPE_TX:
19 offset += dice->tx_offset;
21 case SND_DICE_ADDR_TYPE_RX:
22 offset += dice->rx_offset;
24 case SND_DICE_ADDR_TYPE_SYNC:
25 offset += dice->sync_offset;
27 case SND_DICE_ADDR_TYPE_RSRV:
28 offset += dice->rsrv_offset;
30 case SND_DICE_ADDR_TYPE_GLOBAL:
32 offset += dice->global_offset;
35 offset += DICE_PRIVATE_SPACE;
39 int snd_dice_transaction_write(struct snd_dice *dice,
40 enum snd_dice_addr_type type,
41 unsigned int offset, void *buf, unsigned int len)
43 return snd_fw_transaction(dice->unit,
44 (len == 4) ? TCODE_WRITE_QUADLET_REQUEST :
45 TCODE_WRITE_BLOCK_REQUEST,
46 get_subaddr(dice, type, offset), buf, len, 0);
49 int snd_dice_transaction_read(struct snd_dice *dice,
50 enum snd_dice_addr_type type, unsigned int offset,
51 void *buf, unsigned int len)
53 return snd_fw_transaction(dice->unit,
54 (len == 4) ? TCODE_READ_QUADLET_REQUEST :
55 TCODE_READ_BLOCK_REQUEST,
56 get_subaddr(dice, type, offset), buf, len, 0);
59 static unsigned int get_clock_info(struct snd_dice *dice, __be32 *info)
61 return snd_dice_transaction_read_global(dice, GLOBAL_CLOCK_SELECT,
65 static int set_clock_info(struct snd_dice *dice,
66 unsigned int rate, unsigned int source)
74 err = get_clock_info(dice, &info);
78 clock = be32_to_cpu(info);
79 if (source != UINT_MAX) {
80 mask = CLOCK_SOURCE_MASK;
84 if (rate != UINT_MAX) {
85 for (i = 0; i < ARRAY_SIZE(snd_dice_rates); i++) {
86 if (snd_dice_rates[i] == rate)
89 if (i == ARRAY_SIZE(snd_dice_rates))
92 mask = CLOCK_RATE_MASK;
94 clock |= i << CLOCK_RATE_SHIFT;
96 info = cpu_to_be32(clock);
98 if (completion_done(&dice->clock_accepted))
99 reinit_completion(&dice->clock_accepted);
101 err = snd_dice_transaction_write_global(dice, GLOBAL_CLOCK_SELECT,
106 if (wait_for_completion_timeout(&dice->clock_accepted,
107 msecs_to_jiffies(NOTIFICATION_TIMEOUT_MS)) == 0)
113 int snd_dice_transaction_get_clock_source(struct snd_dice *dice,
114 unsigned int *source)
119 err = get_clock_info(dice, &info);
121 *source = be32_to_cpu(info) & CLOCK_SOURCE_MASK;
126 int snd_dice_transaction_get_rate(struct snd_dice *dice, unsigned int *rate)
132 err = get_clock_info(dice, &info);
136 index = (be32_to_cpu(info) & CLOCK_RATE_MASK) >> CLOCK_RATE_SHIFT;
137 if (index >= SND_DICE_RATES_COUNT) {
142 *rate = snd_dice_rates[index];
146 int snd_dice_transaction_set_rate(struct snd_dice *dice, unsigned int rate)
148 return set_clock_info(dice, rate, UINT_MAX);
151 int snd_dice_transaction_set_enable(struct snd_dice *dice)
156 if (dice->global_enabled)
159 value = cpu_to_be32(1);
160 err = snd_fw_transaction(dice->unit, TCODE_WRITE_QUADLET_REQUEST,
161 get_subaddr(dice, SND_DICE_ADDR_TYPE_GLOBAL,
164 FW_FIXED_GENERATION | dice->owner_generation);
168 dice->global_enabled = true;
173 void snd_dice_transaction_clear_enable(struct snd_dice *dice)
178 snd_fw_transaction(dice->unit, TCODE_WRITE_QUADLET_REQUEST,
179 get_subaddr(dice, SND_DICE_ADDR_TYPE_GLOBAL,
181 &value, 4, FW_QUIET |
182 FW_FIXED_GENERATION | dice->owner_generation);
184 dice->global_enabled = false;
187 static void dice_notification(struct fw_card *card, struct fw_request *request,
188 int tcode, int destination, int source,
189 int generation, unsigned long long offset,
190 void *data, size_t length, void *callback_data)
192 struct snd_dice *dice = callback_data;
196 if (tcode != TCODE_WRITE_QUADLET_REQUEST) {
197 fw_send_response(card, request, RCODE_TYPE_ERROR);
200 if ((offset & 3) != 0) {
201 fw_send_response(card, request, RCODE_ADDRESS_ERROR);
205 bits = be32_to_cpup(data);
207 spin_lock_irqsave(&dice->lock, flags);
208 dice->notification_bits |= bits;
209 spin_unlock_irqrestore(&dice->lock, flags);
211 fw_send_response(card, request, RCODE_COMPLETE);
213 if (bits & NOTIFY_CLOCK_ACCEPTED)
214 complete(&dice->clock_accepted);
215 wake_up(&dice->hwdep_wait);
218 static int register_notification_address(struct snd_dice *dice, bool retry)
220 struct fw_device *device = fw_parent_device(dice->unit);
222 unsigned int retries;
225 retries = (retry) ? 3 : 0;
227 buffer = kmalloc(2 * 8, GFP_KERNEL);
232 buffer[0] = cpu_to_be64(OWNER_NO_OWNER);
233 buffer[1] = cpu_to_be64(
234 ((u64)device->card->node_id << OWNER_NODE_SHIFT) |
235 dice->notification_handler.offset);
237 dice->owner_generation = device->generation;
238 smp_rmb(); /* node_id vs. generation */
239 err = snd_fw_transaction(dice->unit, TCODE_LOCK_COMPARE_SWAP,
241 SND_DICE_ADDR_TYPE_GLOBAL,
244 FW_FIXED_GENERATION |
245 dice->owner_generation);
248 if (buffer[0] == cpu_to_be64(OWNER_NO_OWNER))
250 /* The address seems to be already registered. */
251 if (buffer[0] == buffer[1])
254 dev_err(&dice->unit->device,
255 "device is already in use\n");
258 if (err != -EAGAIN || retries-- > 0)
267 dice->owner_generation = -1;
272 static void unregister_notification_address(struct snd_dice *dice)
274 struct fw_device *device = fw_parent_device(dice->unit);
277 buffer = kmalloc(2 * 8, GFP_KERNEL);
281 buffer[0] = cpu_to_be64(
282 ((u64)device->card->node_id << OWNER_NODE_SHIFT) |
283 dice->notification_handler.offset);
284 buffer[1] = cpu_to_be64(OWNER_NO_OWNER);
285 snd_fw_transaction(dice->unit, TCODE_LOCK_COMPARE_SWAP,
286 get_subaddr(dice, SND_DICE_ADDR_TYPE_GLOBAL,
288 buffer, 2 * 8, FW_QUIET |
289 FW_FIXED_GENERATION | dice->owner_generation);
293 dice->owner_generation = -1;
296 void snd_dice_transaction_destroy(struct snd_dice *dice)
298 struct fw_address_handler *handler = &dice->notification_handler;
300 if (handler->callback_data == NULL)
303 unregister_notification_address(dice);
305 fw_core_remove_address_handler(handler);
306 handler->callback_data = NULL;
309 int snd_dice_transaction_reinit(struct snd_dice *dice)
311 struct fw_address_handler *handler = &dice->notification_handler;
313 if (handler->callback_data == NULL)
316 return register_notification_address(dice, false);
319 static int get_subaddrs(struct snd_dice *dice)
321 static const int min_values[10] = {
334 pointers = kmalloc_array(ARRAY_SIZE(min_values), sizeof(__be32),
336 if (pointers == NULL)
340 * Check that the sub address spaces exist and are located inside the
341 * private address space. The minimum values are chosen so that all
342 * minimally required registers are included.
344 err = snd_fw_transaction(dice->unit, TCODE_READ_BLOCK_REQUEST,
345 DICE_PRIVATE_SPACE, pointers,
346 sizeof(__be32) * ARRAY_SIZE(min_values), 0);
350 for (i = 0; i < ARRAY_SIZE(min_values); ++i) {
351 data = be32_to_cpu(pointers[i]);
352 if (data < min_values[i] || data >= 0x40000) {
359 * Check that the implemented DICE driver specification major version
362 err = snd_fw_transaction(dice->unit, TCODE_READ_QUADLET_REQUEST,
364 be32_to_cpu(pointers[0]) * 4 + GLOBAL_VERSION,
365 &version, sizeof(version), 0);
369 if ((version & cpu_to_be32(0xff000000)) != cpu_to_be32(0x01000000)) {
370 dev_err(&dice->unit->device,
371 "unknown DICE version: 0x%08x\n", be32_to_cpu(version));
376 dice->global_offset = be32_to_cpu(pointers[0]) * 4;
377 dice->tx_offset = be32_to_cpu(pointers[2]) * 4;
378 dice->rx_offset = be32_to_cpu(pointers[4]) * 4;
379 dice->sync_offset = be32_to_cpu(pointers[6]) * 4;
380 dice->rsrv_offset = be32_to_cpu(pointers[8]) * 4;
383 if (be32_to_cpu(pointers[1]) * 4 >= GLOBAL_CLOCK_CAPABILITIES + 4)
384 dice->clock_caps = 1;
390 int snd_dice_transaction_init(struct snd_dice *dice)
392 struct fw_address_handler *handler = &dice->notification_handler;
395 err = get_subaddrs(dice);
399 /* Allocation callback in address space over host controller */
401 handler->address_callback = dice_notification;
402 handler->callback_data = dice;
403 err = fw_core_add_address_handler(handler, &fw_high_memory_region);
405 handler->callback_data = NULL;
409 /* Register the address space */
410 err = register_notification_address(dice, true);
412 fw_core_remove_address_handler(handler);
413 handler->callback_data = NULL;