370 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * dice_transaction.c - a part of driver for Dice based devices4 *5 * Copyright (c) Clemens Ladisch6 * Copyright (c) 2014 Takashi Sakamoto7 */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 the286 * private address space. The minimum values are chosen so that all287 * 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 major306 * 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