Path: blob/master/sound/firewire/dice/dice-transaction.c
26424 views
// SPDX-License-Identifier: GPL-2.0-only1/*2* dice_transaction.c - a part of driver for Dice based devices3*4* Copyright (c) Clemens Ladisch5* Copyright (c) 2014 Takashi Sakamoto6*/78#include "dice.h"910static u64 get_subaddr(struct snd_dice *dice, enum snd_dice_addr_type type,11u64 offset)12{13switch (type) {14case SND_DICE_ADDR_TYPE_TX:15offset += dice->tx_offset;16break;17case SND_DICE_ADDR_TYPE_RX:18offset += dice->rx_offset;19break;20case SND_DICE_ADDR_TYPE_SYNC:21offset += dice->sync_offset;22break;23case SND_DICE_ADDR_TYPE_RSRV:24offset += dice->rsrv_offset;25break;26case SND_DICE_ADDR_TYPE_GLOBAL:27default:28offset += dice->global_offset;29break;30}31offset += DICE_PRIVATE_SPACE;32return offset;33}3435int snd_dice_transaction_write(struct snd_dice *dice,36enum snd_dice_addr_type type,37unsigned int offset, void *buf, unsigned int len)38{39return snd_fw_transaction(dice->unit,40(len == 4) ? TCODE_WRITE_QUADLET_REQUEST :41TCODE_WRITE_BLOCK_REQUEST,42get_subaddr(dice, type, offset), buf, len, 0);43}4445int snd_dice_transaction_read(struct snd_dice *dice,46enum snd_dice_addr_type type, unsigned int offset,47void *buf, unsigned int len)48{49return snd_fw_transaction(dice->unit,50(len == 4) ? TCODE_READ_QUADLET_REQUEST :51TCODE_READ_BLOCK_REQUEST,52get_subaddr(dice, type, offset), buf, len, 0);53}5455static unsigned int get_clock_info(struct snd_dice *dice, __be32 *info)56{57return snd_dice_transaction_read_global(dice, GLOBAL_CLOCK_SELECT,58info, 4);59}6061int snd_dice_transaction_get_clock_source(struct snd_dice *dice,62unsigned int *source)63{64__be32 info;65int err;6667err = get_clock_info(dice, &info);68if (err >= 0)69*source = be32_to_cpu(info) & CLOCK_SOURCE_MASK;7071return err;72}7374int snd_dice_transaction_get_rate(struct snd_dice *dice, unsigned int *rate)75{76__be32 info;77unsigned int index;78int err;7980err = get_clock_info(dice, &info);81if (err < 0)82goto end;8384index = (be32_to_cpu(info) & CLOCK_RATE_MASK) >> CLOCK_RATE_SHIFT;85if (index >= SND_DICE_RATES_COUNT) {86err = -ENOSYS;87goto end;88}8990*rate = snd_dice_rates[index];91end:92return err;93}9495int snd_dice_transaction_set_enable(struct snd_dice *dice)96{97__be32 value;98int err = 0;99100if (dice->global_enabled)101goto end;102103value = cpu_to_be32(1);104err = snd_fw_transaction(dice->unit, TCODE_WRITE_QUADLET_REQUEST,105get_subaddr(dice, SND_DICE_ADDR_TYPE_GLOBAL,106GLOBAL_ENABLE),107&value, 4,108FW_FIXED_GENERATION | dice->owner_generation);109if (err < 0)110goto end;111112dice->global_enabled = true;113end:114return err;115}116117void snd_dice_transaction_clear_enable(struct snd_dice *dice)118{119__be32 value;120121value = 0;122snd_fw_transaction(dice->unit, TCODE_WRITE_QUADLET_REQUEST,123get_subaddr(dice, SND_DICE_ADDR_TYPE_GLOBAL,124GLOBAL_ENABLE),125&value, 4, FW_QUIET |126FW_FIXED_GENERATION | dice->owner_generation);127128dice->global_enabled = false;129}130131static void dice_notification(struct fw_card *card, struct fw_request *request,132int tcode, int destination, int source,133int generation, unsigned long long offset,134void *data, size_t length, void *callback_data)135{136struct snd_dice *dice = callback_data;137u32 bits;138unsigned long flags;139140if (tcode != TCODE_WRITE_QUADLET_REQUEST) {141fw_send_response(card, request, RCODE_TYPE_ERROR);142return;143}144if ((offset & 3) != 0) {145fw_send_response(card, request, RCODE_ADDRESS_ERROR);146return;147}148149bits = be32_to_cpup(data);150151spin_lock_irqsave(&dice->lock, flags);152dice->notification_bits |= bits;153spin_unlock_irqrestore(&dice->lock, flags);154155fw_send_response(card, request, RCODE_COMPLETE);156157if (bits & NOTIFY_CLOCK_ACCEPTED)158complete(&dice->clock_accepted);159wake_up(&dice->hwdep_wait);160}161162static int register_notification_address(struct snd_dice *dice, bool retry)163{164struct fw_device *device = fw_parent_device(dice->unit);165__be64 *buffer;166unsigned int retries;167int err;168169retries = (retry) ? 3 : 0;170171buffer = kmalloc(2 * 8, GFP_KERNEL);172if (!buffer)173return -ENOMEM;174175for (;;) {176buffer[0] = cpu_to_be64(OWNER_NO_OWNER);177buffer[1] = cpu_to_be64(178((u64)device->card->node_id << OWNER_NODE_SHIFT) |179dice->notification_handler.offset);180181dice->owner_generation = device->generation;182smp_rmb(); /* node_id vs. generation */183err = snd_fw_transaction(dice->unit, TCODE_LOCK_COMPARE_SWAP,184get_subaddr(dice,185SND_DICE_ADDR_TYPE_GLOBAL,186GLOBAL_OWNER),187buffer, 2 * 8,188FW_FIXED_GENERATION |189dice->owner_generation);190if (err == 0) {191/* success */192if (buffer[0] == cpu_to_be64(OWNER_NO_OWNER))193break;194/* The address seems to be already registered. */195if (buffer[0] == buffer[1])196break;197198dev_err(&dice->unit->device,199"device is already in use\n");200err = -EBUSY;201}202if (err != -EAGAIN || retries-- > 0)203break;204205msleep(20);206}207208kfree(buffer);209210if (err < 0)211dice->owner_generation = -1;212213return err;214}215216static void unregister_notification_address(struct snd_dice *dice)217{218struct fw_device *device = fw_parent_device(dice->unit);219__be64 *buffer;220221buffer = kmalloc(2 * 8, GFP_KERNEL);222if (buffer == NULL)223return;224225buffer[0] = cpu_to_be64(226((u64)device->card->node_id << OWNER_NODE_SHIFT) |227dice->notification_handler.offset);228buffer[1] = cpu_to_be64(OWNER_NO_OWNER);229snd_fw_transaction(dice->unit, TCODE_LOCK_COMPARE_SWAP,230get_subaddr(dice, SND_DICE_ADDR_TYPE_GLOBAL,231GLOBAL_OWNER),232buffer, 2 * 8, FW_QUIET |233FW_FIXED_GENERATION | dice->owner_generation);234235kfree(buffer);236237dice->owner_generation = -1;238}239240void snd_dice_transaction_destroy(struct snd_dice *dice)241{242struct fw_address_handler *handler = &dice->notification_handler;243244if (handler->callback_data == NULL)245return;246247unregister_notification_address(dice);248249fw_core_remove_address_handler(handler);250handler->callback_data = NULL;251}252253int snd_dice_transaction_reinit(struct snd_dice *dice)254{255struct fw_address_handler *handler = &dice->notification_handler;256257if (handler->callback_data == NULL)258return -EINVAL;259260return register_notification_address(dice, false);261}262263static int get_subaddrs(struct snd_dice *dice)264{265static const int min_values[10] = {26610, 0x60 / 4,26710, 0x18 / 4,26810, 0x18 / 4,2690, 0,2700, 0,271};272__be32 *pointers;273__be32 version;274u32 data;275unsigned int i;276int err;277278pointers = kmalloc_array(ARRAY_SIZE(min_values), sizeof(__be32),279GFP_KERNEL);280if (pointers == NULL)281return -ENOMEM;282283/*284* Check that the sub address spaces exist and are located inside the285* private address space. The minimum values are chosen so that all286* minimally required registers are included.287*/288err = snd_fw_transaction(dice->unit, TCODE_READ_BLOCK_REQUEST,289DICE_PRIVATE_SPACE, pointers,290sizeof(__be32) * ARRAY_SIZE(min_values), 0);291if (err < 0)292goto end;293294for (i = 0; i < ARRAY_SIZE(min_values); ++i) {295data = be32_to_cpu(pointers[i]);296if (data < min_values[i] || data >= 0x40000) {297err = -ENODEV;298goto end;299}300}301302if (be32_to_cpu(pointers[1]) > 0x18) {303/*304* Check that the implemented DICE driver specification major305* version number matches.306*/307err = snd_fw_transaction(dice->unit, TCODE_READ_QUADLET_REQUEST,308DICE_PRIVATE_SPACE +309be32_to_cpu(pointers[0]) * 4 + GLOBAL_VERSION,310&version, sizeof(version), 0);311if (err < 0)312goto end;313314if ((version & cpu_to_be32(0xff000000)) !=315cpu_to_be32(0x01000000)) {316dev_err(&dice->unit->device,317"unknown DICE version: 0x%08x\n",318be32_to_cpu(version));319err = -ENODEV;320goto end;321}322323/* Set up later. */324dice->clock_caps = 1;325}326327dice->global_offset = be32_to_cpu(pointers[0]) * 4;328dice->tx_offset = be32_to_cpu(pointers[2]) * 4;329dice->rx_offset = be32_to_cpu(pointers[4]) * 4;330331/* Old firmware doesn't support these fields. */332if (pointers[7])333dice->sync_offset = be32_to_cpu(pointers[6]) * 4;334if (pointers[9])335dice->rsrv_offset = be32_to_cpu(pointers[8]) * 4;336end:337kfree(pointers);338return err;339}340341int snd_dice_transaction_init(struct snd_dice *dice)342{343struct fw_address_handler *handler = &dice->notification_handler;344int err;345346err = get_subaddrs(dice);347if (err < 0)348return err;349350/* Allocation callback in address space over host controller */351handler->length = 4;352handler->address_callback = dice_notification;353handler->callback_data = dice;354err = fw_core_add_address_handler(handler, &fw_high_memory_region);355if (err < 0) {356handler->callback_data = NULL;357return err;358}359360/* Register the address space */361err = register_notification_address(dice, true);362if (err < 0) {363fw_core_remove_address_handler(handler);364handler->callback_data = NULL;365}366367return err;368}369370371