Path: blob/master/sound/firewire/dice/dice-transaction.c
29266 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;138139if (tcode != TCODE_WRITE_QUADLET_REQUEST) {140fw_send_response(card, request, RCODE_TYPE_ERROR);141return;142}143if ((offset & 3) != 0) {144fw_send_response(card, request, RCODE_ADDRESS_ERROR);145return;146}147148bits = be32_to_cpup(data);149150scoped_guard(spinlock_irqsave, &dice->lock) {151dice->notification_bits |= bits;152}153154fw_send_response(card, request, RCODE_COMPLETE);155156if (bits & NOTIFY_CLOCK_ACCEPTED)157complete(&dice->clock_accepted);158wake_up(&dice->hwdep_wait);159}160161static int register_notification_address(struct snd_dice *dice, bool retry)162{163struct fw_device *device = fw_parent_device(dice->unit);164__be64 *buffer;165unsigned int retries;166int err;167168retries = (retry) ? 3 : 0;169170buffer = kmalloc(2 * 8, GFP_KERNEL);171if (!buffer)172return -ENOMEM;173174for (;;) {175buffer[0] = cpu_to_be64(OWNER_NO_OWNER);176buffer[1] = cpu_to_be64(177((u64)device->card->node_id << OWNER_NODE_SHIFT) |178dice->notification_handler.offset);179180dice->owner_generation = device->generation;181smp_rmb(); /* node_id vs. generation */182err = snd_fw_transaction(dice->unit, TCODE_LOCK_COMPARE_SWAP,183get_subaddr(dice,184SND_DICE_ADDR_TYPE_GLOBAL,185GLOBAL_OWNER),186buffer, 2 * 8,187FW_FIXED_GENERATION |188dice->owner_generation);189if (err == 0) {190/* success */191if (buffer[0] == cpu_to_be64(OWNER_NO_OWNER))192break;193/* The address seems to be already registered. */194if (buffer[0] == buffer[1])195break;196197dev_err(&dice->unit->device,198"device is already in use\n");199err = -EBUSY;200}201if (err != -EAGAIN || retries-- > 0)202break;203204msleep(20);205}206207kfree(buffer);208209if (err < 0)210dice->owner_generation = -1;211212return err;213}214215static void unregister_notification_address(struct snd_dice *dice)216{217struct fw_device *device = fw_parent_device(dice->unit);218__be64 *buffer;219220buffer = kmalloc(2 * 8, GFP_KERNEL);221if (buffer == NULL)222return;223224buffer[0] = cpu_to_be64(225((u64)device->card->node_id << OWNER_NODE_SHIFT) |226dice->notification_handler.offset);227buffer[1] = cpu_to_be64(OWNER_NO_OWNER);228snd_fw_transaction(dice->unit, TCODE_LOCK_COMPARE_SWAP,229get_subaddr(dice, SND_DICE_ADDR_TYPE_GLOBAL,230GLOBAL_OWNER),231buffer, 2 * 8, FW_QUIET |232FW_FIXED_GENERATION | dice->owner_generation);233234kfree(buffer);235236dice->owner_generation = -1;237}238239void snd_dice_transaction_destroy(struct snd_dice *dice)240{241struct fw_address_handler *handler = &dice->notification_handler;242243if (handler->callback_data == NULL)244return;245246unregister_notification_address(dice);247248fw_core_remove_address_handler(handler);249handler->callback_data = NULL;250}251252int snd_dice_transaction_reinit(struct snd_dice *dice)253{254struct fw_address_handler *handler = &dice->notification_handler;255256if (handler->callback_data == NULL)257return -EINVAL;258259return register_notification_address(dice, false);260}261262static int get_subaddrs(struct snd_dice *dice)263{264static const int min_values[10] = {26510, 0x60 / 4,26610, 0x18 / 4,26710, 0x18 / 4,2680, 0,2690, 0,270};271__be32 *pointers;272__be32 version;273u32 data;274unsigned int i;275int err;276277pointers = kmalloc_array(ARRAY_SIZE(min_values), sizeof(__be32),278GFP_KERNEL);279if (pointers == NULL)280return -ENOMEM;281282/*283* Check that the sub address spaces exist and are located inside the284* private address space. The minimum values are chosen so that all285* minimally required registers are included.286*/287err = snd_fw_transaction(dice->unit, TCODE_READ_BLOCK_REQUEST,288DICE_PRIVATE_SPACE, pointers,289sizeof(__be32) * ARRAY_SIZE(min_values), 0);290if (err < 0)291goto end;292293for (i = 0; i < ARRAY_SIZE(min_values); ++i) {294data = be32_to_cpu(pointers[i]);295if (data < min_values[i] || data >= 0x40000) {296err = -ENODEV;297goto end;298}299}300301if (be32_to_cpu(pointers[1]) > 0x18) {302/*303* Check that the implemented DICE driver specification major304* version number matches.305*/306err = snd_fw_transaction(dice->unit, TCODE_READ_QUADLET_REQUEST,307DICE_PRIVATE_SPACE +308be32_to_cpu(pointers[0]) * 4 + GLOBAL_VERSION,309&version, sizeof(version), 0);310if (err < 0)311goto end;312313if ((version & cpu_to_be32(0xff000000)) !=314cpu_to_be32(0x01000000)) {315dev_err(&dice->unit->device,316"unknown DICE version: 0x%08x\n",317be32_to_cpu(version));318err = -ENODEV;319goto end;320}321322/* Set up later. */323dice->clock_caps = 1;324}325326dice->global_offset = be32_to_cpu(pointers[0]) * 4;327dice->tx_offset = be32_to_cpu(pointers[2]) * 4;328dice->rx_offset = be32_to_cpu(pointers[4]) * 4;329330/* Old firmware doesn't support these fields. */331if (pointers[7])332dice->sync_offset = be32_to_cpu(pointers[6]) * 4;333if (pointers[9])334dice->rsrv_offset = be32_to_cpu(pointers[8]) * 4;335end:336kfree(pointers);337return err;338}339340int snd_dice_transaction_init(struct snd_dice *dice)341{342struct fw_address_handler *handler = &dice->notification_handler;343int err;344345err = get_subaddrs(dice);346if (err < 0)347return err;348349/* Allocation callback in address space over host controller */350handler->length = 4;351handler->address_callback = dice_notification;352handler->callback_data = dice;353err = fw_core_add_address_handler(handler, &fw_high_memory_region);354if (err < 0) {355handler->callback_data = NULL;356return err;357}358359/* Register the address space */360err = register_notification_address(dice, true);361if (err < 0) {362fw_core_remove_address_handler(handler);363handler->callback_data = NULL;364}365366return err;367}368369370