162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * dice_transaction.c - a part of driver for Dice based devices 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) Clemens Ladisch 662306a36Sopenharmony_ci * Copyright (c) 2014 Takashi Sakamoto 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include "dice.h" 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_cistatic u64 get_subaddr(struct snd_dice *dice, enum snd_dice_addr_type type, 1262306a36Sopenharmony_ci u64 offset) 1362306a36Sopenharmony_ci{ 1462306a36Sopenharmony_ci switch (type) { 1562306a36Sopenharmony_ci case SND_DICE_ADDR_TYPE_TX: 1662306a36Sopenharmony_ci offset += dice->tx_offset; 1762306a36Sopenharmony_ci break; 1862306a36Sopenharmony_ci case SND_DICE_ADDR_TYPE_RX: 1962306a36Sopenharmony_ci offset += dice->rx_offset; 2062306a36Sopenharmony_ci break; 2162306a36Sopenharmony_ci case SND_DICE_ADDR_TYPE_SYNC: 2262306a36Sopenharmony_ci offset += dice->sync_offset; 2362306a36Sopenharmony_ci break; 2462306a36Sopenharmony_ci case SND_DICE_ADDR_TYPE_RSRV: 2562306a36Sopenharmony_ci offset += dice->rsrv_offset; 2662306a36Sopenharmony_ci break; 2762306a36Sopenharmony_ci case SND_DICE_ADDR_TYPE_GLOBAL: 2862306a36Sopenharmony_ci default: 2962306a36Sopenharmony_ci offset += dice->global_offset; 3062306a36Sopenharmony_ci break; 3162306a36Sopenharmony_ci } 3262306a36Sopenharmony_ci offset += DICE_PRIVATE_SPACE; 3362306a36Sopenharmony_ci return offset; 3462306a36Sopenharmony_ci} 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ciint snd_dice_transaction_write(struct snd_dice *dice, 3762306a36Sopenharmony_ci enum snd_dice_addr_type type, 3862306a36Sopenharmony_ci unsigned int offset, void *buf, unsigned int len) 3962306a36Sopenharmony_ci{ 4062306a36Sopenharmony_ci return snd_fw_transaction(dice->unit, 4162306a36Sopenharmony_ci (len == 4) ? TCODE_WRITE_QUADLET_REQUEST : 4262306a36Sopenharmony_ci TCODE_WRITE_BLOCK_REQUEST, 4362306a36Sopenharmony_ci get_subaddr(dice, type, offset), buf, len, 0); 4462306a36Sopenharmony_ci} 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ciint snd_dice_transaction_read(struct snd_dice *dice, 4762306a36Sopenharmony_ci enum snd_dice_addr_type type, unsigned int offset, 4862306a36Sopenharmony_ci void *buf, unsigned int len) 4962306a36Sopenharmony_ci{ 5062306a36Sopenharmony_ci return snd_fw_transaction(dice->unit, 5162306a36Sopenharmony_ci (len == 4) ? TCODE_READ_QUADLET_REQUEST : 5262306a36Sopenharmony_ci TCODE_READ_BLOCK_REQUEST, 5362306a36Sopenharmony_ci get_subaddr(dice, type, offset), buf, len, 0); 5462306a36Sopenharmony_ci} 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_cistatic unsigned int get_clock_info(struct snd_dice *dice, __be32 *info) 5762306a36Sopenharmony_ci{ 5862306a36Sopenharmony_ci return snd_dice_transaction_read_global(dice, GLOBAL_CLOCK_SELECT, 5962306a36Sopenharmony_ci info, 4); 6062306a36Sopenharmony_ci} 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ciint snd_dice_transaction_get_clock_source(struct snd_dice *dice, 6362306a36Sopenharmony_ci unsigned int *source) 6462306a36Sopenharmony_ci{ 6562306a36Sopenharmony_ci __be32 info; 6662306a36Sopenharmony_ci int err; 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci err = get_clock_info(dice, &info); 6962306a36Sopenharmony_ci if (err >= 0) 7062306a36Sopenharmony_ci *source = be32_to_cpu(info) & CLOCK_SOURCE_MASK; 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci return err; 7362306a36Sopenharmony_ci} 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ciint snd_dice_transaction_get_rate(struct snd_dice *dice, unsigned int *rate) 7662306a36Sopenharmony_ci{ 7762306a36Sopenharmony_ci __be32 info; 7862306a36Sopenharmony_ci unsigned int index; 7962306a36Sopenharmony_ci int err; 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci err = get_clock_info(dice, &info); 8262306a36Sopenharmony_ci if (err < 0) 8362306a36Sopenharmony_ci goto end; 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci index = (be32_to_cpu(info) & CLOCK_RATE_MASK) >> CLOCK_RATE_SHIFT; 8662306a36Sopenharmony_ci if (index >= SND_DICE_RATES_COUNT) { 8762306a36Sopenharmony_ci err = -ENOSYS; 8862306a36Sopenharmony_ci goto end; 8962306a36Sopenharmony_ci } 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci *rate = snd_dice_rates[index]; 9262306a36Sopenharmony_ciend: 9362306a36Sopenharmony_ci return err; 9462306a36Sopenharmony_ci} 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ciint snd_dice_transaction_set_enable(struct snd_dice *dice) 9762306a36Sopenharmony_ci{ 9862306a36Sopenharmony_ci __be32 value; 9962306a36Sopenharmony_ci int err = 0; 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci if (dice->global_enabled) 10262306a36Sopenharmony_ci goto end; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci value = cpu_to_be32(1); 10562306a36Sopenharmony_ci err = snd_fw_transaction(dice->unit, TCODE_WRITE_QUADLET_REQUEST, 10662306a36Sopenharmony_ci get_subaddr(dice, SND_DICE_ADDR_TYPE_GLOBAL, 10762306a36Sopenharmony_ci GLOBAL_ENABLE), 10862306a36Sopenharmony_ci &value, 4, 10962306a36Sopenharmony_ci FW_FIXED_GENERATION | dice->owner_generation); 11062306a36Sopenharmony_ci if (err < 0) 11162306a36Sopenharmony_ci goto end; 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci dice->global_enabled = true; 11462306a36Sopenharmony_ciend: 11562306a36Sopenharmony_ci return err; 11662306a36Sopenharmony_ci} 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_civoid snd_dice_transaction_clear_enable(struct snd_dice *dice) 11962306a36Sopenharmony_ci{ 12062306a36Sopenharmony_ci __be32 value; 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci value = 0; 12362306a36Sopenharmony_ci snd_fw_transaction(dice->unit, TCODE_WRITE_QUADLET_REQUEST, 12462306a36Sopenharmony_ci get_subaddr(dice, SND_DICE_ADDR_TYPE_GLOBAL, 12562306a36Sopenharmony_ci GLOBAL_ENABLE), 12662306a36Sopenharmony_ci &value, 4, FW_QUIET | 12762306a36Sopenharmony_ci FW_FIXED_GENERATION | dice->owner_generation); 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci dice->global_enabled = false; 13062306a36Sopenharmony_ci} 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_cistatic void dice_notification(struct fw_card *card, struct fw_request *request, 13362306a36Sopenharmony_ci int tcode, int destination, int source, 13462306a36Sopenharmony_ci int generation, unsigned long long offset, 13562306a36Sopenharmony_ci void *data, size_t length, void *callback_data) 13662306a36Sopenharmony_ci{ 13762306a36Sopenharmony_ci struct snd_dice *dice = callback_data; 13862306a36Sopenharmony_ci u32 bits; 13962306a36Sopenharmony_ci unsigned long flags; 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci if (tcode != TCODE_WRITE_QUADLET_REQUEST) { 14262306a36Sopenharmony_ci fw_send_response(card, request, RCODE_TYPE_ERROR); 14362306a36Sopenharmony_ci return; 14462306a36Sopenharmony_ci } 14562306a36Sopenharmony_ci if ((offset & 3) != 0) { 14662306a36Sopenharmony_ci fw_send_response(card, request, RCODE_ADDRESS_ERROR); 14762306a36Sopenharmony_ci return; 14862306a36Sopenharmony_ci } 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci bits = be32_to_cpup(data); 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci spin_lock_irqsave(&dice->lock, flags); 15362306a36Sopenharmony_ci dice->notification_bits |= bits; 15462306a36Sopenharmony_ci spin_unlock_irqrestore(&dice->lock, flags); 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci fw_send_response(card, request, RCODE_COMPLETE); 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci if (bits & NOTIFY_CLOCK_ACCEPTED) 15962306a36Sopenharmony_ci complete(&dice->clock_accepted); 16062306a36Sopenharmony_ci wake_up(&dice->hwdep_wait); 16162306a36Sopenharmony_ci} 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_cistatic int register_notification_address(struct snd_dice *dice, bool retry) 16462306a36Sopenharmony_ci{ 16562306a36Sopenharmony_ci struct fw_device *device = fw_parent_device(dice->unit); 16662306a36Sopenharmony_ci __be64 *buffer; 16762306a36Sopenharmony_ci unsigned int retries; 16862306a36Sopenharmony_ci int err; 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci retries = (retry) ? 3 : 0; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci buffer = kmalloc(2 * 8, GFP_KERNEL); 17362306a36Sopenharmony_ci if (!buffer) 17462306a36Sopenharmony_ci return -ENOMEM; 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci for (;;) { 17762306a36Sopenharmony_ci buffer[0] = cpu_to_be64(OWNER_NO_OWNER); 17862306a36Sopenharmony_ci buffer[1] = cpu_to_be64( 17962306a36Sopenharmony_ci ((u64)device->card->node_id << OWNER_NODE_SHIFT) | 18062306a36Sopenharmony_ci dice->notification_handler.offset); 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci dice->owner_generation = device->generation; 18362306a36Sopenharmony_ci smp_rmb(); /* node_id vs. generation */ 18462306a36Sopenharmony_ci err = snd_fw_transaction(dice->unit, TCODE_LOCK_COMPARE_SWAP, 18562306a36Sopenharmony_ci get_subaddr(dice, 18662306a36Sopenharmony_ci SND_DICE_ADDR_TYPE_GLOBAL, 18762306a36Sopenharmony_ci GLOBAL_OWNER), 18862306a36Sopenharmony_ci buffer, 2 * 8, 18962306a36Sopenharmony_ci FW_FIXED_GENERATION | 19062306a36Sopenharmony_ci dice->owner_generation); 19162306a36Sopenharmony_ci if (err == 0) { 19262306a36Sopenharmony_ci /* success */ 19362306a36Sopenharmony_ci if (buffer[0] == cpu_to_be64(OWNER_NO_OWNER)) 19462306a36Sopenharmony_ci break; 19562306a36Sopenharmony_ci /* The address seems to be already registered. */ 19662306a36Sopenharmony_ci if (buffer[0] == buffer[1]) 19762306a36Sopenharmony_ci break; 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci dev_err(&dice->unit->device, 20062306a36Sopenharmony_ci "device is already in use\n"); 20162306a36Sopenharmony_ci err = -EBUSY; 20262306a36Sopenharmony_ci } 20362306a36Sopenharmony_ci if (err != -EAGAIN || retries-- > 0) 20462306a36Sopenharmony_ci break; 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci msleep(20); 20762306a36Sopenharmony_ci } 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci kfree(buffer); 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci if (err < 0) 21262306a36Sopenharmony_ci dice->owner_generation = -1; 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci return err; 21562306a36Sopenharmony_ci} 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_cistatic void unregister_notification_address(struct snd_dice *dice) 21862306a36Sopenharmony_ci{ 21962306a36Sopenharmony_ci struct fw_device *device = fw_parent_device(dice->unit); 22062306a36Sopenharmony_ci __be64 *buffer; 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci buffer = kmalloc(2 * 8, GFP_KERNEL); 22362306a36Sopenharmony_ci if (buffer == NULL) 22462306a36Sopenharmony_ci return; 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci buffer[0] = cpu_to_be64( 22762306a36Sopenharmony_ci ((u64)device->card->node_id << OWNER_NODE_SHIFT) | 22862306a36Sopenharmony_ci dice->notification_handler.offset); 22962306a36Sopenharmony_ci buffer[1] = cpu_to_be64(OWNER_NO_OWNER); 23062306a36Sopenharmony_ci snd_fw_transaction(dice->unit, TCODE_LOCK_COMPARE_SWAP, 23162306a36Sopenharmony_ci get_subaddr(dice, SND_DICE_ADDR_TYPE_GLOBAL, 23262306a36Sopenharmony_ci GLOBAL_OWNER), 23362306a36Sopenharmony_ci buffer, 2 * 8, FW_QUIET | 23462306a36Sopenharmony_ci FW_FIXED_GENERATION | dice->owner_generation); 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci kfree(buffer); 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci dice->owner_generation = -1; 23962306a36Sopenharmony_ci} 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_civoid snd_dice_transaction_destroy(struct snd_dice *dice) 24262306a36Sopenharmony_ci{ 24362306a36Sopenharmony_ci struct fw_address_handler *handler = &dice->notification_handler; 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci if (handler->callback_data == NULL) 24662306a36Sopenharmony_ci return; 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci unregister_notification_address(dice); 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci fw_core_remove_address_handler(handler); 25162306a36Sopenharmony_ci handler->callback_data = NULL; 25262306a36Sopenharmony_ci} 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ciint snd_dice_transaction_reinit(struct snd_dice *dice) 25562306a36Sopenharmony_ci{ 25662306a36Sopenharmony_ci struct fw_address_handler *handler = &dice->notification_handler; 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci if (handler->callback_data == NULL) 25962306a36Sopenharmony_ci return -EINVAL; 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ci return register_notification_address(dice, false); 26262306a36Sopenharmony_ci} 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_cistatic int get_subaddrs(struct snd_dice *dice) 26562306a36Sopenharmony_ci{ 26662306a36Sopenharmony_ci static const int min_values[10] = { 26762306a36Sopenharmony_ci 10, 0x60 / 4, 26862306a36Sopenharmony_ci 10, 0x18 / 4, 26962306a36Sopenharmony_ci 10, 0x18 / 4, 27062306a36Sopenharmony_ci 0, 0, 27162306a36Sopenharmony_ci 0, 0, 27262306a36Sopenharmony_ci }; 27362306a36Sopenharmony_ci __be32 *pointers; 27462306a36Sopenharmony_ci __be32 version; 27562306a36Sopenharmony_ci u32 data; 27662306a36Sopenharmony_ci unsigned int i; 27762306a36Sopenharmony_ci int err; 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_ci pointers = kmalloc_array(ARRAY_SIZE(min_values), sizeof(__be32), 28062306a36Sopenharmony_ci GFP_KERNEL); 28162306a36Sopenharmony_ci if (pointers == NULL) 28262306a36Sopenharmony_ci return -ENOMEM; 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_ci /* 28562306a36Sopenharmony_ci * Check that the sub address spaces exist and are located inside the 28662306a36Sopenharmony_ci * private address space. The minimum values are chosen so that all 28762306a36Sopenharmony_ci * minimally required registers are included. 28862306a36Sopenharmony_ci */ 28962306a36Sopenharmony_ci err = snd_fw_transaction(dice->unit, TCODE_READ_BLOCK_REQUEST, 29062306a36Sopenharmony_ci DICE_PRIVATE_SPACE, pointers, 29162306a36Sopenharmony_ci sizeof(__be32) * ARRAY_SIZE(min_values), 0); 29262306a36Sopenharmony_ci if (err < 0) 29362306a36Sopenharmony_ci goto end; 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(min_values); ++i) { 29662306a36Sopenharmony_ci data = be32_to_cpu(pointers[i]); 29762306a36Sopenharmony_ci if (data < min_values[i] || data >= 0x40000) { 29862306a36Sopenharmony_ci err = -ENODEV; 29962306a36Sopenharmony_ci goto end; 30062306a36Sopenharmony_ci } 30162306a36Sopenharmony_ci } 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ci if (be32_to_cpu(pointers[1]) > 0x18) { 30462306a36Sopenharmony_ci /* 30562306a36Sopenharmony_ci * Check that the implemented DICE driver specification major 30662306a36Sopenharmony_ci * version number matches. 30762306a36Sopenharmony_ci */ 30862306a36Sopenharmony_ci err = snd_fw_transaction(dice->unit, TCODE_READ_QUADLET_REQUEST, 30962306a36Sopenharmony_ci DICE_PRIVATE_SPACE + 31062306a36Sopenharmony_ci be32_to_cpu(pointers[0]) * 4 + GLOBAL_VERSION, 31162306a36Sopenharmony_ci &version, sizeof(version), 0); 31262306a36Sopenharmony_ci if (err < 0) 31362306a36Sopenharmony_ci goto end; 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci if ((version & cpu_to_be32(0xff000000)) != 31662306a36Sopenharmony_ci cpu_to_be32(0x01000000)) { 31762306a36Sopenharmony_ci dev_err(&dice->unit->device, 31862306a36Sopenharmony_ci "unknown DICE version: 0x%08x\n", 31962306a36Sopenharmony_ci be32_to_cpu(version)); 32062306a36Sopenharmony_ci err = -ENODEV; 32162306a36Sopenharmony_ci goto end; 32262306a36Sopenharmony_ci } 32362306a36Sopenharmony_ci 32462306a36Sopenharmony_ci /* Set up later. */ 32562306a36Sopenharmony_ci dice->clock_caps = 1; 32662306a36Sopenharmony_ci } 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_ci dice->global_offset = be32_to_cpu(pointers[0]) * 4; 32962306a36Sopenharmony_ci dice->tx_offset = be32_to_cpu(pointers[2]) * 4; 33062306a36Sopenharmony_ci dice->rx_offset = be32_to_cpu(pointers[4]) * 4; 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_ci /* Old firmware doesn't support these fields. */ 33362306a36Sopenharmony_ci if (pointers[7]) 33462306a36Sopenharmony_ci dice->sync_offset = be32_to_cpu(pointers[6]) * 4; 33562306a36Sopenharmony_ci if (pointers[9]) 33662306a36Sopenharmony_ci dice->rsrv_offset = be32_to_cpu(pointers[8]) * 4; 33762306a36Sopenharmony_ciend: 33862306a36Sopenharmony_ci kfree(pointers); 33962306a36Sopenharmony_ci return err; 34062306a36Sopenharmony_ci} 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_ciint snd_dice_transaction_init(struct snd_dice *dice) 34362306a36Sopenharmony_ci{ 34462306a36Sopenharmony_ci struct fw_address_handler *handler = &dice->notification_handler; 34562306a36Sopenharmony_ci int err; 34662306a36Sopenharmony_ci 34762306a36Sopenharmony_ci err = get_subaddrs(dice); 34862306a36Sopenharmony_ci if (err < 0) 34962306a36Sopenharmony_ci return err; 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_ci /* Allocation callback in address space over host controller */ 35262306a36Sopenharmony_ci handler->length = 4; 35362306a36Sopenharmony_ci handler->address_callback = dice_notification; 35462306a36Sopenharmony_ci handler->callback_data = dice; 35562306a36Sopenharmony_ci err = fw_core_add_address_handler(handler, &fw_high_memory_region); 35662306a36Sopenharmony_ci if (err < 0) { 35762306a36Sopenharmony_ci handler->callback_data = NULL; 35862306a36Sopenharmony_ci return err; 35962306a36Sopenharmony_ci } 36062306a36Sopenharmony_ci 36162306a36Sopenharmony_ci /* Register the address space */ 36262306a36Sopenharmony_ci err = register_notification_address(dice, true); 36362306a36Sopenharmony_ci if (err < 0) { 36462306a36Sopenharmony_ci fw_core_remove_address_handler(handler); 36562306a36Sopenharmony_ci handler->callback_data = NULL; 36662306a36Sopenharmony_ci } 36762306a36Sopenharmony_ci 36862306a36Sopenharmony_ci return err; 36962306a36Sopenharmony_ci} 370