/*
* TC Applied Technologies Digital Interface Communications Engine driver
*
* Copyright (c) Clemens Ladisch <clemens@ladisch.de>
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include <linux/compat.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/firewire.h>
#include <linux/firewire-constants.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/wait.h>
#include <sound/control.h>
#include <sound/core.h>
#include <sound/firewire.h>
#include <sound/hwdep.h>
#include <sound/initval.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include "amdtp.h"
#include "iso-resources.h"
#include "lib.h"
#include "dice-interface.h"
struct dice {
struct snd_card *card;
struct fw_unit *unit;
spinlock_t lock;
struct mutex mutex;
unsigned int global_offset;
unsigned int rx_offset;
struct fw_address_handler notification_handler;
int owner_generation;
int dev_lock_count; /* > 0 driver, < 0 userspace */
bool dev_lock_changed;
bool global_enabled;
wait_queue_head_t hwdep_wait;
u32 notification_bits;
struct fw_iso_resources resources;
struct amdtp_out_stream stream;
};
MODULE_DESCRIPTION("DICE driver");
MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
MODULE_LICENSE("GPL v2");
static const unsigned int dice_rates[] = {
[0] = 32000,
[1] = 44100,
[2] = 48000,
[3] = 88200,
[4] = 96000,
[5] = 176400,
[6] = 192000,
};
static void dice_lock_changed(struct dice *dice)
{
dice->dev_lock_changed = true;
wake_up(&dice->hwdep_wait);
}
static int dice_try_lock(struct dice *dice)
{
int err;
spin_lock_irq(&dice->lock);
if (dice->dev_lock_count < 0) {
err = -EBUSY;
goto out;
}
if (dice->dev_lock_count++ == 0)
dice_lock_changed(dice);
err = 0;
out:
spin_unlock_irq(&dice->lock);
return err;
}
static void dice_unlock(struct dice *dice)
{
spin_lock_irq(&dice->lock);
if (WARN_ON(dice->dev_lock_count <= 0))
goto out;
if (--dice->dev_lock_count == 0)
dice_lock_changed(dice);
out:
spin_unlock_irq(&dice->lock);
}
static inline u64 global_address(struct dice *dice, unsigned int offset)
{
return DICE_PRIVATE_SPACE + dice->global_offset + offset;
}
// TODO: rx index
static inline u64 rx_address(struct dice *dice, unsigned int offset)
{
return DICE_PRIVATE_SPACE + dice->rx_offset + offset;
}
static int dice_owner_set(struct dice *dice)
{
struct fw_device *device = fw_parent_device(dice->unit);
__be64 *buffer;
int err, errors = 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,
global_address(dice, GLOBAL_OWNER),
buffer, 2 * 8,
FW_FIXED_GENERATION |
dice->owner_generation);
if (err == 0) {
if (buffer[0] != cpu_to_be64(OWNER_NO_OWNER))