mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
369 lines
8.6 KiB
369 lines
8.6 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/* |
|
* dice_transaction.c - a part of driver for Dice based devices |
|
* |
|
* Copyright (c) Clemens Ladisch |
|
* Copyright (c) 2014 Takashi Sakamoto |
|
*/ |
|
|
|
#include "dice.h" |
|
|
|
static u64 get_subaddr(struct snd_dice *dice, enum snd_dice_addr_type type, |
|
u64 offset) |
|
{ |
|
switch (type) { |
|
case SND_DICE_ADDR_TYPE_TX: |
|
offset += dice->tx_offset; |
|
break; |
|
case SND_DICE_ADDR_TYPE_RX: |
|
offset += dice->rx_offset; |
|
break; |
|
case SND_DICE_ADDR_TYPE_SYNC: |
|
offset += dice->sync_offset; |
|
break; |
|
case SND_DICE_ADDR_TYPE_RSRV: |
|
offset += dice->rsrv_offset; |
|
break; |
|
case SND_DICE_ADDR_TYPE_GLOBAL: |
|
default: |
|
offset += dice->global_offset; |
|
break; |
|
} |
|
offset += DICE_PRIVATE_SPACE; |
|
return offset; |
|
} |
|
|
|
int snd_dice_transaction_write(struct snd_dice *dice, |
|
enum snd_dice_addr_type type, |
|
unsigned int offset, void *buf, unsigned int len) |
|
{ |
|
return snd_fw_transaction(dice->unit, |
|
(len == 4) ? TCODE_WRITE_QUADLET_REQUEST : |
|
TCODE_WRITE_BLOCK_REQUEST, |
|
get_subaddr(dice, type, offset), buf, len, 0); |
|
} |
|
|
|
int snd_dice_transaction_read(struct snd_dice *dice, |
|
enum snd_dice_addr_type type, unsigned int offset, |
|
void *buf, unsigned int len) |
|
{ |
|
return snd_fw_transaction(dice->unit, |
|
(len == 4) ? TCODE_READ_QUADLET_REQUEST : |
|
TCODE_READ_BLOCK_REQUEST, |
|
get_subaddr(dice, type, offset), buf, len, 0); |
|
} |
|
|
|
static unsigned int get_clock_info(struct snd_dice *dice, __be32 *info) |
|
{ |
|
return snd_dice_transaction_read_global(dice, GLOBAL_CLOCK_SELECT, |
|
info, 4); |
|
} |
|
|
|
int snd_dice_transaction_get_clock_source(struct snd_dice *dice, |
|
unsigned int *source) |
|
{ |
|
__be32 info; |
|
int err; |
|
|
|
err = get_clock_info(dice, &info); |
|
if (err >= 0) |
|
*source = be32_to_cpu(info) & CLOCK_SOURCE_MASK; |
|
|
|
return err; |
|
} |
|
|
|
int snd_dice_transaction_get_rate(struct snd_dice *dice, unsigned int *rate) |
|
{ |
|
__be32 info; |
|
unsigned int index; |
|
int err; |
|
|
|
err = get_clock_info(dice, &info); |
|
if (err < 0) |
|
goto end; |
|
|
|
index = (be32_to_cpu(info) & CLOCK_RATE_MASK) >> CLOCK_RATE_SHIFT; |
|
if (index >= SND_DICE_RATES_COUNT) { |
|
err = -ENOSYS; |
|
goto end; |
|
} |
|
|
|
*rate = snd_dice_rates[index]; |
|
end: |
|
return err; |
|
} |
|
|
|
int snd_dice_transaction_set_enable(struct snd_dice *dice) |
|
{ |
|
__be32 value; |
|
int err = 0; |
|
|
|
if (dice->global_enabled) |
|
goto end; |
|
|
|
value = cpu_to_be32(1); |
|
err = snd_fw_transaction(dice->unit, TCODE_WRITE_QUADLET_REQUEST, |
|
get_subaddr(dice, SND_DICE_ADDR_TYPE_GLOBAL, |
|
GLOBAL_ENABLE), |
|
&value, 4, |
|
FW_FIXED_GENERATION | dice->owner_generation); |
|
if (err < 0) |
|
goto end; |
|
|
|
dice->global_enabled = true; |
|
end: |
|
return err; |
|
} |
|
|
|
void snd_dice_transaction_clear_enable(struct snd_dice *dice) |
|
{ |
|
__be32 value; |
|
|
|
value = 0; |
|
snd_fw_transaction(dice->unit, TCODE_WRITE_QUADLET_REQUEST, |
|
get_subaddr(dice, SND_DICE_ADDR_TYPE_GLOBAL, |
|
GLOBAL_ENABLE), |
|
&value, 4, FW_QUIET | |
|
FW_FIXED_GENERATION | dice->owner_generation); |
|
|
|
dice->global_enabled = false; |
|
} |
|
|
|
static void dice_notification(struct fw_card *card, struct fw_request *request, |
|
int tcode, int destination, int source, |
|
int generation, unsigned long long offset, |
|
void *data, size_t length, void *callback_data) |
|
{ |
|
struct snd_dice *dice = callback_data; |
|
u32 bits; |
|
unsigned long flags; |
|
|
|
if (tcode != TCODE_WRITE_QUADLET_REQUEST) { |
|
fw_send_response(card, request, RCODE_TYPE_ERROR); |
|
return; |
|
} |
|
if ((offset & 3) != 0) { |
|
fw_send_response(card, request, RCODE_ADDRESS_ERROR); |
|
return; |
|
} |
|
|
|
bits = be32_to_cpup(data); |
|
|
|
spin_lock_irqsave(&dice->lock, flags); |
|
dice->notification_bits |= bits; |
|
spin_unlock_irqrestore(&dice->lock, flags); |
|
|
|
fw_send_response(card, request, RCODE_COMPLETE); |
|
|
|
if (bits & NOTIFY_LOCK_CHG) |
|
complete(&dice->clock_accepted); |
|
wake_up(&dice->hwdep_wait); |
|
} |
|
|
|
static int register_notification_address(struct snd_dice *dice, bool retry) |
|
{ |
|
struct fw_device *device = fw_parent_device(dice->unit); |
|
__be64 *buffer; |
|
unsigned int retries; |
|
int err; |
|
|
|
retries = (retry) ? 3 : 0; |
|
|
|
buffer = kmalloc(2 * 8, GFP_KERNEL); |
|
if (!buffer) |
|
return -ENOMEM; |
|
|
|
for (;;) { |
|
buffer[0] = cpu_to_be64(OWNER_NO_OWNER); |
|
buffer[1] = cpu_to_be64( |
|
((u64)device->card->node_id << OWNER_NODE_SHIFT) | |
|
dice->notification_handler.offset); |
|
|
|
dice->owner_generation = device->generation; |
|
smp_rmb(); /* node_id vs. generation */ |
|
err = snd_fw_transaction(dice->unit, TCODE_LOCK_COMPARE_SWAP, |
|
get_subaddr(dice, |
|
SND_DICE_ADDR_TYPE_GLOBAL, |
|
GLOBAL_OWNER), |
|
buffer, 2 * 8, |
|
FW_FIXED_GENERATION | |
|
dice->owner_generation); |
|
if (err == 0) { |
|
/* success */ |
|
if (buffer[0] == cpu_to_be64(OWNER_NO_OWNER)) |
|
break; |
|
/* The address seems to be already registered. */ |
|
if (buffer[0] == buffer[1]) |
|
break; |
|
|
|
dev_err(&dice->unit->device, |
|
"device is already in use\n"); |
|
err = -EBUSY; |
|
} |
|
if (err != -EAGAIN || retries-- > 0) |
|
break; |
|
|
|
msleep(20); |
|
} |
|
|
|
kfree(buffer); |
|
|
|
if (err < 0) |
|
dice->owner_generation = -1; |
|
|
|
return err; |
|
} |
|
|
|
static void unregister_notification_address(struct snd_dice *dice) |
|
{ |
|
struct fw_device *device = fw_parent_device(dice->unit); |
|
__be64 *buffer; |
|
|
|
buffer = kmalloc(2 * 8, GFP_KERNEL); |
|
if (buffer == NULL) |
|
return; |
|
|
|
buffer[0] = cpu_to_be64( |
|
((u64)device->card->node_id << OWNER_NODE_SHIFT) | |
|
dice->notification_handler.offset); |
|
buffer[1] = cpu_to_be64(OWNER_NO_OWNER); |
|
snd_fw_transaction(dice->unit, TCODE_LOCK_COMPARE_SWAP, |
|
get_subaddr(dice, SND_DICE_ADDR_TYPE_GLOBAL, |
|
GLOBAL_OWNER), |
|
buffer, 2 * 8, FW_QUIET | |
|
FW_FIXED_GENERATION | dice->owner_generation); |
|
|
|
kfree(buffer); |
|
|
|
dice->owner_generation = -1; |
|
} |
|
|
|
void snd_dice_transaction_destroy(struct snd_dice *dice) |
|
{ |
|
struct fw_address_handler *handler = &dice->notification_handler; |
|
|
|
if (handler->callback_data == NULL) |
|
return; |
|
|
|
unregister_notification_address(dice); |
|
|
|
fw_core_remove_address_handler(handler); |
|
handler->callback_data = NULL; |
|
} |
|
|
|
int snd_dice_transaction_reinit(struct snd_dice *dice) |
|
{ |
|
struct fw_address_handler *handler = &dice->notification_handler; |
|
|
|
if (handler->callback_data == NULL) |
|
return -EINVAL; |
|
|
|
return register_notification_address(dice, false); |
|
} |
|
|
|
static int get_subaddrs(struct snd_dice *dice) |
|
{ |
|
static const int min_values[10] = { |
|
10, 0x60 / 4, |
|
10, 0x18 / 4, |
|
10, 0x18 / 4, |
|
0, 0, |
|
0, 0, |
|
}; |
|
__be32 *pointers; |
|
__be32 version; |
|
u32 data; |
|
unsigned int i; |
|
int err; |
|
|
|
pointers = kmalloc_array(ARRAY_SIZE(min_values), sizeof(__be32), |
|
GFP_KERNEL); |
|
if (pointers == NULL) |
|
return -ENOMEM; |
|
|
|
/* |
|
* Check that the sub address spaces exist and are located inside the |
|
* private address space. The minimum values are chosen so that all |
|
* minimally required registers are included. |
|
*/ |
|
err = snd_fw_transaction(dice->unit, TCODE_READ_BLOCK_REQUEST, |
|
DICE_PRIVATE_SPACE, pointers, |
|
sizeof(__be32) * ARRAY_SIZE(min_values), 0); |
|
if (err < 0) |
|
goto end; |
|
|
|
for (i = 0; i < ARRAY_SIZE(min_values); ++i) { |
|
data = be32_to_cpu(pointers[i]); |
|
if (data < min_values[i] || data >= 0x40000) { |
|
err = -ENODEV; |
|
goto end; |
|
} |
|
} |
|
|
|
if (be32_to_cpu(pointers[1]) > 0x18) { |
|
/* |
|
* Check that the implemented DICE driver specification major |
|
* version number matches. |
|
*/ |
|
err = snd_fw_transaction(dice->unit, TCODE_READ_QUADLET_REQUEST, |
|
DICE_PRIVATE_SPACE + |
|
be32_to_cpu(pointers[0]) * 4 + GLOBAL_VERSION, |
|
&version, sizeof(version), 0); |
|
if (err < 0) |
|
goto end; |
|
|
|
if ((version & cpu_to_be32(0xff000000)) != |
|
cpu_to_be32(0x01000000)) { |
|
dev_err(&dice->unit->device, |
|
"unknown DICE version: 0x%08x\n", |
|
be32_to_cpu(version)); |
|
err = -ENODEV; |
|
goto end; |
|
} |
|
|
|
/* Set up later. */ |
|
dice->clock_caps = 1; |
|
} |
|
|
|
dice->global_offset = be32_to_cpu(pointers[0]) * 4; |
|
dice->tx_offset = be32_to_cpu(pointers[2]) * 4; |
|
dice->rx_offset = be32_to_cpu(pointers[4]) * 4; |
|
|
|
/* Old firmware doesn't support these fields. */ |
|
if (pointers[7]) |
|
dice->sync_offset = be32_to_cpu(pointers[6]) * 4; |
|
if (pointers[9]) |
|
dice->rsrv_offset = be32_to_cpu(pointers[8]) * 4; |
|
end: |
|
kfree(pointers); |
|
return err; |
|
} |
|
|
|
int snd_dice_transaction_init(struct snd_dice *dice) |
|
{ |
|
struct fw_address_handler *handler = &dice->notification_handler; |
|
int err; |
|
|
|
err = get_subaddrs(dice); |
|
if (err < 0) |
|
return err; |
|
|
|
/* Allocation callback in address space over host controller */ |
|
handler->length = 4; |
|
handler->address_callback = dice_notification; |
|
handler->callback_data = dice; |
|
err = fw_core_add_address_handler(handler, &fw_high_memory_region); |
|
if (err < 0) { |
|
handler->callback_data = NULL; |
|
return err; |
|
} |
|
|
|
/* Register the address space */ |
|
err = register_notification_address(dice, true); |
|
if (err < 0) { |
|
fw_core_remove_address_handler(handler); |
|
handler->callback_data = NULL; |
|
} |
|
|
|
return err; |
|
}
|
|
|