418 lines
9.7 KiB
C
418 lines
9.7 KiB
C
/*
|
|
* dice_transaction.c - a part of driver for Dice based devices
|
|
*
|
|
* Copyright (c) Clemens Ladisch
|
|
* Copyright (c) 2014 Takashi Sakamoto
|
|
*
|
|
* Licensed under the terms of the GNU General Public License, version 2.
|
|
*/
|
|
|
|
#include "dice.h"
|
|
|
|
#define NOTIFICATION_TIMEOUT_MS (2 * MSEC_PER_SEC)
|
|
|
|
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);
|
|
}
|
|
|
|
static int set_clock_info(struct snd_dice *dice,
|
|
unsigned int rate, unsigned int source)
|
|
{
|
|
unsigned int i;
|
|
__be32 info;
|
|
u32 mask;
|
|
u32 clock;
|
|
int err;
|
|
|
|
err = get_clock_info(dice, &info);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
clock = be32_to_cpu(info);
|
|
if (source != UINT_MAX) {
|
|
mask = CLOCK_SOURCE_MASK;
|
|
clock &= ~mask;
|
|
clock |= source;
|
|
}
|
|
if (rate != UINT_MAX) {
|
|
for (i = 0; i < ARRAY_SIZE(snd_dice_rates); i++) {
|
|
if (snd_dice_rates[i] == rate)
|
|
break;
|
|
}
|
|
if (i == ARRAY_SIZE(snd_dice_rates))
|
|
return -EINVAL;
|
|
|
|
mask = CLOCK_RATE_MASK;
|
|
clock &= ~mask;
|
|
clock |= i << CLOCK_RATE_SHIFT;
|
|
}
|
|
info = cpu_to_be32(clock);
|
|
|
|
if (completion_done(&dice->clock_accepted))
|
|
reinit_completion(&dice->clock_accepted);
|
|
|
|
err = snd_dice_transaction_write_global(dice, GLOBAL_CLOCK_SELECT,
|
|
&info, 4);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
if (wait_for_completion_timeout(&dice->clock_accepted,
|
|
msecs_to_jiffies(NOTIFICATION_TIMEOUT_MS)) == 0)
|
|
return -ETIMEDOUT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
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_rate(struct snd_dice *dice, unsigned int rate)
|
|
{
|
|
return set_clock_info(dice, rate, UINT_MAX);
|
|
}
|
|
|
|
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_CLOCK_ACCEPTED)
|
|
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, 0x64 / 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;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
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;
|
|
dice->sync_offset = be32_to_cpu(pointers[6]) * 4;
|
|
dice->rsrv_offset = be32_to_cpu(pointers[8]) * 4;
|
|
|
|
/* Set up later. */
|
|
if (be32_to_cpu(pointers[1]) * 4 >= GLOBAL_CLOCK_CAPABILITIES + 4)
|
|
dice->clock_caps = 1;
|
|
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;
|
|
}
|