forked from crt0mega/sixfireusb-dkms-debian
648 lines
18 KiB
C
648 lines
18 KiB
C
|
/*
|
||
|
* Linux driver for TerraTec DMX 6Fire USB
|
||
|
*
|
||
|
* URBS manager: manages usb stream
|
||
|
*
|
||
|
* Author: Torsten Schenk <torsten.schenk@zoho.com>
|
||
|
* Created: Jan 01, 2011
|
||
|
* Copyright: (C) Torsten Schenk
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU General Public License as published by
|
||
|
* the Free Software Foundation; either version 2 of the License, or
|
||
|
* (at your option) any later version.
|
||
|
*/
|
||
|
|
||
|
#include "urbs.h"
|
||
|
#include "chip.h"
|
||
|
#include "control.h"
|
||
|
#include "rates.h"
|
||
|
|
||
|
static struct urbs_runtime *runtimes[SNDRV_CARDS];
|
||
|
static DEFINE_SPINLOCK(runtimes_lock);
|
||
|
|
||
|
enum {
|
||
|
STREAM_DISABLED, /* writer: pcm calls (start(), stop())*/
|
||
|
STREAM_STOPPING, /* writer: tasklet */
|
||
|
STREAM_STOPPED, /* writer: pcm calls (stop()) */
|
||
|
STREAM_STARTING, /* writer: out urb retire */
|
||
|
STREAM_STARTED, /* writer: pcm calls (start()) */
|
||
|
STREAM_ENABLED /* writer: pcm calls (start(), stop())*/
|
||
|
};
|
||
|
|
||
|
enum {
|
||
|
OUT_EP = 6,
|
||
|
IN_EP = 2,
|
||
|
ISOPACKET_HEADER_SIZE = 4,
|
||
|
MAX_PACKETS_PER_URB = 512
|
||
|
};
|
||
|
|
||
|
static void usb6fire_urbs_enqueue(struct urbs_queue *queue, struct urb *urb)
|
||
|
{
|
||
|
int node = atomic_read(&queue->tail);
|
||
|
|
||
|
queue->node[node] = urb;
|
||
|
node++;
|
||
|
node %= URBS_COUNT + 1;
|
||
|
atomic_set(&queue->tail, node);
|
||
|
}
|
||
|
|
||
|
static struct urb *usb6fire_urbs_dequeue(struct urbs_queue *queue)
|
||
|
{
|
||
|
int node = atomic_read(&queue->head);
|
||
|
struct urb *urb = NULL;
|
||
|
|
||
|
if(node != atomic_read(&queue->tail)) {
|
||
|
urb = queue->node[node];
|
||
|
node++;
|
||
|
node %= URBS_COUNT + 1;
|
||
|
atomic_set(&queue->head, node);
|
||
|
}
|
||
|
return urb;
|
||
|
}
|
||
|
|
||
|
static void usb6fire_urbs_clearqueue(struct urbs_queue *queue)
|
||
|
{
|
||
|
atomic_set(&queue->head, 0);
|
||
|
atomic_set(&queue->tail, 0);
|
||
|
}
|
||
|
|
||
|
static void usb6fire_urbs_init_out_header(
|
||
|
struct usb_iso_packet_descriptor *packets, int p, u8 *buffer,
|
||
|
int preframe_count, int frame_count, int frame_size)
|
||
|
{
|
||
|
struct usb_iso_packet_descriptor *packet = packets + p;
|
||
|
|
||
|
packet->offset = p * ISOPACKET_HEADER_SIZE + preframe_count * frame_size;
|
||
|
packet->length = ISOPACKET_HEADER_SIZE + frame_count * frame_size;
|
||
|
packet->status = 0;
|
||
|
packet->actual_length = 0;
|
||
|
buffer += packet->offset;
|
||
|
buffer[0] = 0xaa;
|
||
|
buffer[1] = 0xaa;
|
||
|
buffer[2] = frame_count;
|
||
|
buffer[3] = 0x00;
|
||
|
}
|
||
|
|
||
|
static void usb6fire_urbs_process_sub(struct urbs_substream *sub, struct urb *urb)
|
||
|
{
|
||
|
int p;
|
||
|
int c;
|
||
|
u8 *buffer;
|
||
|
int frame_count;
|
||
|
|
||
|
for (p = 0; p < urb->number_of_packets; p++) {
|
||
|
buffer = urb->transfer_buffer + urb->iso_frame_desc[p].offset;
|
||
|
frame_count = buffer[2];
|
||
|
buffer += ISOPACKET_HEADER_SIZE;
|
||
|
c = 0;
|
||
|
if (sub->active && sub->state == URBS_SUBSTATE_ENABLED)
|
||
|
for (; c < sub->channels; c++)
|
||
|
sub->copier[c].exec(sub->copier + c, buffer, frame_count);
|
||
|
for (; c < sub->max_channels; c++)
|
||
|
sub->muter[c].exec(sub->muter + c, buffer, frame_count);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void usb6fire_urbs_process(struct urbs_runtime *rt, struct urb *in_urb)
|
||
|
{
|
||
|
int frame_count;
|
||
|
int p;
|
||
|
int s;
|
||
|
int state;
|
||
|
u8 *si;
|
||
|
struct urbs_substream *sub;
|
||
|
struct urb *out_urb;
|
||
|
unsigned long flags;
|
||
|
int total_frame_count = 0;
|
||
|
|
||
|
state = atomic_read(&rt->state);
|
||
|
if (state == STREAM_DISABLED || state == STREAM_STOPPING)
|
||
|
return;
|
||
|
|
||
|
for (s = 0; s < rt->n_substreams; s++) {
|
||
|
sub = rt->substreams + s;
|
||
|
if (sub->active != sub->trigger_active) {
|
||
|
sub->active = sub->trigger_active;
|
||
|
sub->resetter.exec(&sub->resetter, rt->rate);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (in_urb->status)
|
||
|
return;
|
||
|
else {
|
||
|
for (p = 0; p < in_urb->number_of_packets; p++)
|
||
|
if(in_urb->iso_frame_desc[p].status)
|
||
|
break;
|
||
|
if (p != in_urb->number_of_packets)
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
out_urb = usb6fire_urbs_dequeue(&rt->out_queue);
|
||
|
|
||
|
if (out_urb) {
|
||
|
if (out_urb->number_of_packets != in_urb->number_of_packets)
|
||
|
snd_printk(KERN_WARNING PREFIX "Packet count of out/in urbs differ: "
|
||
|
"o = %d, i = %d", out_urb->number_of_packets,
|
||
|
in_urb->number_of_packets);
|
||
|
else if (state == STREAM_ENABLED) {
|
||
|
for (p = 0; p < in_urb->number_of_packets; p++) {
|
||
|
if (in_urb->iso_frame_desc[p].actual_length > ISOPACKET_HEADER_SIZE)
|
||
|
frame_count = (in_urb->iso_frame_desc[p].actual_length - ISOPACKET_HEADER_SIZE) / rt->in_frame_size;
|
||
|
else
|
||
|
frame_count = 0;
|
||
|
si = in_urb->transfer_buffer + in_urb->iso_frame_desc[p].offset + 2;
|
||
|
*si = frame_count;
|
||
|
|
||
|
usb6fire_urbs_init_out_header(out_urb->iso_frame_desc, p, out_urb->transfer_buffer, total_frame_count, frame_count, rt->out_frame_size);
|
||
|
total_frame_count += frame_count;
|
||
|
}
|
||
|
|
||
|
for (s = 0; s < rt->n_substreams; s++) {
|
||
|
sub = rt->substreams + s;
|
||
|
spin_lock_irqsave(&sub->lock, flags);
|
||
|
if (sub->in)
|
||
|
usb6fire_urbs_process_sub(sub, in_urb);
|
||
|
else
|
||
|
usb6fire_urbs_process_sub(sub, out_urb);
|
||
|
if (sub->active && sub->state == URBS_SUBSTATE_ENABLED) {
|
||
|
sub->dma_off += total_frame_count;
|
||
|
sub->dma_off %= sub->dma_size;
|
||
|
atomic_set(&sub->dma_off_public, sub->dma_off);
|
||
|
sub->period_off += total_frame_count;
|
||
|
if (sub->period_off >= sub->period_size) {
|
||
|
sub->period_off %= sub->period_size;
|
||
|
spin_unlock_irqrestore(&sub->lock, flags);
|
||
|
snd_pcm_period_elapsed(sub->alsa_sub);
|
||
|
}
|
||
|
else
|
||
|
spin_unlock_irqrestore(&sub->lock, flags);
|
||
|
}
|
||
|
else
|
||
|
spin_unlock_irqrestore(&sub->lock, flags);
|
||
|
}
|
||
|
}
|
||
|
else if (state == STREAM_STARTING) {
|
||
|
for (p = 0; p < in_urb->number_of_packets; p++) {
|
||
|
si = in_urb->transfer_buffer + in_urb->iso_frame_desc[p].offset;
|
||
|
usb6fire_urbs_init_out_header(out_urb->iso_frame_desc, p, out_urb->transfer_buffer, 0, 0, 0);
|
||
|
}
|
||
|
}
|
||
|
else if (state == STREAM_STARTED) {
|
||
|
for (p = 0; p < in_urb->number_of_packets; p++) {
|
||
|
si = in_urb->transfer_buffer + in_urb->iso_frame_desc[p].offset;
|
||
|
usb6fire_urbs_init_out_header(out_urb->iso_frame_desc, p, out_urb->transfer_buffer, 0, 0, 0);
|
||
|
}
|
||
|
wake_up(&rt->stream_wait_queue);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (out_urb)
|
||
|
usb_submit_urb(out_urb, GFP_ATOMIC);
|
||
|
usb_submit_urb(in_urb, GFP_ATOMIC);
|
||
|
}
|
||
|
|
||
|
static void usb6fire_urbs_direct_in_retire(struct urb *urb)
|
||
|
{
|
||
|
struct urbs_runtime *rt = urb->context;
|
||
|
|
||
|
usb6fire_urbs_process(rt, urb);
|
||
|
}
|
||
|
|
||
|
static void usb6fire_urbs_direct_out_retire(struct urb *urb)
|
||
|
{
|
||
|
struct urbs_runtime *rt = urb->context;
|
||
|
|
||
|
if (atomic_read(&rt->state) == STREAM_STARTING)
|
||
|
atomic_set(&rt->state, STREAM_STARTED);
|
||
|
usb6fire_urbs_enqueue(&rt->out_queue, urb);
|
||
|
}
|
||
|
|
||
|
static void usb6fire_urbs_tasklet_in_retire(struct urb *urb)
|
||
|
{
|
||
|
struct urbs_runtime *rt = urb->context;
|
||
|
|
||
|
usb6fire_urbs_enqueue(&rt->in_queue, urb);
|
||
|
tasklet_schedule(&rt->tasklet);
|
||
|
}
|
||
|
|
||
|
static void usb6fire_urbs_tasklet_out_retire(struct urb *urb)
|
||
|
{
|
||
|
struct urbs_runtime *rt = urb->context;
|
||
|
|
||
|
if (atomic_read(&rt->state) == STREAM_STARTING)
|
||
|
atomic_set(&rt->state, STREAM_STARTED);
|
||
|
usb6fire_urbs_enqueue(&rt->out_queue, urb);
|
||
|
tasklet_schedule(&rt->tasklet);
|
||
|
}
|
||
|
|
||
|
static void usb6fire_urbs_tasklet(unsigned long regidx)
|
||
|
{
|
||
|
struct urbs_runtime *rt;
|
||
|
struct urb *urb;
|
||
|
|
||
|
spin_lock(&runtimes_lock);
|
||
|
rt = runtimes[regidx];
|
||
|
spin_unlock(&runtimes_lock);
|
||
|
if (!rt)
|
||
|
return;
|
||
|
|
||
|
while ((urb = usb6fire_urbs_dequeue(&rt->in_queue)))
|
||
|
usb6fire_urbs_process(rt, urb);
|
||
|
}
|
||
|
|
||
|
static int usb6fire_urbs_set_rate(struct urbs_runtime *rt, unsigned int rate_id)
|
||
|
{
|
||
|
int ret;
|
||
|
struct control_runtime *ctrl_rt = rt->chip->control;
|
||
|
|
||
|
ctrl_rt->usb_streaming = false;
|
||
|
ret = ctrl_rt->update_streaming(ctrl_rt);
|
||
|
if (ret < 0) {
|
||
|
snd_printk(KERN_ERR PREFIX "error stopping streaming while "
|
||
|
"setting samplerate %d.\n", rates[rate_id]);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ret = ctrl_rt->set_rate(ctrl_rt, rates[rate_id]);
|
||
|
if (ret < 0) {
|
||
|
snd_printk(KERN_ERR PREFIX "error setting samplerate %d.\n", rates[rate_id]);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ctrl_rt->usb_streaming = true;
|
||
|
ret = ctrl_rt->update_streaming(ctrl_rt);
|
||
|
if (ret < 0) {
|
||
|
snd_printk(KERN_ERR PREFIX "error starting streaming while "
|
||
|
"setting samplerate %d.\n", rates[rate_id]);
|
||
|
return ret;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void usb6fire_urbs_stop(struct urbs_runtime *rt)
|
||
|
{
|
||
|
int u;
|
||
|
|
||
|
atomic_set(&rt->state, STREAM_STOPPING);
|
||
|
for (u = 0; u < URBS_COUNT; u++) {
|
||
|
usb_kill_urb(rt->in_urbs[u]);
|
||
|
usb_kill_urb(rt->out_urbs[u]);
|
||
|
}
|
||
|
if (rt->tasklet_enabled) {
|
||
|
tasklet_disable(&rt->tasklet);
|
||
|
rt->tasklet_enabled = false;
|
||
|
}
|
||
|
atomic_set(&rt->state, STREAM_DISABLED);
|
||
|
rt->packets_per_urb = 0;
|
||
|
rt->rate = 0;
|
||
|
}
|
||
|
|
||
|
static int usb6fire_urbs_modify(struct urb *urb, unsigned int packet_size,
|
||
|
unsigned int packet_count, bool use_tasklet, bool in)
|
||
|
{
|
||
|
unsigned int size = packet_size * packet_count;
|
||
|
|
||
|
if (use_tasklet)
|
||
|
if (in)
|
||
|
urb->complete = usb6fire_urbs_tasklet_in_retire;
|
||
|
else
|
||
|
urb->complete = usb6fire_urbs_tasklet_out_retire;
|
||
|
else if (in)
|
||
|
urb->complete = usb6fire_urbs_direct_in_retire;
|
||
|
else
|
||
|
urb->complete = usb6fire_urbs_direct_out_retire;
|
||
|
|
||
|
if (urb->transfer_buffer && urb->transfer_buffer_length < size) {
|
||
|
kfree(urb->transfer_buffer);
|
||
|
urb->transfer_buffer = NULL;
|
||
|
urb->transfer_buffer_length = 0;
|
||
|
}
|
||
|
if (!urb->transfer_buffer) {
|
||
|
urb->transfer_buffer = kmalloc(size, GFP_KERNEL);
|
||
|
if (!urb->transfer_buffer) {
|
||
|
snd_printk(KERN_ERR PREFIX "Error allocating urb buffer of size %d:"
|
||
|
"not enough memory.", size);
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
urb->transfer_buffer_length = size;
|
||
|
}
|
||
|
urb->number_of_packets = packet_count;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int usb6fire_urbs_start(struct urbs_runtime *rt, unsigned int rate_id,
|
||
|
unsigned int period_size)
|
||
|
{
|
||
|
int u;
|
||
|
int p;
|
||
|
struct usb_iso_packet_descriptor *packet;
|
||
|
int ret;
|
||
|
unsigned int packets_per_urb;
|
||
|
|
||
|
if (rate_id >= ARRAY_SIZE(rates)) {
|
||
|
snd_printk(KERN_ERR PREFIX "Invalid rate selected. ID: %d.\n", rate_id);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
atomic_set(&rt->state, STREAM_STARTING);
|
||
|
usb6fire_urbs_clearqueue(&rt->in_queue);
|
||
|
usb6fire_urbs_clearqueue(&rt->out_queue);
|
||
|
packets_per_urb = period_size / rates_max_fpp[rate_id];
|
||
|
if (!packets_per_urb) {
|
||
|
snd_printk(KERN_ERR PREFIX "Invalid period size %d selected for rate %d.\n",
|
||
|
period_size, rates[rate_id]);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
if (packets_per_urb > MAX_PACKETS_PER_URB)
|
||
|
packets_per_urb = MAX_PACKETS_PER_URB;
|
||
|
rt->use_tasklet = rt->chip->tasklet_thresh
|
||
|
&& packets_per_urb >= rt->chip->tasklet_thresh;
|
||
|
for (u = 0; u < URBS_COUNT; u++) {
|
||
|
ret = usb6fire_urbs_modify(rt->out_urbs[u],
|
||
|
rates_out_packet_size[rate_id], packets_per_urb, rt->use_tasklet, false);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
ret = usb6fire_urbs_modify(rt->in_urbs[u],
|
||
|
rates_in_packet_size[rate_id], packets_per_urb, rt->use_tasklet, true);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
if (rt->use_tasklet && !rt->tasklet_enabled) {
|
||
|
tasklet_enable(&rt->tasklet);
|
||
|
rt->tasklet_enabled = true;
|
||
|
}
|
||
|
for (u = 0; u < URBS_COUNT; u++)
|
||
|
usb6fire_urbs_enqueue(&rt->out_queue, rt->out_urbs[u]);
|
||
|
for (u = 0; u < URBS_COUNT; u++) {
|
||
|
for (p = 0; p < packets_per_urb; p++) {
|
||
|
packet = rt->in_urbs[u]->iso_frame_desc + p;
|
||
|
packet->offset = p * rates_in_packet_size[rate_id];
|
||
|
packet->length = rates_in_packet_size[rate_id];
|
||
|
packet->actual_length = 0;
|
||
|
packet->status = 0;
|
||
|
}
|
||
|
ret = usb_submit_urb(rt->in_urbs[u],
|
||
|
GFP_ATOMIC);
|
||
|
if (ret) {
|
||
|
snd_printk(KERN_ERR PREFIX "Error %d while starting stream.\n", ret);
|
||
|
atomic_set(&rt->state, STREAM_DISABLED);
|
||
|
for (u = 0; u < URBS_COUNT; u++) {
|
||
|
usb_kill_urb(rt->in_urbs[u]);
|
||
|
usb_kill_urb(rt->out_urbs[u]);
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
}
|
||
|
wait_event_timeout(rt->stream_wait_queue,
|
||
|
atomic_read(&rt->state) == STREAM_STARTED,
|
||
|
msecs_to_jiffies(1000));
|
||
|
if (atomic_read(&rt->state) != STREAM_STARTED) {
|
||
|
snd_printk("Error while starting stream: timeout.\n");
|
||
|
usb6fire_urbs_stop(rt);
|
||
|
return -EIO;
|
||
|
}
|
||
|
else {
|
||
|
rt->packets_per_urb = packets_per_urb;
|
||
|
rt->rate = rates[rate_id];
|
||
|
atomic_set(&rt->state, STREAM_ENABLED);
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int usb6fire_urbs_get_min_psize(struct urbs_runtime *rt, unsigned int rate)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
if (rt->packets_per_urb) {
|
||
|
for (i = 0; i < ARRAY_SIZE(rates); i++)
|
||
|
if (rates[i] == rt->rate)
|
||
|
return rt->packets_per_urb * rates_max_fpp[i];
|
||
|
} else
|
||
|
for (i = 0; i < ARRAY_SIZE(rates); i++)
|
||
|
if (rates[i] == rate)
|
||
|
return rates_max_fpp[i];
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int usb6fire_urbs_set_state(struct urbs_runtime *rt, struct substream_descriptor *desc, int state)
|
||
|
{
|
||
|
struct urbs_substream *sub = rt->substreams + desc->index;
|
||
|
struct substream_runtime *sub_rt = rt->chip->substream;
|
||
|
int ret;
|
||
|
int i;
|
||
|
unsigned long flags;
|
||
|
unsigned int rate_id;
|
||
|
|
||
|
switch(state)
|
||
|
{
|
||
|
case URBS_SUBSTATE_ENABLED:
|
||
|
for (rate_id = 0; rate_id < ARRAY_SIZE(rates); rate_id++)
|
||
|
if (rates[rate_id] == desc->alsa_sub->runtime->rate)
|
||
|
break;
|
||
|
if (rate_id == ARRAY_SIZE(rates))
|
||
|
return -EINVAL;
|
||
|
if (rt->substreams[desc->index].state == URBS_SUBSTATE_DISABLED) {
|
||
|
if (atomic_read(&rt->state) == STREAM_DISABLED) {
|
||
|
ret = usb6fire_urbs_set_rate(rt, rate_id);
|
||
|
if (ret < 0) {
|
||
|
snd_printk(KERN_ERR PREFIX "error setting rate %d.\n",
|
||
|
desc->alsa_sub->runtime->rate);
|
||
|
return ret;
|
||
|
}
|
||
|
ret = usb6fire_urbs_start(rt, rate_id,
|
||
|
desc->alsa_sub->runtime->period_size);
|
||
|
if (ret < 0) {
|
||
|
snd_printk(KERN_ERR PREFIX "error starting usb stream.\n");
|
||
|
return ret;
|
||
|
}
|
||
|
} else if (rt->rate != desc->alsa_sub->runtime->rate) {
|
||
|
snd_printk(KERN_ERR PREFIX "error setting rate: device busy.\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
spin_lock_irqsave(&sub->lock, flags);
|
||
|
sub->state = URBS_SUBSTATE_ENABLED;
|
||
|
sub->channels = desc->alsa_sub->runtime->channels;
|
||
|
sub->active = false;
|
||
|
sub->trigger_active = false;
|
||
|
sub->dma_off = 0;
|
||
|
sub->period_off = 0;
|
||
|
sub->dma_size = desc->alsa_sub->runtime->buffer_size;
|
||
|
sub->period_size = desc->alsa_sub->runtime->period_size;
|
||
|
atomic_set(&sub->dma_off_public, 0);
|
||
|
sub->alsa_sub = desc->alsa_sub;
|
||
|
for (i = 0; i < sub->channels; i++)
|
||
|
sub->copier[i] = sub->get_copier(sub_rt, desc, i);
|
||
|
spin_unlock_irqrestore(&sub->lock, flags);
|
||
|
break;
|
||
|
|
||
|
case URBS_SUBSTATE_DISABLED:
|
||
|
if (rt->substreams[desc->index].state != URBS_SUBSTATE_DISABLED) {
|
||
|
spin_lock_irqsave(&sub->lock, flags);
|
||
|
sub->channels = 0;
|
||
|
sub->state = URBS_SUBSTATE_DISABLED;
|
||
|
sub->active = false;
|
||
|
sub->trigger_active = false;
|
||
|
sub->alsa_sub = NULL;
|
||
|
spin_unlock_irqrestore(&sub->lock, flags);
|
||
|
|
||
|
for (i = 0; i < rt->n_substreams; i++)
|
||
|
if (rt->substreams[i].state != URBS_SUBSTATE_DISABLED)
|
||
|
break;
|
||
|
if (i == rt->n_substreams)
|
||
|
usb6fire_urbs_stop(rt);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int usb6fire_urbs_set_active(struct urbs_runtime *rt, struct substream_descriptor *desc, bool active)
|
||
|
{
|
||
|
struct urbs_substream *sub = rt->substreams + desc->index;
|
||
|
|
||
|
sub->trigger_active = active;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static snd_pcm_uframes_t usb6fire_urbs_get_pointer(struct urbs_runtime *rt,
|
||
|
struct substream_descriptor *desc)
|
||
|
{
|
||
|
struct urbs_substream *sub = rt->substreams + desc->index;
|
||
|
|
||
|
return atomic_read(&sub->dma_off_public);
|
||
|
}
|
||
|
|
||
|
static struct urb *usb6fire_urbs_create(struct urbs_runtime *rt,
|
||
|
bool in, int ep)
|
||
|
{
|
||
|
struct urb *urb = usb_alloc_urb(MAX_PACKETS_PER_URB, GFP_KERNEL);
|
||
|
urb->dev = rt->chip->dev;
|
||
|
urb->pipe = in ? usb_rcvisocpipe(urb->dev, ep)
|
||
|
: usb_sndisocpipe(urb->dev, ep);
|
||
|
urb->interval = 1;
|
||
|
urb->transfer_flags = URB_ISO_ASAP;
|
||
|
urb->context = rt;
|
||
|
|
||
|
return urb;
|
||
|
}
|
||
|
|
||
|
int usb6fire_urbs_init(struct sfire_chip *chip)
|
||
|
{
|
||
|
int i;
|
||
|
int k;
|
||
|
struct substream_runtime *sub_rt = chip->substream;
|
||
|
struct substream_definition sub_def;
|
||
|
struct urbs_runtime *rt =
|
||
|
kzalloc(sizeof(struct urbs_runtime), GFP_KERNEL);
|
||
|
unsigned long flags;
|
||
|
|
||
|
if(!rt)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
spin_lock_irqsave(&runtimes_lock, flags);
|
||
|
runtimes[chip->regidx] = rt;
|
||
|
spin_unlock_irqrestore(&runtimes_lock, flags);
|
||
|
|
||
|
rt->chip = chip;
|
||
|
init_waitqueue_head(&rt->stream_wait_queue);
|
||
|
atomic_set(&rt->state, STREAM_DISABLED);
|
||
|
|
||
|
rt->n_substreams = sub_rt->n_substreams;
|
||
|
for (i = 0; i < rt->n_substreams; i++) {
|
||
|
rt->substreams[i].state = URBS_SUBSTATE_DISABLED;
|
||
|
spin_lock_init(&rt->substreams[i].lock);
|
||
|
sub_def = sub_rt->get_definition(sub_rt, i);
|
||
|
rt->substreams[i].in = sub_def.in;
|
||
|
rt->substreams[i].max_channels = sub_def.max_channels;
|
||
|
rt->substreams[i].copier = kzalloc(sub_def.max_channels * sizeof(struct substream_copier), GFP_KERNEL);
|
||
|
rt->substreams[i].muter = kzalloc(sub_def.max_channels * sizeof(struct substream_muter), GFP_KERNEL);
|
||
|
rt->substreams[i].get_copier = sub_def.get_copier;
|
||
|
rt->substreams[i].resetter = sub_def.get_resetter(sub_rt);
|
||
|
for (k = 0; k < sub_def.max_channels; k++)
|
||
|
rt->substreams[i].muter[k] = sub_def.get_muter(sub_rt, k);
|
||
|
}
|
||
|
|
||
|
for(i = 0; i < URBS_COUNT; i++) {
|
||
|
rt->in_urbs[i] = usb6fire_urbs_create(rt, true, IN_EP);
|
||
|
rt->out_urbs[i] = usb6fire_urbs_create(rt, false, OUT_EP);
|
||
|
}
|
||
|
|
||
|
if (chip->tasklet_thresh) {
|
||
|
tasklet_init(&rt->tasklet, usb6fire_urbs_tasklet, rt->chip->regidx);
|
||
|
rt->tasklet_enabled = true;
|
||
|
}
|
||
|
|
||
|
rt->in_frame_size = sub_rt->in_urb_frame_size;
|
||
|
rt->out_frame_size = sub_rt->out_urb_frame_size;
|
||
|
rt->set_state = usb6fire_urbs_set_state;
|
||
|
rt->set_active = usb6fire_urbs_set_active;
|
||
|
rt->get_pointer = usb6fire_urbs_get_pointer;
|
||
|
rt->get_min_period_size = usb6fire_urbs_get_min_psize;
|
||
|
|
||
|
chip->urbs = rt;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void usb6fire_urbs_abort(struct sfire_chip *chip)
|
||
|
{
|
||
|
struct urbs_runtime *rt = chip->urbs;
|
||
|
unsigned long flags;
|
||
|
int i;
|
||
|
|
||
|
spin_lock_irqsave(&runtimes_lock, flags);
|
||
|
runtimes[chip->regidx] = NULL;
|
||
|
spin_unlock_irqrestore(&runtimes_lock, flags);
|
||
|
|
||
|
if (rt) {
|
||
|
for (i = 0; i < rt->n_substreams; i++)
|
||
|
if (rt->substreams[i].alsa_sub) {
|
||
|
rt->substreams[i].active = false;
|
||
|
snd_pcm_stop(rt->substreams[i].alsa_sub,
|
||
|
SNDRV_PCM_STATE_DISCONNECTED);
|
||
|
}
|
||
|
usb6fire_urbs_stop(rt);
|
||
|
|
||
|
if (rt->chip->tasklet_thresh) {
|
||
|
if (!rt->tasklet_enabled) {
|
||
|
tasklet_enable(&rt->tasklet);
|
||
|
rt->tasklet_enabled = true;
|
||
|
}
|
||
|
tasklet_kill(&rt->tasklet);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void usb6fire_urbs_destroy(struct sfire_chip *chip)
|
||
|
{
|
||
|
int i;
|
||
|
struct urbs_runtime *rt = chip->urbs;
|
||
|
|
||
|
if (rt) {
|
||
|
for (i = 0; i < rt->n_substreams; i++) {
|
||
|
kfree(rt->substreams[i].copier);
|
||
|
kfree(rt->substreams[i].muter);
|
||
|
rt->substreams[i].copier = NULL;
|
||
|
rt->substreams[i].muter = NULL;
|
||
|
}
|
||
|
for (i = 0; i < URBS_COUNT; i++) {
|
||
|
if (rt->in_urbs[i]->transfer_buffer)
|
||
|
kfree(rt->in_urbs[i]->transfer_buffer);
|
||
|
if (rt->out_urbs[i]->transfer_buffer)
|
||
|
kfree(rt->out_urbs[i]->transfer_buffer);
|
||
|
rt->in_urbs[i]->transfer_buffer = NULL;
|
||
|
rt->out_urbs[i]->transfer_buffer = NULL;
|
||
|
usb_free_urb(rt->in_urbs[i]);
|
||
|
usb_free_urb(rt->out_urbs[i]);
|
||
|
rt->in_urbs[i] = NULL;
|
||
|
rt->out_urbs[i] = NULL;
|
||
|
}
|
||
|
kfree(rt);
|
||
|
}
|
||
|
chip->urbs = NULL;
|
||
|
}
|