forked from crt0mega/sixfireusb-dkms-debian
Initial commit
This commit is contained in:
commit
3cd3904bf8
|
@ -0,0 +1,12 @@
|
||||||
|
debian/debhelper*
|
||||||
|
debian/.debhelper
|
||||||
|
debian/*.log
|
||||||
|
debian/*.debhelper
|
||||||
|
debian/*.ex
|
||||||
|
debian/*.EX
|
||||||
|
debian/*.substvars
|
||||||
|
debian/debhelper-build-stamp
|
||||||
|
debian/sixfireusb-dkms/
|
||||||
|
debian/sixfireusb-dkms*
|
||||||
|
debian/files
|
||||||
|
.pc
|
|
@ -0,0 +1,6 @@
|
||||||
|
sixfireusb-dkms for Debian
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
You will need firmware files for this driver, so ...
|
||||||
|
|
||||||
|
-- crt0mega <crt0mega@c-r-t.tk> Tue, 24 Aug 2021 10:32:20 +0200
|
|
@ -0,0 +1,9 @@
|
||||||
|
sixfireusb-dkms for Debian
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
This package contains several patches for building the source on modern
|
||||||
|
systems. You can easily add new patches through quilt.
|
||||||
|
|
||||||
|
|
||||||
|
-- crt0mega <crt0mega@c-r-t.tk> Tue, 24 Aug 2021 10:32:20 +0200
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
sixfireusb-dkms (0.6.2-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Initial release (Closes: #nnnn) <nnnn is the bug number of your ITP>
|
||||||
|
|
||||||
|
-- crt0mega <crt0mega@c-r-t.tk> Tue, 24 Aug 2021 10:32:20 +0200
|
|
@ -0,0 +1,27 @@
|
||||||
|
Source: sixfireusb-dkms
|
||||||
|
Section: kernel
|
||||||
|
Priority: optional
|
||||||
|
Maintainer: crt0mega <crt0mega@c-r-t.tk>
|
||||||
|
Build-Depends: debhelper-compat (= 13)
|
||||||
|
Standards-Version: 4.5.1
|
||||||
|
Homepage: https://sourceforge.net/projects/sixfireusb/
|
||||||
|
#Vcs-Browser: https://salsa.debian.org/debian/sixfireusb-dkms
|
||||||
|
#Vcs-Git: https://salsa.debian.org/debian/sixfireusb-dkms.git
|
||||||
|
#Rules-Requires-Root: no
|
||||||
|
|
||||||
|
Package: sixfireusb-dkms
|
||||||
|
Architecture: any
|
||||||
|
Depends: ${misc:Depends}, dkms
|
||||||
|
Recommends: sixfireusb-fwcutter
|
||||||
|
Description: Linux driver for the USB box from TerraTec, DMX 6Fire USB.
|
||||||
|
This is the driver for the TerraTec DMX 6Fire USB. Current features include:
|
||||||
|
- Firmware uploading if device has been reconnected to power
|
||||||
|
- Playback and capture at 16, 24 and 32 bit, Samplerates 44100 to 192000,
|
||||||
|
6/4 analog channels
|
||||||
|
- Playback at 16, 24 and 32 bit, Samplerates 44100 to 96000, 2 digital channels
|
||||||
|
(no passthrough yet)
|
||||||
|
- Master volume in mixer
|
||||||
|
- Individual analog channel volumes in mixer
|
||||||
|
- Selection of line/phono capturing
|
||||||
|
- MIDI in/out
|
||||||
|
- Digital thru: device may act as converter coax <-> optical
|
|
@ -0,0 +1,39 @@
|
||||||
|
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||||
|
Upstream-Name: sixfireusb-dkms
|
||||||
|
Upstream-Contact: <preferred name and address to reach the upstream project>
|
||||||
|
Source: <url://example.com>
|
||||||
|
|
||||||
|
Files: *
|
||||||
|
Copyright: <years> <put author's name and email here>
|
||||||
|
<years> <likewise for another author>
|
||||||
|
License: GPL-2.0+
|
||||||
|
|
||||||
|
Files: debian/*
|
||||||
|
Copyright: 2021 crt0mega <crt0mega@c-r-t.tk>
|
||||||
|
License: GPL-2.0+
|
||||||
|
|
||||||
|
License: GPL-2.0+
|
||||||
|
This package 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.
|
||||||
|
.
|
||||||
|
This package is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
.
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>
|
||||||
|
.
|
||||||
|
On Debian systems, the complete text of the GNU General
|
||||||
|
Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".
|
||||||
|
|
||||||
|
# Please also look if there are files or directories which have a
|
||||||
|
# different copyright/license attached and list them here.
|
||||||
|
# Please avoid picking licenses with terms that are more restrictive than the
|
||||||
|
# packaged work, as it may make Debian's contributions unacceptable upstream.
|
||||||
|
#
|
||||||
|
# If you need, there are some extra license texts available in two places:
|
||||||
|
# /usr/share/debhelper/dh_make/licenses/
|
||||||
|
# /usr/share/common-licenses/
|
|
@ -0,0 +1,8 @@
|
||||||
|
MAKE="make"
|
||||||
|
CLEAN="make clean"
|
||||||
|
PACKAGE_NAME="sixfireusb"
|
||||||
|
PACKAGE_VERSION="@VERSION@"
|
||||||
|
BUILT_MODULE_NAME[0]="snd-usb-6fire"
|
||||||
|
DEST_MODULE_NAME[0]="snd-usb-6fire-dkms"
|
||||||
|
DEST_MODULE_LOCATION[0]="/kernel/sound/usb/6fire"
|
||||||
|
AUTOINSTALL="yes"
|
|
@ -0,0 +1,14 @@
|
||||||
|
Removed deprecated usage of MODULE_SUPPORTED_DEVICE, corrected module version.
|
||||||
|
--- a/src/chip.c
|
||||||
|
+++ b/src/chip.c
|
||||||
|
@@ -31,9 +31,8 @@
|
||||||
|
#include <sound/initval.h>
|
||||||
|
|
||||||
|
MODULE_AUTHOR("Torsten Schenk <torsten.schenk@zoho.com>");
|
||||||
|
-MODULE_DESCRIPTION("TerraTec DMX 6Fire USB audio driver, version 0.6.1");
|
||||||
|
+MODULE_DESCRIPTION("TerraTec DMX 6Fire USB audio driver, version 0.6.2");
|
||||||
|
MODULE_LICENSE("GPL v2");
|
||||||
|
-MODULE_SUPPORTED_DEVICE("{{TerraTec, DMX 6Fire USB}}");
|
||||||
|
|
||||||
|
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */
|
||||||
|
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for card */
|
|
@ -0,0 +1,12 @@
|
||||||
|
Fixing deprecated usage of snd_ctl_add_slave
|
||||||
|
--- a/src/control.c
|
||||||
|
+++ b/src/control.c
|
||||||
|
@@ -571,7 +571,7 @@
|
||||||
|
ret = snd_ctl_add(card, control);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
- ret = snd_ctl_add_slave(vmaster, control);
|
||||||
|
+ ret = snd_ctl_add_follower(vmaster, control);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
i++;
|
|
@ -0,0 +1,29 @@
|
||||||
|
"snd_pcm_lib_preallocate_pages_for_all" does not return any value anymore.
|
||||||
|
--- a/src/pcm.c
|
||||||
|
+++ b/src/pcm.c
|
||||||
|
@@ -202,7 +202,6 @@
|
||||||
|
{
|
||||||
|
struct pcm_runtime *rt = kzalloc(sizeof(struct pcm_runtime), GFP_KERNEL);
|
||||||
|
struct substream_runtime *sub_rt = chip->substream;
|
||||||
|
- int ret;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (!rt)
|
||||||
|
@@ -216,16 +215,10 @@
|
||||||
|
sub_rt->devices[i]->private_data = chip;
|
||||||
|
snd_pcm_set_ops(sub_rt->devices[i], SNDRV_PCM_STREAM_PLAYBACK, &pcm_ops);
|
||||||
|
snd_pcm_set_ops(sub_rt->devices[i], SNDRV_PCM_STREAM_CAPTURE, &pcm_ops);
|
||||||
|
- ret = snd_pcm_lib_preallocate_pages_for_all(sub_rt->devices[i],
|
||||||
|
+ snd_pcm_lib_preallocate_pages_for_all(sub_rt->devices[i],
|
||||||
|
SNDRV_DMA_TYPE_CONTINUOUS,
|
||||||
|
snd_dma_continuous_data(GFP_KERNEL),
|
||||||
|
MAX_BUFSIZE, MAX_BUFSIZE);
|
||||||
|
- if (ret) {
|
||||||
|
- kfree(rt);
|
||||||
|
- snd_printk(KERN_ERR PREFIX
|
||||||
|
- "error preallocating pcm buffers.\n");
|
||||||
|
- return ret;
|
||||||
|
- }
|
||||||
|
}
|
||||||
|
|
||||||
|
chip->pcm = rt;
|
|
@ -0,0 +1,3 @@
|
||||||
|
pcm.patch
|
||||||
|
chip.patch
|
||||||
|
control.patch
|
|
@ -0,0 +1,32 @@
|
||||||
|
#!/usr/bin/make -f
|
||||||
|
# See debhelper(7) (uncomment to enable)
|
||||||
|
# output every command that modifies files on the build system.
|
||||||
|
#export DH_VERBOSE = 1
|
||||||
|
VERSION=$(shell dpkg-parsechangelog |grep ^Version:|cut -d ' ' -f 2)
|
||||||
|
|
||||||
|
# see FEATURE AREAS in dpkg-buildflags(1)
|
||||||
|
#export DEB_BUILD_MAINT_OPTIONS = hardening=+all
|
||||||
|
|
||||||
|
# see ENVIRONMENT in dpkg-buildflags(1)
|
||||||
|
# package maintainers to append CFLAGS
|
||||||
|
#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic
|
||||||
|
# package maintainers to append LDFLAGS
|
||||||
|
#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed
|
||||||
|
|
||||||
|
|
||||||
|
%:
|
||||||
|
dh $@ --with dkms
|
||||||
|
|
||||||
|
|
||||||
|
# dh_make generated override targets
|
||||||
|
# This is example for Cmake (See https://bugs.debian.org/641051 )
|
||||||
|
#override_dh_auto_configure:
|
||||||
|
# dh_auto_configure -- \
|
||||||
|
# -DCMAKE_LIBRARY_PATH=$(DEB_HOST_MULTIARCH)
|
||||||
|
|
||||||
|
override_dh_install:
|
||||||
|
dh_install src/* usr/src/sixfireusb-$(VERSION)/
|
||||||
|
dh_install sixfireusb.conf etc/modprobe.d/
|
||||||
|
|
||||||
|
override_dh_dkms:
|
||||||
|
dh_dkms -V $(VERSION)
|
|
@ -0,0 +1,2 @@
|
||||||
|
README.Debian
|
||||||
|
README.source
|
|
@ -0,0 +1 @@
|
||||||
|
3.0 (quilt)
|
|
@ -0,0 +1 @@
|
||||||
|
blacklist snd_usb_6fire
|
|
@ -0,0 +1,127 @@
|
||||||
|
0.6.2
|
||||||
|
---------------------------
|
||||||
|
- added support for snd_card_new() (thanks to crepererum)
|
||||||
|
- grsecurity compatibility (thanks to crepererum)
|
||||||
|
|
||||||
|
0.6.1
|
||||||
|
---------------------------
|
||||||
|
- usb buffers are now allocated with kmalloc()
|
||||||
|
- Makefile can now select kernel version (thanks to prettyvanilla for this)
|
||||||
|
- firmware check relaxed
|
||||||
|
|
||||||
|
0.6.0
|
||||||
|
---------------------------
|
||||||
|
- introduced kernel version awareness into code
|
||||||
|
- changed README to contain a hint about volume and muting
|
||||||
|
|
||||||
|
0.5.7
|
||||||
|
---------------------------
|
||||||
|
- updated driver boilerplate
|
||||||
|
|
||||||
|
0.5.6
|
||||||
|
---------------------------
|
||||||
|
- fixed a bug that muted/unmuted the wrong channels
|
||||||
|
|
||||||
|
0.5.5
|
||||||
|
---------------------------
|
||||||
|
- fixed a bug that on some kernel version prevented building with error
|
||||||
|
"expected declaration specifiers or '...' before string"
|
||||||
|
- target kremove in Makefile now also removes compressed modules
|
||||||
|
|
||||||
|
0.5.4
|
||||||
|
---------------------------
|
||||||
|
- added firmware versions that have been reported to work
|
||||||
|
- fixed a bug that caused noise when capturing high sample rates
|
||||||
|
- fixed a bug that prevented high sample rates from being selected
|
||||||
|
- temporarily disabled pcm_mode_both because of remaining problems
|
||||||
|
- added capture gain control for inputs 1/2
|
||||||
|
- minor control updates: channel names changed, dB scale added,
|
||||||
|
virtual master introduced, log conversion removed
|
||||||
|
|
||||||
|
0.5.3
|
||||||
|
---------------------------
|
||||||
|
- fixed a bug that prevented urb buffers from being freed on card disconnect
|
||||||
|
- fixed a bug that caused audio problems after decreasing period size
|
||||||
|
- modified module parameter "use_tasklet" and renamed to "tasklet_thresh":
|
||||||
|
tasklet usage now dynamically selectable, dependend on period size
|
||||||
|
|
||||||
|
0.5.2
|
||||||
|
---------------------------
|
||||||
|
- improved period size handling, urb size not fixed any longer but dependend
|
||||||
|
on selected period size
|
||||||
|
- pcm interface names improved
|
||||||
|
|
||||||
|
0.5.1
|
||||||
|
---------------------------
|
||||||
|
- fixed a bug that caused the driver to panic when device was disconnected
|
||||||
|
while usage
|
||||||
|
- changed buffer and period limits to 0.3.x since these settings worked better
|
||||||
|
|
||||||
|
0.5.0
|
||||||
|
---------------------------
|
||||||
|
- added digital output (no passthrough yet)
|
||||||
|
- added module parameter "pcm_mode"
|
||||||
|
|
||||||
|
0.4.0
|
||||||
|
---------------------------
|
||||||
|
- complete rework of pcm handling
|
||||||
|
- added module parameter "use_tasklet"
|
||||||
|
- fixed a bug that caused firmware not to load properly
|
||||||
|
|
||||||
|
0.3.6
|
||||||
|
---------------------------
|
||||||
|
Thanks to Holger Ruckdeschel, who found out how to make these features possible.
|
||||||
|
|
||||||
|
- mute switches introduced
|
||||||
|
- volume control now for each individual analog channel
|
||||||
|
|
||||||
|
0.3.5
|
||||||
|
---------------------------
|
||||||
|
- fixed a bug that caused problems with available samplerates
|
||||||
|
- Makefile now has a "kremove" target that allows to remove all snd-usb-6fire
|
||||||
|
modules from the kernel module directory
|
||||||
|
|
||||||
|
0.3.4
|
||||||
|
---------------------------
|
||||||
|
- digital thru implemented to enable the device to convert from coax to
|
||||||
|
optical and vice versa
|
||||||
|
|
||||||
|
0.3.3
|
||||||
|
---------------------------
|
||||||
|
- fixed installation path for module (now extra/ instead of extras/)
|
||||||
|
- fixed capture buffer position when capturing in S32_LE
|
||||||
|
|
||||||
|
0.3.2
|
||||||
|
---------------------------
|
||||||
|
- added sampleformat 32 bit little endian
|
||||||
|
|
||||||
|
0.3.1
|
||||||
|
---------------------------
|
||||||
|
- check for device version removed before uploading firmware
|
||||||
|
|
||||||
|
0.3.0
|
||||||
|
---------------------------
|
||||||
|
- improved stability
|
||||||
|
- SPDIF out removed from features
|
||||||
|
- samplerates 176400 and 192000 now supported
|
||||||
|
|
||||||
|
0.2.0
|
||||||
|
---------------------------
|
||||||
|
- MIDI in and out working
|
||||||
|
- channel 1 and 2 are copied to SPDIF out
|
||||||
|
|
||||||
|
0.1.2
|
||||||
|
---------------------------
|
||||||
|
- pointer from integer warning fixed in capture16
|
||||||
|
- playback and capture now also available for 8 and 24 bits per sample
|
||||||
|
- fixed bug that didn't allow other samplerates than 44100
|
||||||
|
|
||||||
|
0.1.1
|
||||||
|
---------------------------
|
||||||
|
- capture at 16 bit, 6 channels, samplerate 44100 to 96000
|
||||||
|
|
||||||
|
0.1.0
|
||||||
|
---------------------------
|
||||||
|
- playback at 16 bit, 6 channels, samplerate 44100 to 96000
|
||||||
|
- firmware upload for devices that recently have been connected to power
|
||||||
|
- hardware master volume
|
|
@ -0,0 +1,281 @@
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 2, June 1991
|
||||||
|
|
||||||
|
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||||
|
675 Mass Ave, Cambridge, MA 02139, USA
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The licenses for most software are designed to take away your
|
||||||
|
freedom to share and change it. By contrast, the GNU General Public
|
||||||
|
License is intended to guarantee your freedom to share and change free
|
||||||
|
software--to make sure the software is free for all its users. This
|
||||||
|
General Public License applies to most of the Free Software
|
||||||
|
Foundation's software and to any other program whose authors commit to
|
||||||
|
using it. (Some other Free Software Foundation software is covered by
|
||||||
|
the GNU Library General Public License instead.) You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
this service if you wish), that you receive source code or can get it
|
||||||
|
if you want it, that you can change the software or use pieces of it
|
||||||
|
in new free programs; and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to make restrictions that forbid
|
||||||
|
anyone to deny you these rights or to ask you to surrender the rights.
|
||||||
|
These restrictions translate to certain responsibilities for you if you
|
||||||
|
distribute copies of the software, or if you modify it.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must give the recipients all the rights that
|
||||||
|
you have. You must make sure that they, too, receive or can get the
|
||||||
|
source code. And you must show them these terms so they know their
|
||||||
|
rights.
|
||||||
|
|
||||||
|
We protect your rights with two steps: (1) copyright the software, and
|
||||||
|
(2) offer you this license which gives you legal permission to copy,
|
||||||
|
distribute and/or modify the software.
|
||||||
|
|
||||||
|
Also, for each author's protection and ours, we want to make certain
|
||||||
|
that everyone understands that there is no warranty for this free
|
||||||
|
software. If the software is modified by someone else and passed on, we
|
||||||
|
want its recipients to know that what they have is not the original, so
|
||||||
|
that any problems introduced by others will not reflect on the original
|
||||||
|
authors' reputations.
|
||||||
|
|
||||||
|
Finally, any free program is threatened constantly by software
|
||||||
|
patents. We wish to avoid the danger that redistributors of a free
|
||||||
|
program will individually obtain patent licenses, in effect making the
|
||||||
|
program proprietary. To prevent this, we have made it clear that any
|
||||||
|
patent must be licensed for everyone's free use or not licensed at all.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||||
|
|
||||||
|
0. This License applies to any program or other work which contains
|
||||||
|
a notice placed by the copyright holder saying it may be distributed
|
||||||
|
under the terms of this General Public License. The "Program", below,
|
||||||
|
refers to any such program or work, and a "work based on the Program"
|
||||||
|
means either the Program or any derivative work under copyright law:
|
||||||
|
that is to say, a work containing the Program or a portion of it,
|
||||||
|
either verbatim or with modifications and/or translated into another
|
||||||
|
language. (Hereinafter, translation is included without limitation in
|
||||||
|
the term "modification".) Each licensee is addressed as "you".
|
||||||
|
|
||||||
|
Activities other than copying, distribution and modification are not
|
||||||
|
covered by this License; they are outside its scope. The act of
|
||||||
|
running the Program is not restricted, and the output from the Program
|
||||||
|
is covered only if its contents constitute a work based on the
|
||||||
|
Program (independent of having been made by running the Program).
|
||||||
|
Whether that is true depends on what the Program does.
|
||||||
|
|
||||||
|
1. You may copy and distribute verbatim copies of the Program's
|
||||||
|
source code as you receive it, in any medium, provided that you
|
||||||
|
conspicuously and appropriately publish on each copy an appropriate
|
||||||
|
copyright notice and disclaimer of warranty; keep intact all the
|
||||||
|
notices that refer to this License and to the absence of any warranty;
|
||||||
|
and give any other recipients of the Program a copy of this License
|
||||||
|
along with the Program.
|
||||||
|
|
||||||
|
You may charge a fee for the physical act of transferring a copy, and
|
||||||
|
you may at your option offer warranty protection in exchange for a fee.
|
||||||
|
|
||||||
|
2. You may modify your copy or copies of the Program or any portion
|
||||||
|
of it, thus forming a work based on the Program, and copy and
|
||||||
|
distribute such modifications or work under the terms of Section 1
|
||||||
|
above, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) You must cause the modified files to carry prominent notices
|
||||||
|
stating that you changed the files and the date of any change.
|
||||||
|
|
||||||
|
b) You must cause any work that you distribute or publish, that in
|
||||||
|
whole or in part contains or is derived from the Program or any
|
||||||
|
part thereof, to be licensed as a whole at no charge to all third
|
||||||
|
parties under the terms of this License.
|
||||||
|
|
||||||
|
c) If the modified program normally reads commands interactively
|
||||||
|
when run, you must cause it, when started running for such
|
||||||
|
interactive use in the most ordinary way, to print or display an
|
||||||
|
announcement including an appropriate copyright notice and a
|
||||||
|
notice that there is no warranty (or else, saying that you provide
|
||||||
|
a warranty) and that users may redistribute the program under
|
||||||
|
these conditions, and telling the user how to view a copy of this
|
||||||
|
License. (Exception: if the Program itself is interactive but
|
||||||
|
does not normally print such an announcement, your work based on
|
||||||
|
the Program is not required to print an announcement.)
|
||||||
|
|
||||||
|
These requirements apply to the modified work as a whole. If
|
||||||
|
identifiable sections of that work are not derived from the Program,
|
||||||
|
and can be reasonably considered independent and separate works in
|
||||||
|
themselves, then this License, and its terms, do not apply to those
|
||||||
|
sections when you distribute them as separate works. But when you
|
||||||
|
distribute the same sections as part of a whole which is a work based
|
||||||
|
on the Program, the distribution of the whole must be on the terms of
|
||||||
|
this License, whose permissions for other licensees extend to the
|
||||||
|
entire whole, and thus to each and every part regardless of who wrote it.
|
||||||
|
|
||||||
|
Thus, it is not the intent of this section to claim rights or contest
|
||||||
|
your rights to work written entirely by you; rather, the intent is to
|
||||||
|
exercise the right to control the distribution of derivative or
|
||||||
|
collective works based on the Program.
|
||||||
|
|
||||||
|
In addition, mere aggregation of another work not based on the Program
|
||||||
|
with the Program (or with a work based on the Program) on a volume of
|
||||||
|
a storage or distribution medium does not bring the other work under
|
||||||
|
the scope of this License.
|
||||||
|
|
||||||
|
3. You may copy and distribute the Program (or a work based on it,
|
||||||
|
under Section 2) in object code or executable form under the terms of
|
||||||
|
Sections 1 and 2 above provided that you also do one of the following:
|
||||||
|
|
||||||
|
a) Accompany it with the complete corresponding machine-readable
|
||||||
|
source code, which must be distributed under the terms of Sections
|
||||||
|
1 and 2 above on a medium customarily used for software interchange; or,
|
||||||
|
|
||||||
|
b) Accompany it with a written offer, valid for at least three
|
||||||
|
years, to give any third party, for a charge no more than your
|
||||||
|
cost of physically performing source distribution, a complete
|
||||||
|
machine-readable copy of the corresponding source code, to be
|
||||||
|
distributed under the terms of Sections 1 and 2 above on a medium
|
||||||
|
customarily used for software interchange; or,
|
||||||
|
|
||||||
|
c) Accompany it with the information you received as to the offer
|
||||||
|
to distribute corresponding source code. (This alternative is
|
||||||
|
allowed only for noncommercial distribution and only if you
|
||||||
|
received the program in object code or executable form with such
|
||||||
|
an offer, in accord with Subsection b above.)
|
||||||
|
|
||||||
|
The source code for a work means the preferred form of the work for
|
||||||
|
making modifications to it. For an executable work, complete source
|
||||||
|
code means all the source code for all modules it contains, plus any
|
||||||
|
associated interface definition files, plus the scripts used to
|
||||||
|
control compilation and installation of the executable. However, as a
|
||||||
|
special exception, the source code distributed need not include
|
||||||
|
anything that is normally distributed (in either source or binary
|
||||||
|
form) with the major components (compiler, kernel, and so on) of the
|
||||||
|
operating system on which the executable runs, unless that component
|
||||||
|
itself accompanies the executable.
|
||||||
|
|
||||||
|
If distribution of executable or object code is made by offering
|
||||||
|
access to copy from a designated place, then offering equivalent
|
||||||
|
access to copy the source code from the same place counts as
|
||||||
|
distribution of the source code, even though third parties are not
|
||||||
|
compelled to copy the source along with the object code.
|
||||||
|
|
||||||
|
4. You may not copy, modify, sublicense, or distribute the Program
|
||||||
|
except as expressly provided under this License. Any attempt
|
||||||
|
otherwise to copy, modify, sublicense or distribute the Program is
|
||||||
|
void, and will automatically terminate your rights under this License.
|
||||||
|
However, parties who have received copies, or rights, from you under
|
||||||
|
this License will not have their licenses terminated so long as such
|
||||||
|
parties remain in full compliance.
|
||||||
|
|
||||||
|
5. You are not required to accept this License, since you have not
|
||||||
|
signed it. However, nothing else grants you permission to modify or
|
||||||
|
distribute the Program or its derivative works. These actions are
|
||||||
|
prohibited by law if you do not accept this License. Therefore, by
|
||||||
|
modifying or distributing the Program (or any work based on the
|
||||||
|
Program), you indicate your acceptance of this License to do so, and
|
||||||
|
all its terms and conditions for copying, distributing or modifying
|
||||||
|
the Program or works based on it.
|
||||||
|
|
||||||
|
6. Each time you redistribute the Program (or any work based on the
|
||||||
|
Program), the recipient automatically receives a license from the
|
||||||
|
original licensor to copy, distribute or modify the Program subject to
|
||||||
|
these terms and conditions. You may not impose any further
|
||||||
|
restrictions on the recipients' exercise of the rights granted herein.
|
||||||
|
You are not responsible for enforcing compliance by third parties to
|
||||||
|
this License.
|
||||||
|
|
||||||
|
7. If, as a consequence of a court judgment or allegation of patent
|
||||||
|
infringement or for any other reason (not limited to patent issues),
|
||||||
|
conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot
|
||||||
|
distribute so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you
|
||||||
|
may not distribute the Program at all. For example, if a patent
|
||||||
|
license would not permit royalty-free redistribution of the Program by
|
||||||
|
all those who receive copies directly or indirectly through you, then
|
||||||
|
the only way you could satisfy both it and this License would be to
|
||||||
|
refrain entirely from distribution of the Program.
|
||||||
|
|
||||||
|
If any portion of this section is held invalid or unenforceable under
|
||||||
|
any particular circumstance, the balance of the section is intended to
|
||||||
|
apply and the section as a whole is intended to apply in other
|
||||||
|
circumstances.
|
||||||
|
|
||||||
|
It is not the purpose of this section to induce you to infringe any
|
||||||
|
patents or other property right claims or to contest validity of any
|
||||||
|
such claims; this section has the sole purpose of protecting the
|
||||||
|
integrity of the free software distribution system, which is
|
||||||
|
implemented by public license practices. Many people have made
|
||||||
|
generous contributions to the wide range of software distributed
|
||||||
|
through that system in reliance on consistent application of that
|
||||||
|
system; it is up to the author/donor to decide if he or she is willing
|
||||||
|
to distribute software through any other system and a licensee cannot
|
||||||
|
impose that choice.
|
||||||
|
|
||||||
|
This section is intended to make thoroughly clear what is believed to
|
||||||
|
be a consequence of the rest of this License.
|
||||||
|
|
||||||
|
8. If the distribution and/or use of the Program is restricted in
|
||||||
|
certain countries either by patents or by copyrighted interfaces, the
|
||||||
|
original copyright holder who places the Program under this License
|
||||||
|
may add an explicit geographical distribution limitation excluding
|
||||||
|
those countries, so that distribution is permitted only in or among
|
||||||
|
countries not thus excluded. In such case, this License incorporates
|
||||||
|
the limitation as if written in the body of this License.
|
||||||
|
|
||||||
|
9. The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the Program
|
||||||
|
specifies a version number of this License which applies to it and "any
|
||||||
|
later version", you have the option of following the terms and conditions
|
||||||
|
either of that version or of any later version published by the Free
|
||||||
|
Software Foundation. If the Program does not specify a version number of
|
||||||
|
this License, you may choose any version ever published by the Free Software
|
||||||
|
Foundation.
|
||||||
|
|
||||||
|
10. If you wish to incorporate parts of the Program into other free
|
||||||
|
programs whose distribution conditions are different, write to the author
|
||||||
|
to ask for permission. For software which is copyrighted by the Free
|
||||||
|
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||||
|
make exceptions for this. Our decision will be guided by the two goals
|
||||||
|
of preserving the free status of all derivatives of our free software and
|
||||||
|
of promoting the sharing and reuse of software generally.
|
||||||
|
|
||||||
|
NO WARRANTY
|
||||||
|
|
||||||
|
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||||
|
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||||
|
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||||
|
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||||
|
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||||
|
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||||
|
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||||
|
REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||||
|
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||||
|
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||||
|
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||||
|
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||||
|
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGES.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
Prerequisites
|
||||||
|
--------------
|
||||||
|
|
||||||
|
- kernel headers for current kernel
|
||||||
|
- internet connection
|
||||||
|
|
||||||
|
|
||||||
|
Building the kernel module
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
Usually you should be able to simple type:
|
||||||
|
$ make
|
||||||
|
|
||||||
|
|
||||||
|
Installing windows firmware
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
You also need the firmware for the card. To install it, visit
|
||||||
|
http://sourceforge.net/projects/sixfireusb/files/tools
|
||||||
|
|
||||||
|
Download both files and follow the instructions in fwinst.txt
|
||||||
|
|
||||||
|
|
||||||
|
Using this module with kernel >= 2.6.39 and alsa >= 1.0.24
|
||||||
|
-----------------------------------------------------------
|
||||||
|
Since Kernel 2.6.39 (and alsa 1.0.24) the module is integrated. Changes (either from user requests or bug fixed) will first be available on the
|
||||||
|
sourceforge page. Until these changes are integrated into the kernel/alsa, it will last a while. To use the sourceforge module with the new kernel/alsa,
|
||||||
|
all modules that are installed from the kernel/alsa named "snd-usb-6fire.ko" need to be removed from the kernel module directory. To accomplish that, you can type:
|
||||||
|
$ sudo make kremove
|
||||||
|
|
||||||
|
Afterwards a new make install is required.
|
||||||
|
|
||||||
|
|
||||||
|
Installing the kernel module
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
To install the module in the kernel, type:
|
||||||
|
$ sudo make install
|
||||||
|
|
||||||
|
The module will then be loaded automatically on startup or if the device is inserted.
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
obj-m += snd-usb-6fire.o
|
||||||
|
snd-usb-6fire-y := chip.o comm.o control.o firmware.o midi.o pcm.o substream.o urbs.o
|
||||||
|
|
||||||
|
KERNELRELEASE ?= $(shell uname -r)
|
||||||
|
KDIR ?= /lib/modules/$(KERNELRELEASE)/build
|
||||||
|
PWD ?= $(shell pwd)
|
||||||
|
|
||||||
|
default: modules
|
||||||
|
install: modules_install
|
||||||
|
|
||||||
|
modules modules_install clean:
|
||||||
|
$(MAKE) -C $(KDIR) M=$(PWD) $@
|
||||||
|
|
||||||
|
kremove:
|
||||||
|
for x in $(shell find /lib/modules/$(shell uname -r) -iname snd-usb-6fire\*); do \
|
||||||
|
rm -f $$x; \
|
||||||
|
done
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
TerraTec DMX 6Fire USB driver for ALSA 0.7.0
|
||||||
|
=============================================
|
||||||
|
|
||||||
|
This is the driver for the TerraTec DMX 6Fire USB. Current features include:
|
||||||
|
- Firmware uploading if device has been reconnected to power
|
||||||
|
- Playback and capture at 16, 24 and 32 bit, Samplerates 44100 to 192000,
|
||||||
|
6/4 analog channels
|
||||||
|
- Playback at 16, 24 and 32 bit, Samplerates 44100 to 96000, 2 digital channels
|
||||||
|
(no passthrough yet)
|
||||||
|
- Master volume in mixer
|
||||||
|
- Individual analog channel volumes in mixer
|
||||||
|
- Selection of line/phono capturing
|
||||||
|
- MIDI in/out
|
||||||
|
- Digital thru: device may act as converter coax <-> optical
|
||||||
|
|
||||||
|
What is yet missing:
|
||||||
|
- Digital input
|
||||||
|
- SPDIF passthrough
|
||||||
|
- pcm_mode=4 (see below)
|
||||||
|
|
||||||
|
Volume and Muting
|
||||||
|
------------------
|
||||||
|
After the device is plugged in the first time, depending on the sound
|
||||||
|
frontend you are using it may happen that no sound output occurs. This
|
||||||
|
is due to the fact that the driver initializes all channels with
|
||||||
|
volume set to zero and muting enabled. Not all sound frontends are
|
||||||
|
aware of these control (like pulseaudio for example). To unmute and
|
||||||
|
change the volume of these channels, please open a terminal and type:
|
||||||
|
|
||||||
|
$ aplay -l
|
||||||
|
|
||||||
|
This will show all devices that alsa is aware of. You will recognize
|
||||||
|
a string "card X" at the beginning of the line listing a device.
|
||||||
|
Remember this X and replace it in the following line:
|
||||||
|
|
||||||
|
$ alsamixer -c X
|
||||||
|
|
||||||
|
Here you have access to all voulme and mute controls and other settings
|
||||||
|
that can be changed on the device.
|
||||||
|
|
||||||
|
It is also possible to use a graphical mixer, there are several out there.
|
||||||
|
|
||||||
|
Module parameters
|
||||||
|
------------------
|
||||||
|
* tasklet_thresh
|
||||||
|
possible values: non-negative integer
|
||||||
|
default: 128
|
||||||
|
|
||||||
|
Specifies the threshold, which causes a tasklet to be used to copy data
|
||||||
|
between alsa and the device.
|
||||||
|
|
||||||
|
0: deactivate this feature
|
||||||
|
positive number: if the selected period size uses more or equal packets per
|
||||||
|
urb than this number, a tasklet will be used to transfer data. Otherwise
|
||||||
|
data is transferred immediately in the hardware interrupt.
|
||||||
|
Remark: one packet maps to approximately 0.15 milliseconds.
|
||||||
|
|
||||||
|
Using a tasklet means that the hardware interrupt routine will have to do less
|
||||||
|
work and the OS is ready to serve other devices more quickly (keyword:
|
||||||
|
"decreased DPC-Latency"). Data transfer (alsa <-> device) is scheduled later.
|
||||||
|
This might lead to crackles, when this data transfer is delayed because of
|
||||||
|
system activity. If you experience such problems, set this parameter to a
|
||||||
|
higher value or disable the feature.
|
||||||
|
|
||||||
|
* pcm_mode
|
||||||
|
possible values: 0-3
|
||||||
|
default: 3
|
||||||
|
|
||||||
|
Specifies the pcm mode the card will work at. The five modes are:
|
||||||
|
0: no pcm at all (only midi and digital passthrough)
|
||||||
|
1: analog only (all sample rates, including high sample rates)
|
||||||
|
2: digital only (maximum sample rate: 96kHz)
|
||||||
|
3: analog/digital mode, sample rates restricted to 96kHz
|
||||||
|
96kHz is the maximum sample rate that is allowed for digital audio.
|
||||||
|
If this mode is selected, analog and digital may be used in parallel
|
||||||
|
for all circumstances.
|
||||||
|
4: analog/digital mode, sample rates not restricted (currently disabled)
|
||||||
|
In this mode an analog stream may be played back/captured at high sample
|
||||||
|
rates (176.4kHz and 192kHz). However, while an analog stream is open using
|
||||||
|
one of these sample rates, opening the digital audio device will fail.
|
||||||
|
|
||||||
|
Remarks
|
||||||
|
--------
|
||||||
|
Concerning firmware uploading: I removed the check that made firmware uploading
|
||||||
|
possible only for a specific device version. I don't known if firmware loading
|
||||||
|
will be successful on all devices. Reports from other users indicate that it
|
||||||
|
should work. If the device behaves strangely after a firmware upload has been
|
||||||
|
performed (every time the device has been reconnected to power), please report
|
||||||
|
this issue with a short description.
|
||||||
|
|
||||||
|
Since version 0.3.0, this driver can also be found in the linux kernel. This
|
||||||
|
page will be maintained in order for experimental things like digital in-/output
|
||||||
|
and latency optimization. If no problems are reported for a while here, the
|
||||||
|
version will go into the kernel.
|
||||||
|
|
|
@ -0,0 +1,280 @@
|
||||||
|
/*
|
||||||
|
* Linux driver for TerraTec DMX 6Fire USB
|
||||||
|
*
|
||||||
|
* Main routines and module definitions.
|
||||||
|
*
|
||||||
|
* 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 "chip.h"
|
||||||
|
#include "firmware.h"
|
||||||
|
#include "control.h"
|
||||||
|
#include "comm.h"
|
||||||
|
#include "midi.h"
|
||||||
|
#include "substream.h"
|
||||||
|
#include "urbs.h"
|
||||||
|
#include "pcm.h"
|
||||||
|
|
||||||
|
#include <linux/version.h>
|
||||||
|
#include <linux/moduleparam.h>
|
||||||
|
#include <linux/interrupt.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/gfp.h>
|
||||||
|
#include <sound/initval.h>
|
||||||
|
|
||||||
|
MODULE_AUTHOR("Torsten Schenk <torsten.schenk@zoho.com>");
|
||||||
|
MODULE_DESCRIPTION("TerraTec DMX 6Fire USB audio driver, version 0.6.1");
|
||||||
|
MODULE_LICENSE("GPL v2");
|
||||||
|
MODULE_SUPPORTED_DEVICE("{{TerraTec, DMX 6Fire USB}}");
|
||||||
|
|
||||||
|
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */
|
||||||
|
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for card */
|
||||||
|
static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable card */
|
||||||
|
static struct sfire_chip *chips[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
|
||||||
|
static struct usb_device *devices[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
|
||||||
|
|
||||||
|
static int tasklet_thresh[SNDRV_CARDS] = { PCM_TASKLET_THRESH_DEFAULT };
|
||||||
|
static int pcm_mode[SNDRV_CARDS] = { PCM_MODE_DEFAULT };
|
||||||
|
|
||||||
|
module_param_array(index, int, NULL, 0444);
|
||||||
|
MODULE_PARM_DESC(index, "Index value for the 6fire sound device");
|
||||||
|
module_param_array(id, charp, NULL, 0444);
|
||||||
|
MODULE_PARM_DESC(id, "ID string for the 6fire sound device.");
|
||||||
|
module_param_array(enable, bool, NULL, 0444);
|
||||||
|
MODULE_PARM_DESC(enable, "Enable the 6fire sound device.");
|
||||||
|
module_param_array(tasklet_thresh, int, NULL, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
|
||||||
|
MODULE_PARM_DESC(tasklet_thresh, "Tasklet threshold (packets/urb, default = 128).");
|
||||||
|
module_param_array(pcm_mode, int, NULL, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
|
||||||
|
MODULE_PARM_DESC(pcm_mode, "PCM mode (default = 3).");
|
||||||
|
|
||||||
|
static DEFINE_MUTEX(register_mutex);
|
||||||
|
|
||||||
|
static void usb6fire_chip_abort(struct sfire_chip *chip)
|
||||||
|
{
|
||||||
|
if (chip) {
|
||||||
|
if (chip->card)
|
||||||
|
snd_card_disconnect(chip->card);
|
||||||
|
if (chip->midi)
|
||||||
|
usb6fire_midi_abort(chip);
|
||||||
|
if (chip->comm)
|
||||||
|
usb6fire_comm_abort(chip);
|
||||||
|
if (chip->control)
|
||||||
|
usb6fire_control_abort(chip);
|
||||||
|
if (chip->pcm)
|
||||||
|
usb6fire_pcm_abort(chip);
|
||||||
|
if (chip->urbs)
|
||||||
|
usb6fire_urbs_abort(chip);
|
||||||
|
if (chip->substream)
|
||||||
|
usb6fire_substream_abort(chip);
|
||||||
|
if (chip->card) {
|
||||||
|
snd_card_free_when_closed(chip->card);
|
||||||
|
chip->card = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usb6fire_chip_destroy(struct sfire_chip *chip)
|
||||||
|
{
|
||||||
|
if (chip) {
|
||||||
|
if (chip->midi)
|
||||||
|
usb6fire_midi_destroy(chip);
|
||||||
|
if (chip->comm)
|
||||||
|
usb6fire_comm_destroy(chip);
|
||||||
|
if (chip->control)
|
||||||
|
usb6fire_control_destroy(chip);
|
||||||
|
if (chip->pcm)
|
||||||
|
usb6fire_pcm_destroy(chip);
|
||||||
|
if (chip->urbs)
|
||||||
|
usb6fire_urbs_destroy(chip);
|
||||||
|
if (chip->substream)
|
||||||
|
usb6fire_substream_destroy(chip);
|
||||||
|
if (chip->card)
|
||||||
|
snd_card_free(chip->card);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb6fire_chip_probe(struct usb_interface *intf,
|
||||||
|
const struct usb_device_id *usb_id)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
int i;
|
||||||
|
struct sfire_chip *chip = NULL;
|
||||||
|
struct usb_device *device = interface_to_usbdev(intf);
|
||||||
|
int regidx = -1; /* index in module parameter array */
|
||||||
|
struct snd_card *card = NULL;
|
||||||
|
|
||||||
|
/* check, if firmware is present on device, upload it if not */
|
||||||
|
ret = usb6fire_fw_init(intf);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
else if (ret == FW_NOT_READY) /* firmware update performed */
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* look if we already serve this card and return if so */
|
||||||
|
mutex_lock(®ister_mutex);
|
||||||
|
for (i = 0; i < SNDRV_CARDS; i++) {
|
||||||
|
if (devices[i] == device) {
|
||||||
|
if (chips[i])
|
||||||
|
chips[i]->intf_count++;
|
||||||
|
usb_set_intfdata(intf, chips[i]);
|
||||||
|
mutex_unlock(®ister_mutex);
|
||||||
|
return 0;
|
||||||
|
} else if (regidx < 0)
|
||||||
|
regidx = i;
|
||||||
|
}
|
||||||
|
if (regidx < 0) {
|
||||||
|
mutex_unlock(®ister_mutex);
|
||||||
|
snd_printk(KERN_ERR PREFIX "too many cards registered.\n");
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
devices[regidx] = device;
|
||||||
|
mutex_unlock(®ister_mutex);
|
||||||
|
|
||||||
|
/* if we are here, card can be registered in alsa. */
|
||||||
|
if (usb_set_interface(device, 0, 0) != 0) {
|
||||||
|
snd_printk(KERN_ERR PREFIX "can't set first interface.\n");
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 15, 0)
|
||||||
|
ret = snd_card_new(&intf->dev, index[regidx], id[regidx],
|
||||||
|
THIS_MODULE, sizeof(struct sfire_chip), &card);
|
||||||
|
#else
|
||||||
|
ret = snd_card_create(index[regidx], id[regidx],
|
||||||
|
THIS_MODULE, sizeof(struct sfire_chip), &card);
|
||||||
|
#endif
|
||||||
|
if (ret < 0) {
|
||||||
|
snd_printk(KERN_ERR PREFIX "cannot create alsa card.\n");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
strcpy(card->driver, "6FireUSB");
|
||||||
|
strcpy(card->shortname, "TerraTec DMX6FireUSB");
|
||||||
|
sprintf(card->longname, "%s at %d:%d", card->shortname,
|
||||||
|
device->bus->busnum, device->devnum);
|
||||||
|
snd_card_set_dev(card, &intf->dev);
|
||||||
|
|
||||||
|
chip = card->private_data;
|
||||||
|
chips[regidx] = chip;
|
||||||
|
chip->tasklet_thresh = tasklet_thresh[regidx];
|
||||||
|
if (chip->tasklet_thresh < 0)
|
||||||
|
chip->tasklet_thresh = PCM_TASKLET_THRESH_DEFAULT;
|
||||||
|
chip->pcm_mode = pcm_mode[regidx];
|
||||||
|
if (chip->pcm_mode < 0 || chip->pcm_mode >= PCM_MODES)
|
||||||
|
chip->pcm_mode = PCM_MODE_DEFAULT;
|
||||||
|
chip->dev = device;
|
||||||
|
chip->regidx = regidx;
|
||||||
|
chip->intf_count = 1;
|
||||||
|
chip->card = card;
|
||||||
|
|
||||||
|
ret = usb6fire_comm_init(chip);
|
||||||
|
if (ret < 0) {
|
||||||
|
usb6fire_chip_destroy(chip);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = usb6fire_midi_init(chip);
|
||||||
|
if (ret < 0) {
|
||||||
|
usb6fire_chip_destroy(chip);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = usb6fire_control_init(chip);
|
||||||
|
if (ret < 0) {
|
||||||
|
usb6fire_chip_destroy(chip);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chip->pcm_mode != PCM_MODE_OFF) {
|
||||||
|
ret = usb6fire_substream_init(chip);
|
||||||
|
if (ret < 0) {
|
||||||
|
usb6fire_chip_destroy(chip);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = usb6fire_urbs_init(chip);
|
||||||
|
if (ret < 0) {
|
||||||
|
usb6fire_chip_destroy(chip);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = usb6fire_pcm_init(chip);
|
||||||
|
if (ret < 0) {
|
||||||
|
usb6fire_chip_destroy(chip);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = snd_card_register(card);
|
||||||
|
if (ret < 0) {
|
||||||
|
snd_printk(KERN_ERR PREFIX "cannot register card.");
|
||||||
|
usb6fire_chip_destroy(chip);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
usb_set_intfdata(intf, chip);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usb6fire_chip_disconnect(struct usb_interface *intf)
|
||||||
|
{
|
||||||
|
struct sfire_chip *chip;
|
||||||
|
struct snd_card *card;
|
||||||
|
|
||||||
|
chip = usb_get_intfdata(intf);
|
||||||
|
if (chip) { /* if !chip, fw upload has been performed */
|
||||||
|
card = chip->card;
|
||||||
|
chip->intf_count--;
|
||||||
|
if (!chip->intf_count) {
|
||||||
|
mutex_lock(®ister_mutex);
|
||||||
|
devices[chip->regidx] = NULL;
|
||||||
|
chips[chip->regidx] = NULL;
|
||||||
|
mutex_unlock(®ister_mutex);
|
||||||
|
|
||||||
|
chip->shutdown = true;
|
||||||
|
usb6fire_chip_abort(chip);
|
||||||
|
usb6fire_chip_destroy(chip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct usb_device_id device_table[] = {
|
||||||
|
{
|
||||||
|
.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
|
||||||
|
.idVendor = 0x0ccd,
|
||||||
|
.idProduct = 0x0080
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
MODULE_DEVICE_TABLE(usb, device_table);
|
||||||
|
|
||||||
|
static struct usb_driver usb_driver = {
|
||||||
|
.name = "snd-usb-6fire",
|
||||||
|
.probe = usb6fire_chip_probe,
|
||||||
|
.disconnect = usb6fire_chip_disconnect,
|
||||||
|
.id_table = device_table,
|
||||||
|
};
|
||||||
|
|
||||||
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 0)
|
||||||
|
module_usb_driver(usb_driver);
|
||||||
|
#else
|
||||||
|
static int __init usb6fire_chip_init(void)
|
||||||
|
{
|
||||||
|
return usb_register(&usb_driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __exit usb6fire_chip_cleanup(void)
|
||||||
|
{
|
||||||
|
usb_deregister(&usb_driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
module_init(usb6fire_chip_init);
|
||||||
|
module_exit(usb6fire_chip_cleanup);
|
||||||
|
#endif
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* Linux driver for TerraTec DMX 6Fire USB
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
#ifndef USB6FIRE_CHIP_H
|
||||||
|
#define USB6FIRE_CHIP_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
enum {
|
||||||
|
PCM_MODE_OFF,
|
||||||
|
PCM_MODE_ANALOG_ONLY,
|
||||||
|
PCM_MODE_SPDIF_ONLY,
|
||||||
|
PCM_MODE_BOTH_RATE_RESTRICT,
|
||||||
|
/* PCM_MODE_BOTH,*/
|
||||||
|
PCM_MODES,
|
||||||
|
PCM_MODE_DEFAULT = PCM_MODE_BOTH_RATE_RESTRICT
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
PCM_TASKLET_THRESH_DEFAULT = 128
|
||||||
|
};
|
||||||
|
|
||||||
|
#define PCM_MODE_USES_ANALOG(X) (X == PCM_MODE_ANALOG_ONLY || X == PCM_MODE_BOTH_RATE_RESTRICT/* || X == PCM_MODE_BOTH*/)
|
||||||
|
#define PCM_MODE_USES_SPDIF(X) (X == PCM_MODE_SPDIF_ONLY || X == PCM_MODE_BOTH_RATE_RESTRICT/* || X == PCM_MODE_BOTH*/)
|
||||||
|
#define PCM_MODE_ALLOWS_ANALOG_ONLY_RATES(X) (X == PCM_MODE_ANALOG_ONLY/* || X == PCM_MODE_BOTH*/)
|
||||||
|
#define PCM_MODE_ALLOWS_SPDIF_ONLY_RATES(X) (X == PCM_MODE_SPDIF_ONLY/* || X == PCM_MODE_BOTH*/)
|
||||||
|
|
||||||
|
struct sfire_chip {
|
||||||
|
struct usb_device *dev;
|
||||||
|
struct snd_card *card;
|
||||||
|
int intf_count; /* number of registered interfaces */
|
||||||
|
int regidx; /* index in module parameter arrays */
|
||||||
|
bool shutdown;
|
||||||
|
|
||||||
|
int tasklet_thresh;
|
||||||
|
int pcm_mode;
|
||||||
|
|
||||||
|
struct midi_runtime *midi;
|
||||||
|
struct control_runtime *control;
|
||||||
|
struct comm_runtime *comm;
|
||||||
|
struct substream_runtime *substream;
|
||||||
|
struct urbs_runtime *urbs;
|
||||||
|
struct pcm_runtime *pcm;
|
||||||
|
};
|
||||||
|
#endif /* USB6FIRE_CHIP_H */
|
||||||
|
|
|
@ -0,0 +1,201 @@
|
||||||
|
/*
|
||||||
|
* Linux driver for TerraTec DMX 6Fire USB
|
||||||
|
*
|
||||||
|
* Device communications
|
||||||
|
*
|
||||||
|
* 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 "comm.h"
|
||||||
|
#include "chip.h"
|
||||||
|
#include "midi.h"
|
||||||
|
|
||||||
|
enum {
|
||||||
|
COMM_EP = 1,
|
||||||
|
COMM_FPGA_EP = 2,
|
||||||
|
COMM_RECEIVER_BUFSIZE = 64
|
||||||
|
};
|
||||||
|
|
||||||
|
static void usb6fire_comm_init_urb(struct comm_runtime *rt, struct urb *urb,
|
||||||
|
u8 *buffer, void *context, void(*handler)(struct urb *urb))
|
||||||
|
{
|
||||||
|
usb_init_urb(urb);
|
||||||
|
urb->transfer_buffer = buffer;
|
||||||
|
urb->pipe = usb_sndintpipe(rt->chip->dev, COMM_EP);
|
||||||
|
urb->complete = handler;
|
||||||
|
urb->context = context;
|
||||||
|
urb->interval = 1;
|
||||||
|
urb->dev = rt->chip->dev;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usb6fire_comm_receiver_handler(struct urb *urb)
|
||||||
|
{
|
||||||
|
struct comm_runtime *rt = urb->context;
|
||||||
|
struct midi_runtime *midi_rt = rt->chip->midi;
|
||||||
|
|
||||||
|
if (!urb->status) {
|
||||||
|
if (rt->receiver_buffer[0] == 0x10) /* midi in event */
|
||||||
|
if (midi_rt)
|
||||||
|
midi_rt->in_received(midi_rt,
|
||||||
|
rt->receiver_buffer + 2,
|
||||||
|
rt->receiver_buffer[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rt->chip->shutdown) {
|
||||||
|
urb->status = 0;
|
||||||
|
urb->actual_length = 0;
|
||||||
|
if (usb_submit_urb(urb, GFP_ATOMIC) < 0)
|
||||||
|
snd_printk(KERN_WARNING PREFIX
|
||||||
|
"comm data receiver aborted.\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usb6fire_comm_init_buffer(u8 *buffer, u8 id, u8 request,
|
||||||
|
u8 reg, u8 vl, u8 vh)
|
||||||
|
{
|
||||||
|
buffer[0] = 0x01;
|
||||||
|
buffer[2] = request;
|
||||||
|
buffer[3] = id;
|
||||||
|
switch (request) {
|
||||||
|
case 0x02:
|
||||||
|
buffer[1] = 0x05; /* length (starting at buffer[2]) */
|
||||||
|
buffer[4] = reg;
|
||||||
|
buffer[5] = vl;
|
||||||
|
buffer[6] = vh;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x12:
|
||||||
|
buffer[1] = 0x0b; /* length (starting at buffer[2]) */
|
||||||
|
buffer[4] = 0x00;
|
||||||
|
buffer[5] = 0x18;
|
||||||
|
buffer[6] = 0x05;
|
||||||
|
buffer[7] = 0x00;
|
||||||
|
buffer[8] = 0x01;
|
||||||
|
buffer[9] = 0x00;
|
||||||
|
buffer[10] = 0x9e;
|
||||||
|
buffer[11] = reg;
|
||||||
|
buffer[12] = vl;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x20:
|
||||||
|
case 0x21:
|
||||||
|
case 0x22:
|
||||||
|
buffer[1] = 0x04;
|
||||||
|
buffer[4] = reg;
|
||||||
|
buffer[5] = vl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb6fire_comm_send_buffer(u8 *buffer, struct usb_device *dev)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
int actual_len;
|
||||||
|
|
||||||
|
ret = usb_interrupt_msg(dev, usb_sndintpipe(dev, COMM_EP),
|
||||||
|
buffer, buffer[1] + 2, &actual_len, HZ);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
else if (actual_len != buffer[1] + 2)
|
||||||
|
return -EIO;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb6fire_comm_write8(struct comm_runtime *rt, u8 request,
|
||||||
|
u8 reg, u8 value)
|
||||||
|
{
|
||||||
|
u8 *buffer;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
buffer = kmalloc(13, GFP_KERNEL); /* 13: maximum length of message */
|
||||||
|
if(!buffer)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
usb6fire_comm_init_buffer(buffer, 0x00, request, reg, value, 0x00);
|
||||||
|
ret = usb6fire_comm_send_buffer(buffer, rt->chip->dev);
|
||||||
|
|
||||||
|
kfree(buffer);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb6fire_comm_write16(struct comm_runtime *rt, u8 request,
|
||||||
|
u8 reg, u8 vl, u8 vh)
|
||||||
|
{
|
||||||
|
u8 *buffer;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
buffer = kmalloc(13, GFP_KERNEL); /* 13: maximum length of message */
|
||||||
|
if(!buffer)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
usb6fire_comm_init_buffer(buffer, 0x00, request, reg, vl, vh);
|
||||||
|
ret = usb6fire_comm_send_buffer(buffer, rt->chip->dev);
|
||||||
|
kfree(buffer);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int usb6fire_comm_init(struct sfire_chip *chip)
|
||||||
|
{
|
||||||
|
struct comm_runtime *rt = kzalloc(sizeof(struct comm_runtime),
|
||||||
|
GFP_KERNEL);
|
||||||
|
struct urb *urb = &rt->receiver;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!rt)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
rt->receiver_buffer = kzalloc(COMM_RECEIVER_BUFSIZE, GFP_KERNEL);
|
||||||
|
if(!rt->receiver_buffer) {
|
||||||
|
kfree(rt);
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
rt->serial = 1;
|
||||||
|
rt->chip = chip;
|
||||||
|
usb_init_urb(urb);
|
||||||
|
rt->init_urb = usb6fire_comm_init_urb;
|
||||||
|
rt->write8 = usb6fire_comm_write8;
|
||||||
|
rt->write16 = usb6fire_comm_write16;
|
||||||
|
|
||||||
|
/* submit an urb that receives communication data from device */
|
||||||
|
urb->transfer_buffer = rt->receiver_buffer;
|
||||||
|
urb->transfer_buffer_length = COMM_RECEIVER_BUFSIZE;
|
||||||
|
urb->pipe = usb_rcvintpipe(chip->dev, COMM_EP);
|
||||||
|
urb->dev = chip->dev;
|
||||||
|
urb->complete = usb6fire_comm_receiver_handler;
|
||||||
|
urb->context = rt;
|
||||||
|
urb->interval = 1;
|
||||||
|
ret = usb_submit_urb(urb, GFP_KERNEL);
|
||||||
|
if (ret < 0) {
|
||||||
|
kfree(rt);
|
||||||
|
snd_printk(KERN_ERR PREFIX "cannot create comm data receiver.");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
chip->comm = rt;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void usb6fire_comm_abort(struct sfire_chip *chip)
|
||||||
|
{
|
||||||
|
struct comm_runtime *rt = chip->comm;
|
||||||
|
|
||||||
|
if (rt)
|
||||||
|
usb_poison_urb(&rt->receiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
void usb6fire_comm_destroy(struct sfire_chip *chip)
|
||||||
|
{
|
||||||
|
struct comm_runtime *rt = chip->comm;
|
||||||
|
|
||||||
|
kfree(rt->receiver_buffer);
|
||||||
|
kfree(rt);
|
||||||
|
chip->comm = NULL;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* Linux driver for TerraTec DMX 6Fire USB
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
#ifndef USB6FIRE_COMM_H
|
||||||
|
#define USB6FIRE_COMM_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
struct comm_runtime {
|
||||||
|
struct sfire_chip *chip;
|
||||||
|
|
||||||
|
struct urb receiver;
|
||||||
|
u8 *receiver_buffer;
|
||||||
|
|
||||||
|
u8 serial; /* urb serial */
|
||||||
|
|
||||||
|
void (*init_urb)(struct comm_runtime *rt, struct urb *urb, u8 *buffer,
|
||||||
|
void *context, void(*handler)(struct urb *urb));
|
||||||
|
/* writes control data to the device */
|
||||||
|
int (*write8)(struct comm_runtime *rt, u8 request, u8 reg, u8 value);
|
||||||
|
int (*write16)(struct comm_runtime *rt, u8 request, u8 reg,
|
||||||
|
u8 vh, u8 vl);
|
||||||
|
};
|
||||||
|
|
||||||
|
int usb6fire_comm_init(struct sfire_chip *chip);
|
||||||
|
void usb6fire_comm_abort(struct sfire_chip *chip);
|
||||||
|
void usb6fire_comm_destroy(struct sfire_chip *chip);
|
||||||
|
#endif /* USB6FIRE_COMM_H */
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Linux driver for TerraTec DMX 6Fire USB
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef USB6FIRE_COMMON_H
|
||||||
|
#define USB6FIRE_COMMON_H
|
||||||
|
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/usb.h>
|
||||||
|
#include <sound/core.h>
|
||||||
|
|
||||||
|
#define PREFIX "6fire: "
|
||||||
|
|
||||||
|
struct sfire_chip;
|
||||||
|
struct midi_runtime;
|
||||||
|
struct control_runtime;
|
||||||
|
struct comm_runtime;
|
||||||
|
struct substream_runtime;
|
||||||
|
struct urbs_runtime;
|
||||||
|
struct pcm_runtime;
|
||||||
|
#endif /* USB6FIRE_COMMON_H */
|
||||||
|
|
|
@ -0,0 +1,662 @@
|
||||||
|
/*
|
||||||
|
* Linux driver for TerraTec DMX 6Fire USB
|
||||||
|
*
|
||||||
|
* Mixer control
|
||||||
|
*
|
||||||
|
* Author: Torsten Schenk <torsten.schenk@zoho.com>
|
||||||
|
* Created: Jan 01, 2011
|
||||||
|
* Copyright: (C) Torsten Schenk
|
||||||
|
*
|
||||||
|
* Thanks to:
|
||||||
|
* - Holger Ruckdeschel: he found out how to control individual analog
|
||||||
|
* channel volumes and introduced mute switches
|
||||||
|
*
|
||||||
|
* 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 <linux/interrupt.h>
|
||||||
|
#include <sound/control.h>
|
||||||
|
#include <sound/tlv.h>
|
||||||
|
|
||||||
|
#include "control.h"
|
||||||
|
#include "comm.h"
|
||||||
|
#include "chip.h"
|
||||||
|
#include "rates.h"
|
||||||
|
|
||||||
|
static char *opt_coax_texts[2] = { "Optical", "Coax" };
|
||||||
|
static char *line_phono_texts[2] = { "Line", "Phono" };
|
||||||
|
|
||||||
|
/*
|
||||||
|
* data that needs to be sent to device. sets up card internal stuff.
|
||||||
|
* values dumped from windows driver and filtered by trial'n'error.
|
||||||
|
*/
|
||||||
|
static const struct {
|
||||||
|
u8 type;
|
||||||
|
u8 reg;
|
||||||
|
u8 value;
|
||||||
|
}
|
||||||
|
init_data[] = {
|
||||||
|
{ 0x12, 0x0e, 0x2f },
|
||||||
|
{ 0x22, 0x00, 0x00 }, { 0x20, 0x00, 0x08 }, { 0x22, 0x01, 0x01 },
|
||||||
|
{ 0x20, 0x01, 0x08 }, { 0x22, 0x02, 0x00 }, { 0x20, 0x02, 0x08 },
|
||||||
|
{ 0x22, 0x03, 0x00 }, { 0x20, 0x03, 0x08 }, { 0x22, 0x04, 0x00 },
|
||||||
|
{ 0x20, 0x04, 0x08 }, { 0x22, 0x05, 0x01 }, { 0x20, 0x05, 0x08 },
|
||||||
|
{ 0x22, 0x04, 0x01 }, { 0x12, 0x04, 0x00 }, { 0x12, 0x05, 0x00 },
|
||||||
|
{ 0x12, 0x23, 0x00 }, { 0x12, 0x06, 0x02 }, { 0x12, 0x03, 0x00 },
|
||||||
|
{ 0x12, 0x02, 0x00 }, { 0x22, 0x03, 0x01 },
|
||||||
|
{} /* TERMINATING ENTRY */
|
||||||
|
};
|
||||||
|
|
||||||
|
static DECLARE_TLV_DB_MINMAX(tlv_output, -9000, 0);
|
||||||
|
static DECLARE_TLV_DB_MINMAX(tlv_input, -1500, 1500);
|
||||||
|
|
||||||
|
static void usb6fire_control_output_vol_update(struct control_runtime *rt)
|
||||||
|
{
|
||||||
|
struct comm_runtime *comm_rt = rt->chip->comm;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (comm_rt)
|
||||||
|
for (i = 0; i < 6; i++)
|
||||||
|
comm_rt->write8(comm_rt, 0x12, 0x0f + i,
|
||||||
|
180 - rt->output_vol[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usb6fire_control_output_mute_update(struct control_runtime *rt)
|
||||||
|
{
|
||||||
|
struct comm_runtime *comm_rt = rt->chip->comm;
|
||||||
|
|
||||||
|
if (comm_rt)
|
||||||
|
comm_rt->write8(comm_rt, 0x12, 0x0e, ~rt->output_mute);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usb6fire_control_input_vol_update(struct control_runtime *rt)
|
||||||
|
{
|
||||||
|
struct comm_runtime *comm_rt = rt->chip->comm;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (comm_rt)
|
||||||
|
for (i = 0; i < 2; i++)
|
||||||
|
comm_rt->write8(comm_rt, 0x12, 0x1c + i,
|
||||||
|
rt->input_vol[i] & 0x3f);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usb6fire_control_line_phono_update(struct control_runtime *rt)
|
||||||
|
{
|
||||||
|
struct comm_runtime *comm_rt = rt->chip->comm;
|
||||||
|
if (comm_rt) {
|
||||||
|
comm_rt->write8(comm_rt, 0x22, 0x02, rt->line_phono_switch);
|
||||||
|
comm_rt->write8(comm_rt, 0x21, 0x02, rt->line_phono_switch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usb6fire_control_opt_coax_update(struct control_runtime *rt)
|
||||||
|
{
|
||||||
|
struct comm_runtime *comm_rt = rt->chip->comm;
|
||||||
|
if (comm_rt) {
|
||||||
|
comm_rt->write8(comm_rt, 0x22, 0x00, rt->opt_coax_switch);
|
||||||
|
comm_rt->write8(comm_rt, 0x21, 0x00, rt->opt_coax_switch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb6fire_control_set_rate(struct control_runtime *rt, unsigned int rate)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
int rate_id;
|
||||||
|
struct usb_device *device = rt->chip->dev;
|
||||||
|
struct comm_runtime *comm_rt = rt->chip->comm;
|
||||||
|
|
||||||
|
rt->use_analog = false;
|
||||||
|
rt->use_spdif = false;
|
||||||
|
rate_id = rate_to_id(rate);
|
||||||
|
if (!rates_valid[rate_id])
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
ret = usb_set_interface(device, 1, rates_altsetting[rate_id]);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
/* set soundcard clock */
|
||||||
|
ret = comm_rt->write16(comm_rt, 0x02, 0x01, rates_6fire_vl[rate_id],
|
||||||
|
rates_6fire_vh[rate_id]);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
ret = comm_rt->write8(comm_rt, 0x12, 0x03, rates_fmode[rate_id]);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
ret = comm_rt->write8(comm_rt, 0x12, 0x06, rates_clock[rate_id]);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
if (PCM_MODE_USES_ANALOG(rt->chip->pcm_mode))
|
||||||
|
rt->use_analog = true;
|
||||||
|
if (PCM_MODE_USES_SPDIF(rt->chip->pcm_mode)
|
||||||
|
&& rates_spdif_possible[rate_id])
|
||||||
|
rt->use_spdif = true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb6fire_control_streaming_update(struct control_runtime *rt)
|
||||||
|
{
|
||||||
|
struct comm_runtime *comm_rt = rt->chip->comm;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (comm_rt) {
|
||||||
|
if (!rt->usb_streaming && rt->digital_thru_switch)
|
||||||
|
usb6fire_control_set_rate(rt,
|
||||||
|
rates[RATE_DIGITAL_THRU_ONLY]);
|
||||||
|
if (rt->usb_streaming) {
|
||||||
|
ret = comm_rt->write16(comm_rt, 0x02, 0x02, 0x00, 0x00);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
ret = comm_rt->write16(comm_rt, 0x02, 0x03, 0x00, 0x00);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
if (rt->use_analog) {
|
||||||
|
ret = comm_rt->write16(comm_rt, 0x02, 0x02, 0x07, 0x03);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
if (rt->use_spdif) {
|
||||||
|
if (rt->use_analog)
|
||||||
|
ret = comm_rt->write16(comm_rt, 0x02, 0x03, 0x00, 0x02);
|
||||||
|
else
|
||||||
|
ret = comm_rt->write16(comm_rt, 0x02, 0x03, 0x00, 0x03);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return comm_rt->write16(comm_rt, 0x02, 0x00, 0x00,
|
||||||
|
(rt->usb_streaming ? 0x01 : 0x00) |
|
||||||
|
(rt->digital_thru_switch ? 0x08 : 0x00));
|
||||||
|
}
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb6fire_control_output_vol_info(struct snd_kcontrol *kcontrol,
|
||||||
|
struct snd_ctl_elem_info *uinfo)
|
||||||
|
{
|
||||||
|
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
||||||
|
uinfo->count = 2;
|
||||||
|
uinfo->value.integer.min = 0;
|
||||||
|
uinfo->value.integer.max = 180;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb6fire_control_output_vol_put(struct snd_kcontrol *kcontrol,
|
||||||
|
struct snd_ctl_elem_value *ucontrol)
|
||||||
|
{
|
||||||
|
struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
|
||||||
|
unsigned int ch = kcontrol->private_value;
|
||||||
|
int changed = 0;
|
||||||
|
|
||||||
|
if (ch > 4) {
|
||||||
|
snd_printk(KERN_ERR PREFIX "Invalid channel in volume control.");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rt->output_vol[ch] != ucontrol->value.integer.value[0]) {
|
||||||
|
rt->output_vol[ch] = ucontrol->value.integer.value[0];
|
||||||
|
changed = 1;
|
||||||
|
}
|
||||||
|
if (rt->output_vol[ch + 1] != ucontrol->value.integer.value[1]) {
|
||||||
|
rt->output_vol[ch + 1] = ucontrol->value.integer.value[1];
|
||||||
|
changed = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed)
|
||||||
|
usb6fire_control_output_vol_update(rt);
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb6fire_control_output_vol_get(struct snd_kcontrol *kcontrol,
|
||||||
|
struct snd_ctl_elem_value *ucontrol)
|
||||||
|
{
|
||||||
|
struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
|
||||||
|
unsigned int ch = kcontrol->private_value;
|
||||||
|
|
||||||
|
if (ch > 4) {
|
||||||
|
snd_printk(KERN_ERR PREFIX "Invalid channel in volume control.");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ucontrol->value.integer.value[0] = rt->output_vol[ch];
|
||||||
|
ucontrol->value.integer.value[1] = rt->output_vol[ch + 1];
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb6fire_control_output_mute_put(struct snd_kcontrol *kcontrol,
|
||||||
|
struct snd_ctl_elem_value *ucontrol)
|
||||||
|
{
|
||||||
|
struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
|
||||||
|
unsigned int ch = kcontrol->private_value;
|
||||||
|
u8 old = rt->output_mute;
|
||||||
|
u8 value = 0;
|
||||||
|
|
||||||
|
if (ch > 4) {
|
||||||
|
snd_printk(KERN_ERR PREFIX "Invalid channel in volume control.");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
rt->output_mute &= ~(3 << ch);
|
||||||
|
if (ucontrol->value.integer.value[0])
|
||||||
|
value |= 1;
|
||||||
|
if (ucontrol->value.integer.value[1])
|
||||||
|
value |= 2;
|
||||||
|
rt->output_mute |= value << ch;
|
||||||
|
|
||||||
|
if (rt->output_mute != old)
|
||||||
|
usb6fire_control_output_mute_update(rt);
|
||||||
|
|
||||||
|
return rt->output_mute != old;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb6fire_control_output_mute_get(struct snd_kcontrol *kcontrol,
|
||||||
|
struct snd_ctl_elem_value *ucontrol)
|
||||||
|
{
|
||||||
|
struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
|
||||||
|
unsigned int ch = kcontrol->private_value;
|
||||||
|
u8 value = rt->output_mute >> ch;
|
||||||
|
|
||||||
|
if (ch > 4) {
|
||||||
|
snd_printk(KERN_ERR PREFIX "Invalid channel in volume control.");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ucontrol->value.integer.value[0] = 1 & value;
|
||||||
|
value >>= 1;
|
||||||
|
ucontrol->value.integer.value[1] = 1 & value;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb6fire_control_input_vol_info(struct snd_kcontrol *kcontrol,
|
||||||
|
struct snd_ctl_elem_info *uinfo)
|
||||||
|
{
|
||||||
|
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
||||||
|
uinfo->count = 2;
|
||||||
|
uinfo->value.integer.min = 0;
|
||||||
|
uinfo->value.integer.max = 30;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb6fire_control_input_vol_put(struct snd_kcontrol *kcontrol,
|
||||||
|
struct snd_ctl_elem_value *ucontrol)
|
||||||
|
{
|
||||||
|
struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
|
||||||
|
int changed = 0;
|
||||||
|
|
||||||
|
if (rt->input_vol[0] != ucontrol->value.integer.value[0]) {
|
||||||
|
rt->input_vol[0] = ucontrol->value.integer.value[0] - 15;
|
||||||
|
changed = 1;
|
||||||
|
}
|
||||||
|
if (rt->input_vol[1] != ucontrol->value.integer.value[1]) {
|
||||||
|
rt->input_vol[1] = ucontrol->value.integer.value[1] - 15;
|
||||||
|
changed = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed)
|
||||||
|
usb6fire_control_input_vol_update(rt);
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb6fire_control_input_vol_get(struct snd_kcontrol *kcontrol,
|
||||||
|
struct snd_ctl_elem_value *ucontrol)
|
||||||
|
{
|
||||||
|
struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
|
||||||
|
|
||||||
|
ucontrol->value.integer.value[0] = rt->input_vol[0] + 15;
|
||||||
|
ucontrol->value.integer.value[1] = rt->input_vol[1] + 15;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb6fire_control_line_phono_info(struct snd_kcontrol *kcontrol,
|
||||||
|
struct snd_ctl_elem_info *uinfo)
|
||||||
|
{
|
||||||
|
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
|
||||||
|
uinfo->count = 1;
|
||||||
|
uinfo->value.enumerated.items = 2;
|
||||||
|
if (uinfo->value.enumerated.item > 1)
|
||||||
|
uinfo->value.enumerated.item = 1;
|
||||||
|
strcpy(uinfo->value.enumerated.name,
|
||||||
|
line_phono_texts[uinfo->value.enumerated.item]);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb6fire_control_line_phono_put(struct snd_kcontrol *kcontrol,
|
||||||
|
struct snd_ctl_elem_value *ucontrol)
|
||||||
|
{
|
||||||
|
struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
|
||||||
|
int changed = 0;
|
||||||
|
if (rt->line_phono_switch != ucontrol->value.integer.value[0]) {
|
||||||
|
rt->line_phono_switch = ucontrol->value.integer.value[0];
|
||||||
|
usb6fire_control_line_phono_update(rt);
|
||||||
|
changed = 1;
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb6fire_control_line_phono_get(struct snd_kcontrol *kcontrol,
|
||||||
|
struct snd_ctl_elem_value *ucontrol)
|
||||||
|
{
|
||||||
|
struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
|
||||||
|
ucontrol->value.integer.value[0] = rt->line_phono_switch;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb6fire_control_opt_coax_info(struct snd_kcontrol *kcontrol,
|
||||||
|
struct snd_ctl_elem_info *uinfo)
|
||||||
|
{
|
||||||
|
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
|
||||||
|
uinfo->count = 1;
|
||||||
|
uinfo->value.enumerated.items = 2;
|
||||||
|
if (uinfo->value.enumerated.item > 1)
|
||||||
|
uinfo->value.enumerated.item = 1;
|
||||||
|
strcpy(uinfo->value.enumerated.name,
|
||||||
|
opt_coax_texts[uinfo->value.enumerated.item]);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb6fire_control_opt_coax_put(struct snd_kcontrol *kcontrol,
|
||||||
|
struct snd_ctl_elem_value *ucontrol)
|
||||||
|
{
|
||||||
|
struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
|
||||||
|
int changed = 0;
|
||||||
|
|
||||||
|
if (rt->opt_coax_switch != ucontrol->value.enumerated.item[0]) {
|
||||||
|
rt->opt_coax_switch = ucontrol->value.enumerated.item[0];
|
||||||
|
usb6fire_control_opt_coax_update(rt);
|
||||||
|
changed = 1;
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb6fire_control_opt_coax_get(struct snd_kcontrol *kcontrol,
|
||||||
|
struct snd_ctl_elem_value *ucontrol)
|
||||||
|
{
|
||||||
|
struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
|
||||||
|
ucontrol->value.enumerated.item[0] = rt->opt_coax_switch;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb6fire_control_digital_thru_put(struct snd_kcontrol *kcontrol,
|
||||||
|
struct snd_ctl_elem_value *ucontrol)
|
||||||
|
{
|
||||||
|
struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
|
||||||
|
int changed = 0;
|
||||||
|
|
||||||
|
if (rt->digital_thru_switch != ucontrol->value.integer.value[0]) {
|
||||||
|
rt->digital_thru_switch = ucontrol->value.integer.value[0];
|
||||||
|
usb6fire_control_streaming_update(rt);
|
||||||
|
changed = 1;
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb6fire_control_digital_thru_get(struct snd_kcontrol *kcontrol,
|
||||||
|
struct snd_ctl_elem_value *ucontrol)
|
||||||
|
{
|
||||||
|
struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
|
||||||
|
ucontrol->value.integer.value[0] = rt->digital_thru_switch;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static struct snd_kcontrol_new common_elements[] = {
|
||||||
|
{
|
||||||
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||||
|
.name = "Digital Thru Playback Route",
|
||||||
|
.index = 0,
|
||||||
|
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
|
||||||
|
.info = snd_ctl_boolean_mono_info,
|
||||||
|
.get = usb6fire_control_digital_thru_get,
|
||||||
|
.put = usb6fire_control_digital_thru_put
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||||
|
.name = "Opt/Coax Capture Route",
|
||||||
|
.index = 0,
|
||||||
|
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
|
||||||
|
.info = usb6fire_control_opt_coax_info,
|
||||||
|
.get = usb6fire_control_opt_coax_get,
|
||||||
|
.put = usb6fire_control_opt_coax_put
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct snd_kcontrol_new analog_vol_elements[] = {
|
||||||
|
{
|
||||||
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||||
|
.name = "Analog Playback Volume",
|
||||||
|
.index = 1,
|
||||||
|
.private_value = 0,
|
||||||
|
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
|
||||||
|
SNDRV_CTL_ELEM_ACCESS_TLV_READ,
|
||||||
|
.info = usb6fire_control_output_vol_info,
|
||||||
|
.get = usb6fire_control_output_vol_get,
|
||||||
|
.put = usb6fire_control_output_vol_put,
|
||||||
|
.tlv = { .p = tlv_output }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||||
|
.name = "Analog Playback Volume",
|
||||||
|
.index = 2,
|
||||||
|
.private_value = 2,
|
||||||
|
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
|
||||||
|
SNDRV_CTL_ELEM_ACCESS_TLV_READ,
|
||||||
|
.info = usb6fire_control_output_vol_info,
|
||||||
|
.get = usb6fire_control_output_vol_get,
|
||||||
|
.put = usb6fire_control_output_vol_put,
|
||||||
|
.tlv = { .p = tlv_output }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||||
|
.name = "Analog Playback Volume",
|
||||||
|
.index = 3,
|
||||||
|
.private_value = 4,
|
||||||
|
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
|
||||||
|
SNDRV_CTL_ELEM_ACCESS_TLV_READ,
|
||||||
|
.info = usb6fire_control_output_vol_info,
|
||||||
|
.get = usb6fire_control_output_vol_get,
|
||||||
|
.put = usb6fire_control_output_vol_put,
|
||||||
|
.tlv = { .p = tlv_output }
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct snd_kcontrol_new analog_mute_elements[] = {
|
||||||
|
{
|
||||||
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||||
|
.name = "Analog Playback Switch",
|
||||||
|
.index = 1,
|
||||||
|
.private_value = 0,
|
||||||
|
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
|
||||||
|
.info = snd_ctl_boolean_stereo_info,
|
||||||
|
.get = usb6fire_control_output_mute_get,
|
||||||
|
.put = usb6fire_control_output_mute_put
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||||
|
.name = "Analog Playback Switch",
|
||||||
|
.index = 2,
|
||||||
|
.private_value = 2,
|
||||||
|
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
|
||||||
|
.info = snd_ctl_boolean_stereo_info,
|
||||||
|
.get = usb6fire_control_output_mute_get,
|
||||||
|
.put = usb6fire_control_output_mute_put
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||||
|
.name = "Analog Playback Switch",
|
||||||
|
.index = 3,
|
||||||
|
.private_value = 4,
|
||||||
|
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
|
||||||
|
.info = snd_ctl_boolean_stereo_info,
|
||||||
|
.get = usb6fire_control_output_mute_get,
|
||||||
|
.put = usb6fire_control_output_mute_put
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct snd_kcontrol_new analog_elements[] = {
|
||||||
|
{
|
||||||
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||||
|
.name = "Line/Phono Capture Route",
|
||||||
|
.index = 0,
|
||||||
|
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
|
||||||
|
.info = usb6fire_control_line_phono_info,
|
||||||
|
.get = usb6fire_control_line_phono_get,
|
||||||
|
.put = usb6fire_control_line_phono_put
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
||||||
|
.name = "Analog Capture Volume",
|
||||||
|
.index = 0,
|
||||||
|
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
|
||||||
|
SNDRV_CTL_ELEM_ACCESS_TLV_READ,
|
||||||
|
.info = usb6fire_control_input_vol_info,
|
||||||
|
.get = usb6fire_control_input_vol_get,
|
||||||
|
.put = usb6fire_control_input_vol_put,
|
||||||
|
.tlv = { .p = tlv_input }
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct snd_kcontrol_new spdif_elements[] = {
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
int usb6fire_control_add_elements(struct control_runtime *rt, struct snd_card *card, struct snd_kcontrol_new *elements)
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
while (elements[i].name) {
|
||||||
|
ret = snd_ctl_add(card, snd_ctl_new1(&elements[i], rt));
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb6fire_control_add_virtual(
|
||||||
|
struct control_runtime *rt,
|
||||||
|
struct snd_card *card,
|
||||||
|
char *name,
|
||||||
|
struct snd_kcontrol_new *elems)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
int i;
|
||||||
|
struct snd_kcontrol *vmaster = snd_ctl_make_virtual_master(name, NULL);
|
||||||
|
struct snd_kcontrol *control;
|
||||||
|
|
||||||
|
if (!vmaster)
|
||||||
|
return -ENOMEM;
|
||||||
|
ret = snd_ctl_add(card, vmaster);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
i = 0;
|
||||||
|
while (elems[i].name) {
|
||||||
|
control = snd_ctl_new1(&elems[i], rt);
|
||||||
|
if (!control)
|
||||||
|
return -ENOMEM;
|
||||||
|
ret = snd_ctl_add(card, control);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
ret = snd_ctl_add_slave(vmaster, control);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int usb6fire_control_init(struct sfire_chip *chip)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
int ret;
|
||||||
|
struct control_runtime *rt = kzalloc(sizeof(struct control_runtime),
|
||||||
|
GFP_KERNEL);
|
||||||
|
struct comm_runtime *comm_rt = chip->comm;
|
||||||
|
|
||||||
|
if (!rt)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
rt->chip = chip;
|
||||||
|
rt->update_streaming = usb6fire_control_streaming_update;
|
||||||
|
rt->set_rate = usb6fire_control_set_rate;
|
||||||
|
|
||||||
|
i = 0;
|
||||||
|
while (init_data[i].type) {
|
||||||
|
comm_rt->write8(comm_rt, init_data[i].type, init_data[i].reg,
|
||||||
|
init_data[i].value);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
usb6fire_control_opt_coax_update(rt);
|
||||||
|
usb6fire_control_line_phono_update(rt);
|
||||||
|
usb6fire_control_output_vol_update(rt);
|
||||||
|
usb6fire_control_output_mute_update(rt);
|
||||||
|
usb6fire_control_input_vol_update(rt);
|
||||||
|
usb6fire_control_streaming_update(rt);
|
||||||
|
|
||||||
|
if (PCM_MODE_USES_ANALOG(chip->pcm_mode)) {
|
||||||
|
ret = usb6fire_control_add_virtual(rt, chip->card,
|
||||||
|
"Master Playback Volume", analog_vol_elements);
|
||||||
|
if (ret < 0) {
|
||||||
|
snd_printk(KERN_ERR "Cannot add control.");
|
||||||
|
kfree(rt);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
ret = usb6fire_control_add_virtual(rt, chip->card,
|
||||||
|
"Master Playback Switch", analog_mute_elements);
|
||||||
|
if (ret < 0) {
|
||||||
|
snd_printk(KERN_ERR "Cannot add control.");
|
||||||
|
kfree(rt);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
ret = usb6fire_control_add_elements(rt, chip->card, analog_elements);
|
||||||
|
if (ret < 0) {
|
||||||
|
snd_printk(KERN_ERR "Cannot add control.");
|
||||||
|
kfree(rt);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PCM_MODE_USES_SPDIF(chip->pcm_mode)) {
|
||||||
|
ret = usb6fire_control_add_elements(rt, chip->card, spdif_elements);
|
||||||
|
if (ret < 0) {
|
||||||
|
snd_printk(KERN_ERR "Cannot add control.");
|
||||||
|
kfree(rt);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = usb6fire_control_add_elements(rt, chip->card, common_elements);
|
||||||
|
if (ret < 0) {
|
||||||
|
snd_printk(KERN_ERR "Cannot add control.");
|
||||||
|
kfree(rt);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
chip->control = rt;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void usb6fire_control_abort(struct sfire_chip *chip)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void usb6fire_control_destroy(struct sfire_chip *chip)
|
||||||
|
{
|
||||||
|
kfree(chip->control);
|
||||||
|
chip->control = NULL;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* Linux driver for TerraTec DMX 6Fire USB
|
||||||
|
*
|
||||||
|
* Author: Torsten Schenk <torsten.schenk@zoho.com>
|
||||||
|
* Created: Jan 01, 2011
|
||||||
|
* Copyright: (C) Torsten Schenk
|
||||||
|
*
|
||||||
|
* Thanks to:
|
||||||
|
* - Holger Ruckdeschel: he found out how to control individual analog
|
||||||
|
* channel volumes and introduced mute switches
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef USB6FIRE_CONTROL_H
|
||||||
|
#define USB6FIRE_CONTROL_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
struct control_runtime {
|
||||||
|
int (*update_streaming)(struct control_runtime *rt);
|
||||||
|
int (*set_rate)(struct control_runtime *rt, unsigned int rate);
|
||||||
|
|
||||||
|
struct sfire_chip *chip;
|
||||||
|
|
||||||
|
bool opt_coax_switch;
|
||||||
|
bool line_phono_switch;
|
||||||
|
bool digital_thru_switch;
|
||||||
|
bool usb_streaming;
|
||||||
|
bool use_analog;
|
||||||
|
bool use_spdif;
|
||||||
|
|
||||||
|
u8 output_vol[6];
|
||||||
|
u8 output_mute;
|
||||||
|
s8 input_vol[2];
|
||||||
|
};
|
||||||
|
|
||||||
|
int usb6fire_control_init(struct sfire_chip *chip);
|
||||||
|
void usb6fire_control_abort(struct sfire_chip *chip);
|
||||||
|
void usb6fire_control_destroy(struct sfire_chip *chip);
|
||||||
|
#endif /* USB6FIRE_CONTROL_H */
|
||||||
|
|
|
@ -0,0 +1,448 @@
|
||||||
|
/*
|
||||||
|
* Linux driver for TerraTec DMX 6Fire USB
|
||||||
|
*
|
||||||
|
* Firmware loader
|
||||||
|
*
|
||||||
|
* 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 <linux/firmware.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/bitrev.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
|
||||||
|
#include "firmware.h"
|
||||||
|
//#include "chip.h"
|
||||||
|
|
||||||
|
MODULE_FIRMWARE("6fire/dmx6firel2.ihx");
|
||||||
|
MODULE_FIRMWARE("6fire/dmx6fireap.ihx");
|
||||||
|
MODULE_FIRMWARE("6fire/dmx6firecf.bin");
|
||||||
|
|
||||||
|
enum {
|
||||||
|
FPGA_BUFSIZE = 512, FPGA_EP = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* wMaxPacketSize of pcm endpoints.
|
||||||
|
* keep synced with rates_in_packet_size and rates_out_packet_size in pcm.c
|
||||||
|
* fpp: frames per isopacket
|
||||||
|
*
|
||||||
|
* CAUTION: keep sizeof <= buffer[] in usb6fire_fw_init
|
||||||
|
*/
|
||||||
|
static const u8 ep_w_max_packet_size[] = {
|
||||||
|
0xe4, 0x00, 0xe4, 0x00, /* alt 1: 228 EP2 and EP6 (7 fpp) */
|
||||||
|
0xa4, 0x01, 0xa4, 0x01, /* alt 2: 420 EP2 and EP6 (13 fpp)*/
|
||||||
|
0x94, 0x01, 0x5c, 0x02 /* alt 3: 404 EP2 and 604 EP6 (25 fpp) */
|
||||||
|
};
|
||||||
|
|
||||||
|
static const u8 known_fw_versions[][2] = {
|
||||||
|
{ 0x03, 0x01 }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ihex_record {
|
||||||
|
u16 address;
|
||||||
|
u8 len;
|
||||||
|
u8 data[256];
|
||||||
|
char error; /* true if an error occurred parsing this record */
|
||||||
|
|
||||||
|
u8 max_len; /* maximum record length in whole ihex */
|
||||||
|
|
||||||
|
/* private */
|
||||||
|
const char *txt_data;
|
||||||
|
unsigned int txt_length;
|
||||||
|
unsigned int txt_offset; /* current position in txt_data */
|
||||||
|
};
|
||||||
|
|
||||||
|
static u8 usb6fire_fw_ihex_hex(const u8 *data, u8 *crc)
|
||||||
|
{
|
||||||
|
u8 val = 0;
|
||||||
|
int hval;
|
||||||
|
|
||||||
|
hval = hex_to_bin(data[0]);
|
||||||
|
if (hval >= 0)
|
||||||
|
val |= (hval << 4);
|
||||||
|
|
||||||
|
hval = hex_to_bin(data[1]);
|
||||||
|
if (hval >= 0)
|
||||||
|
val |= hval;
|
||||||
|
|
||||||
|
*crc += val;
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* returns true if record is available, false otherwise.
|
||||||
|
* iff an error occurred, false will be returned and record->error will be true.
|
||||||
|
*/
|
||||||
|
static bool usb6fire_fw_ihex_next_record(struct ihex_record *record)
|
||||||
|
{
|
||||||
|
u8 crc = 0;
|
||||||
|
u8 type;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
record->error = false;
|
||||||
|
|
||||||
|
/* find begin of record (marked by a colon) */
|
||||||
|
while (record->txt_offset < record->txt_length
|
||||||
|
&& record->txt_data[record->txt_offset] != ':')
|
||||||
|
record->txt_offset++;
|
||||||
|
if (record->txt_offset == record->txt_length)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* number of characters needed for len, addr and type entries */
|
||||||
|
record->txt_offset++;
|
||||||
|
if (record->txt_offset + 8 > record->txt_length) {
|
||||||
|
record->error = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
record->len = usb6fire_fw_ihex_hex(record->txt_data +
|
||||||
|
record->txt_offset, &crc);
|
||||||
|
record->txt_offset += 2;
|
||||||
|
record->address = usb6fire_fw_ihex_hex(record->txt_data +
|
||||||
|
record->txt_offset, &crc) << 8;
|
||||||
|
record->txt_offset += 2;
|
||||||
|
record->address |= usb6fire_fw_ihex_hex(record->txt_data +
|
||||||
|
record->txt_offset, &crc);
|
||||||
|
record->txt_offset += 2;
|
||||||
|
type = usb6fire_fw_ihex_hex(record->txt_data +
|
||||||
|
record->txt_offset, &crc);
|
||||||
|
record->txt_offset += 2;
|
||||||
|
|
||||||
|
/* number of characters needed for data and crc entries */
|
||||||
|
if (record->txt_offset + 2 * (record->len + 1) > record->txt_length) {
|
||||||
|
record->error = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (i = 0; i < record->len; i++) {
|
||||||
|
record->data[i] = usb6fire_fw_ihex_hex(record->txt_data
|
||||||
|
+ record->txt_offset, &crc);
|
||||||
|
record->txt_offset += 2;
|
||||||
|
}
|
||||||
|
usb6fire_fw_ihex_hex(record->txt_data + record->txt_offset, &crc);
|
||||||
|
if (crc) {
|
||||||
|
record->error = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == 1 || !record->len) /* eof */
|
||||||
|
return false;
|
||||||
|
else if (type == 0)
|
||||||
|
return true;
|
||||||
|
else {
|
||||||
|
record->error = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb6fire_fw_ihex_init(const struct firmware *fw,
|
||||||
|
struct ihex_record *record)
|
||||||
|
{
|
||||||
|
record->txt_data = fw->data;
|
||||||
|
record->txt_length = fw->size;
|
||||||
|
record->txt_offset = 0;
|
||||||
|
record->max_len = 0;
|
||||||
|
/* read all records, if loop ends, record->error indicates,
|
||||||
|
* whether ihex is valid. */
|
||||||
|
while (usb6fire_fw_ihex_next_record(record))
|
||||||
|
record->max_len = max(record->len, record->max_len);
|
||||||
|
if (record->error)
|
||||||
|
return -EINVAL;
|
||||||
|
record->txt_offset = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb6fire_fw_ezusb_write(struct usb_device *device,
|
||||||
|
int type, int value, char *data, int len)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = usb_control_msg(device, usb_sndctrlpipe(device, 0), type,
|
||||||
|
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
|
||||||
|
value, 0, data, len, HZ);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
else if (ret != len)
|
||||||
|
return -EIO;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb6fire_fw_ezusb_read(struct usb_device *device,
|
||||||
|
int type, int value, char *data, int len)
|
||||||
|
{
|
||||||
|
int ret = usb_control_msg(device, usb_rcvctrlpipe(device, 0), type,
|
||||||
|
USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, value,
|
||||||
|
0, data, len, HZ);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
else if (ret != len)
|
||||||
|
return -EIO;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb6fire_fw_fpga_write(struct usb_device *device,
|
||||||
|
char *data, int len)
|
||||||
|
{
|
||||||
|
int actual_len;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = usb_bulk_msg(device, usb_sndbulkpipe(device, FPGA_EP), data, len,
|
||||||
|
&actual_len, HZ);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
else if (actual_len != len)
|
||||||
|
return -EIO;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb6fire_fw_ezusb_upload(
|
||||||
|
struct usb_interface *intf, const char *fwname,
|
||||||
|
unsigned int postaddr, u8 *postdata, unsigned int postlen)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
u8 *data;
|
||||||
|
struct usb_device *device = interface_to_usbdev(intf);
|
||||||
|
const struct firmware *fw = NULL;
|
||||||
|
struct ihex_record *rec = kmalloc(sizeof(struct ihex_record),
|
||||||
|
GFP_KERNEL);
|
||||||
|
|
||||||
|
if (!rec)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
data = kmalloc(1, GFP_KERNEL);
|
||||||
|
if (!data) {
|
||||||
|
kfree(rec);
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = request_firmware(&fw, fwname, &device->dev);
|
||||||
|
if (ret < 0) {
|
||||||
|
kfree(data);
|
||||||
|
kfree(rec);
|
||||||
|
snd_printk(KERN_ERR PREFIX "error requesting ezusb "
|
||||||
|
"firmware %s.\n", fwname);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
ret = usb6fire_fw_ihex_init(fw, rec);
|
||||||
|
if (ret < 0) {
|
||||||
|
kfree(data);
|
||||||
|
kfree(rec);
|
||||||
|
release_firmware(fw);
|
||||||
|
snd_printk(KERN_ERR PREFIX "error validating ezusb "
|
||||||
|
"firmware %s.\n", fwname);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
/* upload firmware image */
|
||||||
|
*data = 0x01; /* stop ezusb cpu */
|
||||||
|
ret = usb6fire_fw_ezusb_write(device, 0xa0, 0xe600, data, 1);
|
||||||
|
if (ret < 0) {
|
||||||
|
kfree(data);
|
||||||
|
kfree(rec);
|
||||||
|
release_firmware(fw);
|
||||||
|
snd_printk(KERN_ERR PREFIX "unable to upload ezusb "
|
||||||
|
"firmware %s: begin message.\n", fwname);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (usb6fire_fw_ihex_next_record(rec)) { /* write firmware */
|
||||||
|
ret = usb6fire_fw_ezusb_write(device, 0xa0, rec->address,
|
||||||
|
rec->data, rec->len);
|
||||||
|
if (ret < 0) {
|
||||||
|
kfree(data);
|
||||||
|
kfree(rec);
|
||||||
|
release_firmware(fw);
|
||||||
|
snd_printk(KERN_ERR PREFIX "unable to upload ezusb "
|
||||||
|
"firmware %s: data urb.\n", fwname);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
release_firmware(fw);
|
||||||
|
kfree(rec);
|
||||||
|
if (postdata) { /* write data after firmware has been uploaded */
|
||||||
|
ret = usb6fire_fw_ezusb_write(device, 0xa0, postaddr,
|
||||||
|
postdata, postlen);
|
||||||
|
if (ret < 0) {
|
||||||
|
snd_printk(KERN_ERR PREFIX "unable to upload ezusb "
|
||||||
|
"firmware %s: post urb.\n", fwname);
|
||||||
|
kfree(data);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*data = 0x00; /* resume ezusb cpu */
|
||||||
|
ret = usb6fire_fw_ezusb_write(device, 0xa0, 0xe600, data, 1);
|
||||||
|
kfree(data);
|
||||||
|
if (ret < 0) {
|
||||||
|
snd_printk(KERN_ERR PREFIX "unable to upload ezusb "
|
||||||
|
"firmware %s: end message.\n", fwname);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb6fire_fw_fpga_upload(
|
||||||
|
struct usb_interface *intf, const char *fwname)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
int i;
|
||||||
|
struct usb_device *device = interface_to_usbdev(intf);
|
||||||
|
u8 *buffer = kmalloc(FPGA_BUFSIZE, GFP_KERNEL);
|
||||||
|
const char *c;
|
||||||
|
const char *end;
|
||||||
|
const struct firmware *fw;
|
||||||
|
|
||||||
|
if (!buffer)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
ret = request_firmware(&fw, fwname, &device->dev);
|
||||||
|
if (ret < 0) {
|
||||||
|
snd_printk(KERN_ERR PREFIX "unable to get fpga firmware %s.\n",
|
||||||
|
fwname);
|
||||||
|
kfree(buffer);
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
c = fw->data;
|
||||||
|
end = fw->data + fw->size;
|
||||||
|
|
||||||
|
ret = usb6fire_fw_ezusb_write(device, 8, 0, NULL, 0);
|
||||||
|
if (ret < 0) {
|
||||||
|
kfree(buffer);
|
||||||
|
release_firmware(fw);
|
||||||
|
snd_printk(KERN_ERR PREFIX "unable to upload fpga firmware: "
|
||||||
|
"begin urb.\n");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (c != end) {
|
||||||
|
for (i = 0; c != end && i < FPGA_BUFSIZE; i++, c++)
|
||||||
|
buffer[i] = byte_rev_table[(u8) *c];
|
||||||
|
|
||||||
|
ret = usb6fire_fw_fpga_write(device, buffer, i);
|
||||||
|
if (ret < 0) {
|
||||||
|
release_firmware(fw);
|
||||||
|
kfree(buffer);
|
||||||
|
snd_printk(KERN_ERR PREFIX "unable to upload fpga "
|
||||||
|
"firmware: fw urb.\n");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
release_firmware(fw);
|
||||||
|
kfree(buffer);
|
||||||
|
|
||||||
|
ret = usb6fire_fw_ezusb_write(device, 9, 0, NULL, 0);
|
||||||
|
if (ret < 0) {
|
||||||
|
snd_printk(KERN_ERR PREFIX "unable to upload fpga firmware: "
|
||||||
|
"end urb.\n");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check, if the firmware version the devices has currently loaded
|
||||||
|
* is known by this driver. 'version' needs to have 4 bytes version
|
||||||
|
* info data. */
|
||||||
|
static int usb6fire_fw_check(u8 *version)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < ARRAY_SIZE(known_fw_versions); i++)
|
||||||
|
if (!memcmp(version, known_fw_versions + i, 2))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
snd_printk(KERN_ERR PREFIX "invalid fimware version in device: "
|
||||||
|
"%02x %02x %02x %02x. "
|
||||||
|
"please reconnect to power. if this failure "
|
||||||
|
"still happens, check your firmware installation.",
|
||||||
|
version[0], version[1], version[2], version[3]);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int usb6fire_fw_init(struct usb_interface *intf)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
int ret;
|
||||||
|
struct usb_device *device = interface_to_usbdev(intf);
|
||||||
|
/* buffer: 8 receiving bytes from device and
|
||||||
|
* sizeof(EP_W_MAX_PACKET_SIZE) bytes for non-const copy */
|
||||||
|
u8 *buffer = kmalloc(12, GFP_KERNEL);
|
||||||
|
|
||||||
|
ret = usb6fire_fw_ezusb_read(device, 1, 0, buffer, 8);
|
||||||
|
if (ret < 0) {
|
||||||
|
snd_printk(KERN_ERR PREFIX "unable to receive device "
|
||||||
|
"firmware state.\n");
|
||||||
|
kfree(buffer);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buffer[0] != 0xeb || buffer[1] != 0xaa || buffer[2] != 0x55) {
|
||||||
|
snd_printk(KERN_ERR PREFIX "unknown device firmware state "
|
||||||
|
"received from device: ");
|
||||||
|
for (i = 0; i < 8; i++)
|
||||||
|
snd_printk("%02x ", buffer[i]);
|
||||||
|
snd_printk("\n");
|
||||||
|
kfree(buffer);
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
/* do we need fpga loader ezusb firmware? */
|
||||||
|
if (buffer[3] == 0x01) {
|
||||||
|
ret = usb6fire_fw_ezusb_upload(intf,
|
||||||
|
"6fire/dmx6firel2.ihx", 0, NULL, 0);
|
||||||
|
if (ret < 0) {
|
||||||
|
kfree(buffer);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
kfree(buffer);
|
||||||
|
return FW_NOT_READY;
|
||||||
|
}
|
||||||
|
/* do we need fpga firmware and application ezusb firmware? */
|
||||||
|
else if (buffer[3] == 0x02) {
|
||||||
|
ret = usb6fire_fw_check(buffer + 4);
|
||||||
|
if (ret < 0) {
|
||||||
|
kfree(buffer);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
ret = usb6fire_fw_fpga_upload(intf, "6fire/dmx6firecf.bin");
|
||||||
|
if (ret < 0) {
|
||||||
|
kfree(buffer);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
memcpy(buffer, ep_w_max_packet_size,
|
||||||
|
sizeof(ep_w_max_packet_size));
|
||||||
|
ret = usb6fire_fw_ezusb_upload(intf, "6fire/dmx6fireap.ihx",
|
||||||
|
0x0003, buffer, sizeof(ep_w_max_packet_size));
|
||||||
|
if (ret < 0) {
|
||||||
|
kfree(buffer);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
kfree(buffer);
|
||||||
|
return FW_NOT_READY;
|
||||||
|
}
|
||||||
|
/* all fw loaded? */
|
||||||
|
else if (buffer[3] == 0x03) {
|
||||||
|
ret = usb6fire_fw_check(buffer + 4);;
|
||||||
|
kfree(buffer);
|
||||||
|
return ret;
|
||||||
|
/* unknown data? */
|
||||||
|
} else {
|
||||||
|
snd_printk(KERN_ERR PREFIX "unknown device firmware state "
|
||||||
|
"received from device: ");
|
||||||
|
for (i = 0; i < 8; i++)
|
||||||
|
snd_printk("%02x ", buffer[i]);
|
||||||
|
snd_printk("\n");
|
||||||
|
kfree(buffer);
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
kfree(buffer);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Linux driver for TerraTec DMX 6Fire USB
|
||||||
|
*
|
||||||
|
* Author: Torsten Schenk
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef USB6FIRE_FIRMWARE_H
|
||||||
|
#define USB6FIRE_FIRMWARE_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
enum /* firmware state of device */
|
||||||
|
{
|
||||||
|
FW_READY = 0,
|
||||||
|
FW_NOT_READY = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
int usb6fire_fw_init(struct usb_interface *intf);
|
||||||
|
#endif /* USB6FIRE_FIRMWARE_H */
|
||||||
|
|
|
@ -0,0 +1,215 @@
|
||||||
|
/*
|
||||||
|
* Linux driver for TerraTec DMX 6Fire USB
|
||||||
|
*
|
||||||
|
* Rawmidi driver
|
||||||
|
*
|
||||||
|
* 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 <sound/rawmidi.h>
|
||||||
|
|
||||||
|
#include "midi.h"
|
||||||
|
#include "chip.h"
|
||||||
|
#include "comm.h"
|
||||||
|
|
||||||
|
enum {
|
||||||
|
MIDI_BUFSIZE = 64
|
||||||
|
};
|
||||||
|
|
||||||
|
static void usb6fire_midi_out_handler(struct urb *urb)
|
||||||
|
{
|
||||||
|
struct midi_runtime *rt = urb->context;
|
||||||
|
int ret;
|
||||||
|
unsigned long flags;
|
||||||
|
|
||||||
|
spin_lock_irqsave(&rt->out_lock, flags);
|
||||||
|
|
||||||
|
if (rt->out) {
|
||||||
|
ret = snd_rawmidi_transmit(rt->out, rt->out_buffer + 4,
|
||||||
|
MIDI_BUFSIZE - 4);
|
||||||
|
if (ret > 0) { /* more data available, send next packet */
|
||||||
|
rt->out_buffer[1] = ret + 2;
|
||||||
|
rt->out_buffer[3] = rt->out_serial++;
|
||||||
|
urb->transfer_buffer_length = ret + 4;
|
||||||
|
|
||||||
|
ret = usb_submit_urb(urb, GFP_ATOMIC);
|
||||||
|
if (ret < 0)
|
||||||
|
snd_printk(KERN_ERR PREFIX "midi out urb "
|
||||||
|
"submit failed: %d\n", ret);
|
||||||
|
} else /* no more data to transmit */
|
||||||
|
rt->out = NULL;
|
||||||
|
}
|
||||||
|
spin_unlock_irqrestore(&rt->out_lock, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usb6fire_midi_in_received(
|
||||||
|
struct midi_runtime *rt, u8 *data, int length)
|
||||||
|
{
|
||||||
|
unsigned long flags;
|
||||||
|
|
||||||
|
spin_lock_irqsave(&rt->in_lock, flags);
|
||||||
|
if (rt->in)
|
||||||
|
snd_rawmidi_receive(rt->in, data, length);
|
||||||
|
spin_unlock_irqrestore(&rt->in_lock, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb6fire_midi_out_open(struct snd_rawmidi_substream *alsa_sub)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb6fire_midi_out_close(struct snd_rawmidi_substream *alsa_sub)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usb6fire_midi_out_trigger(
|
||||||
|
struct snd_rawmidi_substream *alsa_sub, int up)
|
||||||
|
{
|
||||||
|
struct midi_runtime *rt = alsa_sub->rmidi->private_data;
|
||||||
|
struct urb *urb = &rt->out_urb;
|
||||||
|
__s8 ret;
|
||||||
|
unsigned long flags;
|
||||||
|
|
||||||
|
spin_lock_irqsave(&rt->out_lock, flags);
|
||||||
|
if (up) { /* start transfer */
|
||||||
|
if (rt->out) { /* we are already transmitting so just return */
|
||||||
|
spin_unlock_irqrestore(&rt->out_lock, flags);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = snd_rawmidi_transmit(alsa_sub, rt->out_buffer + 4,
|
||||||
|
MIDI_BUFSIZE - 4);
|
||||||
|
if (ret > 0) {
|
||||||
|
rt->out_buffer[1] = ret + 2;
|
||||||
|
rt->out_buffer[3] = rt->out_serial++;
|
||||||
|
urb->transfer_buffer_length = ret + 4;
|
||||||
|
|
||||||
|
ret = usb_submit_urb(urb, GFP_ATOMIC);
|
||||||
|
if (ret < 0)
|
||||||
|
snd_printk(KERN_ERR PREFIX "midi out urb "
|
||||||
|
"submit failed: %d\n", ret);
|
||||||
|
else
|
||||||
|
rt->out = alsa_sub;
|
||||||
|
}
|
||||||
|
} else if (rt->out == alsa_sub)
|
||||||
|
rt->out = NULL;
|
||||||
|
spin_unlock_irqrestore(&rt->out_lock, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usb6fire_midi_out_drain(struct snd_rawmidi_substream *alsa_sub)
|
||||||
|
{
|
||||||
|
struct midi_runtime *rt = alsa_sub->rmidi->private_data;
|
||||||
|
int retry = 0;
|
||||||
|
|
||||||
|
while (rt->out && retry++ < 100)
|
||||||
|
msleep(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb6fire_midi_in_open(struct snd_rawmidi_substream *alsa_sub)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb6fire_midi_in_close(struct snd_rawmidi_substream *alsa_sub)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usb6fire_midi_in_trigger(
|
||||||
|
struct snd_rawmidi_substream *alsa_sub, int up)
|
||||||
|
{
|
||||||
|
struct midi_runtime *rt = alsa_sub->rmidi->private_data;
|
||||||
|
unsigned long flags;
|
||||||
|
|
||||||
|
spin_lock_irqsave(&rt->in_lock, flags);
|
||||||
|
if (up)
|
||||||
|
rt->in = alsa_sub;
|
||||||
|
else
|
||||||
|
rt->in = NULL;
|
||||||
|
spin_unlock_irqrestore(&rt->in_lock, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct snd_rawmidi_ops out_ops = {
|
||||||
|
.open = usb6fire_midi_out_open,
|
||||||
|
.close = usb6fire_midi_out_close,
|
||||||
|
.trigger = usb6fire_midi_out_trigger,
|
||||||
|
.drain = usb6fire_midi_out_drain
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct snd_rawmidi_ops in_ops = {
|
||||||
|
.open = usb6fire_midi_in_open,
|
||||||
|
.close = usb6fire_midi_in_close,
|
||||||
|
.trigger = usb6fire_midi_in_trigger
|
||||||
|
};
|
||||||
|
|
||||||
|
int usb6fire_midi_init(struct sfire_chip *chip)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
struct midi_runtime *rt = kzalloc(sizeof(struct midi_runtime),
|
||||||
|
GFP_KERNEL);
|
||||||
|
struct comm_runtime *comm_rt = chip->comm;
|
||||||
|
|
||||||
|
if (!rt)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
rt->out_buffer = kzalloc(MIDI_BUFSIZE, GFP_KERNEL);
|
||||||
|
if(!rt->out_buffer) {
|
||||||
|
kfree(rt);
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
rt->chip = chip;
|
||||||
|
rt->in_received = usb6fire_midi_in_received;
|
||||||
|
rt->out_buffer[0] = 0x80; /* 'send midi' command */
|
||||||
|
rt->out_buffer[1] = 0x00; /* size of data */
|
||||||
|
rt->out_buffer[2] = 0x00; /* always 0 */
|
||||||
|
spin_lock_init(&rt->in_lock);
|
||||||
|
spin_lock_init(&rt->out_lock);
|
||||||
|
|
||||||
|
comm_rt->init_urb(comm_rt, &rt->out_urb, rt->out_buffer, rt,
|
||||||
|
usb6fire_midi_out_handler);
|
||||||
|
|
||||||
|
ret = snd_rawmidi_new(chip->card, "6FireUSB", 0, 1, 1, &rt->instance);
|
||||||
|
if (ret < 0) {
|
||||||
|
kfree(rt);
|
||||||
|
snd_printk(KERN_ERR PREFIX "unable to create midi.\n");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
rt->instance->private_data = rt;
|
||||||
|
strcpy(rt->instance->name, "DMX6FireUSB MIDI");
|
||||||
|
rt->instance->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT |
|
||||||
|
SNDRV_RAWMIDI_INFO_INPUT |
|
||||||
|
SNDRV_RAWMIDI_INFO_DUPLEX;
|
||||||
|
snd_rawmidi_set_ops(rt->instance, SNDRV_RAWMIDI_STREAM_OUTPUT,
|
||||||
|
&out_ops);
|
||||||
|
snd_rawmidi_set_ops(rt->instance, SNDRV_RAWMIDI_STREAM_INPUT,
|
||||||
|
&in_ops);
|
||||||
|
|
||||||
|
chip->midi = rt;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void usb6fire_midi_abort(struct sfire_chip *chip)
|
||||||
|
{
|
||||||
|
struct midi_runtime *rt = chip->midi;
|
||||||
|
|
||||||
|
if (rt)
|
||||||
|
usb_poison_urb(&rt->out_urb);
|
||||||
|
}
|
||||||
|
|
||||||
|
void usb6fire_midi_destroy(struct sfire_chip *chip)
|
||||||
|
{
|
||||||
|
struct midi_runtime *rt = chip->midi;
|
||||||
|
|
||||||
|
kfree(rt->out_buffer);
|
||||||
|
kfree(rt);
|
||||||
|
chip->midi = NULL;
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* Linux driver for TerraTec DMX 6Fire USB
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef USB6FIRE_MIDI_H
|
||||||
|
#define USB6FIRE_MIDI_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
struct midi_runtime {
|
||||||
|
struct sfire_chip *chip;
|
||||||
|
struct snd_rawmidi *instance;
|
||||||
|
|
||||||
|
struct snd_rawmidi_substream *in;
|
||||||
|
char in_active;
|
||||||
|
|
||||||
|
spinlock_t in_lock;
|
||||||
|
spinlock_t out_lock;
|
||||||
|
struct snd_rawmidi_substream *out;
|
||||||
|
struct urb out_urb;
|
||||||
|
u8 out_serial; /* serial number of out packet */
|
||||||
|
u8 *out_buffer;
|
||||||
|
int buffer_offset;
|
||||||
|
|
||||||
|
void (*in_received)(struct midi_runtime *rt, u8 *data, int length);
|
||||||
|
};
|
||||||
|
|
||||||
|
int usb6fire_midi_init(struct sfire_chip *chip);
|
||||||
|
void usb6fire_midi_abort(struct sfire_chip *chip);
|
||||||
|
void usb6fire_midi_destroy(struct sfire_chip *chip);
|
||||||
|
#endif /* USB6FIRE_MIDI_H */
|
||||||
|
|
|
@ -0,0 +1,243 @@
|
||||||
|
/*
|
||||||
|
* Linux driver for TerraTec DMX 6Fire USB
|
||||||
|
*
|
||||||
|
* ALSA PCM interface.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//TODO URL: http://linux.die.net/man/1/iecset http://alsa.opensrc.org/DigitalOut http://lxr.free-electrons.com/source/sound/pci/hda/hda_codec.c#L2705 http://www.epanorama.net/documents/audio/spdif.html http://www.cs.fsu.edu/~baker/devices/lxr/http/source/linux/include/sound/control.h#L169
|
||||||
|
|
||||||
|
#include "pcm.h"
|
||||||
|
#include "chip.h"
|
||||||
|
#include "substream.h"
|
||||||
|
#include "urbs.h"
|
||||||
|
|
||||||
|
enum {
|
||||||
|
MAX_BUFSIZE = 1024 * 1024,
|
||||||
|
MIN_PERIODS = 2,
|
||||||
|
MAX_PERIODS = 64
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct snd_pcm_hardware pcm_hw = {
|
||||||
|
.info = SNDRV_PCM_INFO_MMAP |
|
||||||
|
SNDRV_PCM_INFO_INTERLEAVED |
|
||||||
|
SNDRV_PCM_INFO_BLOCK_TRANSFER |
|
||||||
|
SNDRV_PCM_INFO_MMAP_VALID |
|
||||||
|
SNDRV_PCM_INFO_BATCH,
|
||||||
|
|
||||||
|
.channels_min = 1,
|
||||||
|
.period_bytes_max = MAX_BUFSIZE / MIN_PERIODS,
|
||||||
|
.periods_min = MIN_PERIODS,
|
||||||
|
.buffer_bytes_max = MAX_BUFSIZE,
|
||||||
|
.periods_max = MAX_PERIODS
|
||||||
|
};
|
||||||
|
|
||||||
|
static int usb6fire_pcm_rule_period_size(struct snd_pcm_hw_params *params,
|
||||||
|
struct snd_pcm_hw_rule *rule)
|
||||||
|
{
|
||||||
|
struct snd_interval *sri = hw_param_interval(params,
|
||||||
|
SNDRV_PCM_HW_PARAM_RATE);
|
||||||
|
struct snd_interval *psi = hw_param_interval(params,
|
||||||
|
SNDRV_PCM_HW_PARAM_PERIOD_SIZE);
|
||||||
|
struct sfire_chip *chip = rule->private;
|
||||||
|
struct urbs_runtime *urbs_rt = chip->urbs;
|
||||||
|
unsigned int min_size;
|
||||||
|
|
||||||
|
if (chip->shutdown || !urbs_rt)
|
||||||
|
return -EPIPE;
|
||||||
|
|
||||||
|
min_size = urbs_rt->get_min_period_size(urbs_rt, sri->max);
|
||||||
|
if (!min_size)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (psi->min != min_size) {
|
||||||
|
psi->min = min_size;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb6fire_pcm_open(struct snd_pcm_substream *alsa_sub)
|
||||||
|
{
|
||||||
|
struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime;
|
||||||
|
struct sfire_chip *chip = snd_pcm_substream_chip(alsa_sub);
|
||||||
|
struct pcm_runtime *rt = chip->pcm;
|
||||||
|
struct substream_runtime *sub_rt = chip->substream;
|
||||||
|
struct urbs_runtime *urbs_rt = chip->urbs;
|
||||||
|
struct substream_descriptor desc;
|
||||||
|
|
||||||
|
if (chip->shutdown || !rt || !sub_rt || !urbs_rt)
|
||||||
|
return -EPIPE;
|
||||||
|
desc = sub_rt->get_descriptor(sub_rt, alsa_sub);
|
||||||
|
|
||||||
|
mutex_lock(&rt->lock);
|
||||||
|
alsa_rt->hw = pcm_hw;
|
||||||
|
alsa_rt->hw.channels_max = desc.max_channels;
|
||||||
|
alsa_rt->hw.rate_min = desc.rate_min;
|
||||||
|
alsa_rt->hw.rate_max = desc.rate_max;
|
||||||
|
alsa_rt->hw.rates = desc.rate_bits;
|
||||||
|
alsa_rt->hw.formats = desc.formats;
|
||||||
|
if (urbs_rt->rate) {
|
||||||
|
if (urbs_rt->rate < desc.rate_min || urbs_rt->rate > desc.rate_max)
|
||||||
|
return -EBUSY;
|
||||||
|
alsa_rt->hw.rate_min = urbs_rt->rate;
|
||||||
|
alsa_rt->hw.rate_max = urbs_rt->rate;
|
||||||
|
alsa_rt->hw.rates = snd_pcm_rate_to_rate_bit(urbs_rt->rate);
|
||||||
|
}
|
||||||
|
snd_pcm_hw_rule_add(alsa_sub->runtime, 0,
|
||||||
|
SNDRV_PCM_HW_PARAM_PERIOD_TIME,
|
||||||
|
usb6fire_pcm_rule_period_size, chip,
|
||||||
|
SNDRV_PCM_HW_PARAM_RATE, -1);
|
||||||
|
mutex_unlock(&rt->lock);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb6fire_pcm_close(struct snd_pcm_substream *alsa_sub)
|
||||||
|
{
|
||||||
|
struct sfire_chip *chip = snd_pcm_substream_chip(alsa_sub);
|
||||||
|
struct pcm_runtime *rt = chip->pcm;
|
||||||
|
struct substream_runtime *sub_rt = chip->substream;
|
||||||
|
struct urbs_runtime *urbs_rt = chip->urbs;
|
||||||
|
struct substream_descriptor desc;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (chip->shutdown || !rt || !sub_rt || !urbs_rt)
|
||||||
|
return 0;
|
||||||
|
desc = sub_rt->get_descriptor(sub_rt, alsa_sub);
|
||||||
|
|
||||||
|
mutex_lock(&rt->lock);
|
||||||
|
ret = urbs_rt->set_state(urbs_rt, &desc, URBS_SUBSTATE_DISABLED);
|
||||||
|
mutex_unlock(&rt->lock);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb6fire_pcm_prepare(struct snd_pcm_substream *alsa_sub)
|
||||||
|
{
|
||||||
|
struct sfire_chip *chip = snd_pcm_substream_chip(alsa_sub);
|
||||||
|
struct pcm_runtime *rt = chip->pcm;
|
||||||
|
struct substream_runtime *sub_rt = chip->substream;
|
||||||
|
struct urbs_runtime *urbs_rt = chip->urbs;
|
||||||
|
struct substream_descriptor desc;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (chip->shutdown || !rt || !sub_rt || !urbs_rt)
|
||||||
|
return -EPIPE;
|
||||||
|
desc = sub_rt->get_descriptor(sub_rt, alsa_sub);
|
||||||
|
|
||||||
|
mutex_lock(&rt->lock);
|
||||||
|
ret = urbs_rt->set_state(urbs_rt, &desc, URBS_SUBSTATE_ENABLED);
|
||||||
|
mutex_unlock(&rt->lock);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb6fire_pcm_trigger(struct snd_pcm_substream *alsa_sub, int cmd)
|
||||||
|
{
|
||||||
|
struct sfire_chip *chip = snd_pcm_substream_chip(alsa_sub);
|
||||||
|
struct pcm_runtime *rt = chip->pcm;
|
||||||
|
struct substream_runtime *sub_rt = chip->substream;
|
||||||
|
struct urbs_runtime *urbs_rt = chip->urbs;
|
||||||
|
struct substream_descriptor desc;
|
||||||
|
|
||||||
|
if (chip->shutdown || !rt || !sub_rt || !urbs_rt)
|
||||||
|
return -EPIPE;
|
||||||
|
desc = sub_rt->get_descriptor(sub_rt, alsa_sub);
|
||||||
|
|
||||||
|
switch (cmd) {
|
||||||
|
case SNDRV_PCM_TRIGGER_START:
|
||||||
|
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||||
|
return urbs_rt->set_active(urbs_rt, &desc, true);
|
||||||
|
|
||||||
|
case SNDRV_PCM_TRIGGER_STOP:
|
||||||
|
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||||
|
return urbs_rt->set_active(urbs_rt, &desc, false);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static snd_pcm_uframes_t usb6fire_pcm_pointer(
|
||||||
|
struct snd_pcm_substream *alsa_sub)
|
||||||
|
{
|
||||||
|
struct sfire_chip *chip = snd_pcm_substream_chip(alsa_sub);
|
||||||
|
struct pcm_runtime *rt = chip->pcm;
|
||||||
|
struct substream_runtime *sub_rt = chip->substream;
|
||||||
|
struct urbs_runtime *urbs_rt = chip->urbs;
|
||||||
|
struct substream_descriptor desc;
|
||||||
|
|
||||||
|
if (chip->shutdown || !rt || !sub_rt || !urbs_rt)
|
||||||
|
return -EPIPE;
|
||||||
|
desc = sub_rt->get_descriptor(sub_rt, alsa_sub);
|
||||||
|
return urbs_rt->get_pointer(urbs_rt, &desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb6fire_pcm_hw_params(struct snd_pcm_substream *alsa_sub,
|
||||||
|
struct snd_pcm_hw_params *hw_params)
|
||||||
|
{
|
||||||
|
return snd_pcm_lib_malloc_pages(alsa_sub,
|
||||||
|
params_buffer_bytes(hw_params));
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct snd_pcm_ops pcm_ops = {
|
||||||
|
.open = usb6fire_pcm_open,
|
||||||
|
.close = usb6fire_pcm_close,
|
||||||
|
.ioctl = snd_pcm_lib_ioctl,
|
||||||
|
.hw_params = usb6fire_pcm_hw_params,
|
||||||
|
.hw_free = snd_pcm_lib_free_pages,
|
||||||
|
.prepare = usb6fire_pcm_prepare,
|
||||||
|
.trigger = usb6fire_pcm_trigger,
|
||||||
|
.pointer = usb6fire_pcm_pointer,
|
||||||
|
};
|
||||||
|
|
||||||
|
int usb6fire_pcm_init(struct sfire_chip *chip)
|
||||||
|
{
|
||||||
|
struct pcm_runtime *rt = kzalloc(sizeof(struct pcm_runtime), GFP_KERNEL);
|
||||||
|
struct substream_runtime *sub_rt = chip->substream;
|
||||||
|
int ret;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (!rt)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
rt->chip = chip;
|
||||||
|
|
||||||
|
mutex_init(&rt->lock);
|
||||||
|
|
||||||
|
for (i = 0; i < sub_rt->n_devices; i++) {
|
||||||
|
sub_rt->devices[i]->private_data = chip;
|
||||||
|
snd_pcm_set_ops(sub_rt->devices[i], SNDRV_PCM_STREAM_PLAYBACK, &pcm_ops);
|
||||||
|
snd_pcm_set_ops(sub_rt->devices[i], SNDRV_PCM_STREAM_CAPTURE, &pcm_ops);
|
||||||
|
ret = snd_pcm_lib_preallocate_pages_for_all(sub_rt->devices[i],
|
||||||
|
SNDRV_DMA_TYPE_CONTINUOUS,
|
||||||
|
snd_dma_continuous_data(GFP_KERNEL),
|
||||||
|
MAX_BUFSIZE, MAX_BUFSIZE);
|
||||||
|
if (ret) {
|
||||||
|
kfree(rt);
|
||||||
|
snd_printk(KERN_ERR PREFIX
|
||||||
|
"error preallocating pcm buffers.\n");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
chip->pcm = rt;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void usb6fire_pcm_abort(struct sfire_chip *chip)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void usb6fire_pcm_destroy(struct sfire_chip *chip)
|
||||||
|
{
|
||||||
|
kfree(chip->pcm);
|
||||||
|
chip->pcm = NULL;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Linux driver for TerraTec DMX 6Fire USB
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef USB6FIRE_PCM_H
|
||||||
|
#define USB6FIRE_PCM_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <linux/mutex.h>
|
||||||
|
|
||||||
|
struct pcm_runtime {
|
||||||
|
struct sfire_chip *chip;
|
||||||
|
struct mutex lock;
|
||||||
|
};
|
||||||
|
|
||||||
|
int usb6fire_pcm_init(struct sfire_chip *chip);
|
||||||
|
void usb6fire_pcm_abort(struct sfire_chip *chip);
|
||||||
|
void usb6fire_pcm_destroy(struct sfire_chip *chip);
|
||||||
|
|
||||||
|
#endif /* USB6FIRE_PCM_H */
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
/*
|
||||||
|
* Linux driver for TerraTec DMX 6Fire USB
|
||||||
|
*
|
||||||
|
* Author: Torsten Schenk <torsten.schenk@zoho.com>
|
||||||
|
* Created: Jan 01, 2011
|
||||||
|
* Copyright: (C) Torsten Schenk
|
||||||
|
*
|
||||||
|
* Thanks to:
|
||||||
|
* - Holger Ruckdeschel: he found out how to control individual analog
|
||||||
|
* channel volumes and introduced mute switches
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef USB6FIRE_RATES_H
|
||||||
|
#define USB6FIRE_RATES_H
|
||||||
|
|
||||||
|
#include <sound/pcm.h>
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
/* Rate IDs used throughout driver */
|
||||||
|
enum {
|
||||||
|
RATE_44KHZ,
|
||||||
|
RATE_48KHZ,
|
||||||
|
RATE_88KHZ,
|
||||||
|
RATE_96KHZ,
|
||||||
|
RATE_176KHZ,
|
||||||
|
RATE_192KHZ,
|
||||||
|
N_RATES
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Digital Thru enabled and no pcm streaming? If so, set this rate */
|
||||||
|
enum {
|
||||||
|
RATE_DIGITAL_THRU_ONLY = RATE_96KHZ
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Supported rates in Hz */
|
||||||
|
static const unsigned int rates[] =
|
||||||
|
{ 44100, 48000, 88200, 96000, 176400, 192000, 0 };
|
||||||
|
/* Rates that allow digital audio */
|
||||||
|
static const bool rates_spdif_possible[] =
|
||||||
|
{ true, true, true, true, false, false, false };
|
||||||
|
static const bool rates_analog_possible[] =
|
||||||
|
{ true, true, true, true, true, true, false };
|
||||||
|
static const bool rates_valid[] =
|
||||||
|
{ true, true, true, true, true, true, false };
|
||||||
|
/* Isopacket size for outgoing urbs */
|
||||||
|
static const unsigned int rates_out_packet_size[] =
|
||||||
|
{ 228, 228, 420, 420, 604, 604 };
|
||||||
|
/* Isopacket size for incoming urbs */
|
||||||
|
static const unsigned int rates_in_packet_size[] =
|
||||||
|
{ 228, 228, 420, 420, 404, 404 };
|
||||||
|
/* Inferred from rates_x_packet_size: maximum number of frames per isopacket */
|
||||||
|
static const unsigned int rates_max_fpp[] =
|
||||||
|
{ 7, 7, 13, 13, 25, 25 };
|
||||||
|
/* Altsetting for the rates */
|
||||||
|
static const int rates_altsetting[] =
|
||||||
|
{ 1, 1, 2, 2, 3, 3 };
|
||||||
|
/* 6fire rate id low byte */
|
||||||
|
static const u8 rates_6fire_vl[] =
|
||||||
|
{ 0x00, 0x01, 0x00, 0x01, 0x00, 0x01 };
|
||||||
|
/* 6fire rate id high byte */
|
||||||
|
static const u8 rates_6fire_vh[] =
|
||||||
|
{ 0x11, 0x11, 0x10, 0x10, 0x00, 0x00 };
|
||||||
|
/* 6fire CS42426 functional mode */
|
||||||
|
static const u8 rates_fmode[] =
|
||||||
|
{ 0x00, 0x00, 0x50, 0x50, 0xa0, 0xa0 };
|
||||||
|
/* 6fire CS42426 clock control */
|
||||||
|
static const u8 rates_clock[] =
|
||||||
|
{ 0x02, 0x02, 0x02, 0x02, 0x22, 0x22 };
|
||||||
|
/* Alsa rate bit IDs */
|
||||||
|
static const unsigned int rates_alsa_id[] = {
|
||||||
|
SNDRV_PCM_RATE_44100,
|
||||||
|
SNDRV_PCM_RATE_48000,
|
||||||
|
SNDRV_PCM_RATE_88200,
|
||||||
|
SNDRV_PCM_RATE_96000,
|
||||||
|
SNDRV_PCM_RATE_176400,
|
||||||
|
SNDRV_PCM_RATE_192000
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline unsigned int rate_to_id(unsigned int rate)
|
||||||
|
{
|
||||||
|
unsigned int rate_id;
|
||||||
|
for (rate_id = 0; rate_id < N_RATES; rate_id++)
|
||||||
|
if (rates[rate_id] == rate)
|
||||||
|
break;
|
||||||
|
return rate_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* USB6FIRE_RATES_H */
|
||||||
|
|
|
@ -0,0 +1,651 @@
|
||||||
|
/*
|
||||||
|
* Linux driver for TerraTec DMX 6Fire USB
|
||||||
|
*
|
||||||
|
* Stream manager: specifies available streams in alsa
|
||||||
|
* and on the device
|
||||||
|
*
|
||||||
|
* 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 "substream.h"
|
||||||
|
#include "chip.h"
|
||||||
|
#include "rates.h"
|
||||||
|
|
||||||
|
const char parity_table[256] = {
|
||||||
|
0, 8, 8, 0, 8, 0, 0, 8, 8, 0, 0, 8, 0, 8, 8, 0,
|
||||||
|
8, 0, 0, 8, 0, 8, 8, 0, 0, 8, 8, 0, 8, 0, 0, 8,
|
||||||
|
8, 0, 0, 8, 0, 8, 8, 0, 0, 8, 8, 0, 8, 0, 0, 8,
|
||||||
|
0, 8, 8, 0, 8, 0, 0, 8, 8, 0, 0, 8, 0, 8, 8, 0,
|
||||||
|
8, 0, 0, 8, 0, 8, 8, 0, 0, 8, 8, 0, 8, 0, 0, 8,
|
||||||
|
0, 8, 8, 0, 8, 0, 0, 8, 8, 0, 0, 8, 0, 8, 8, 0,
|
||||||
|
0, 8, 8, 0, 8, 0, 0, 8, 8, 0, 0, 8, 0, 8, 8, 0,
|
||||||
|
8, 0, 0, 8, 0, 8, 8, 0, 0, 8, 8, 0, 8, 0, 0, 8,
|
||||||
|
8, 0, 0, 8, 0, 8, 8, 0, 0, 8, 8, 0, 8, 0, 0, 8,
|
||||||
|
0, 8, 8, 0, 8, 0, 0, 8, 8, 0, 0, 8, 0, 8, 8, 0,
|
||||||
|
0, 8, 8, 0, 8, 0, 0, 8, 8, 0, 0, 8, 0, 8, 8, 0,
|
||||||
|
8, 0, 0, 8, 0, 8, 8, 0, 0, 8, 8, 0, 8, 0, 0, 8,
|
||||||
|
0, 8, 8, 0, 8, 0, 0, 8, 8, 0, 0, 8, 0, 8, 8, 0,
|
||||||
|
8, 0, 0, 8, 0, 8, 8, 0, 0, 8, 8, 0, 8, 0, 0, 8,
|
||||||
|
8, 0, 0, 8, 0, 8, 8, 0, 0, 8, 8, 0, 8, 0, 0, 8,
|
||||||
|
0, 8, 8, 0, 8, 0, 0, 8, 8, 0, 0, 8, 0, 8, 8, 0
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
ANALOG_OUT_N_CHANNELS = 6,
|
||||||
|
ANALOG_IN_N_CHANNELS = 4,
|
||||||
|
SPDIF_OUT_N_CHANNELS = 2,
|
||||||
|
SPDIF_IN_N_CHANNELS = 2,
|
||||||
|
OUT_N_CHANNELS = ANALOG_OUT_N_CHANNELS + SPDIF_OUT_N_CHANNELS,
|
||||||
|
IN_N_CHANNELS = ANALOG_IN_N_CHANNELS
|
||||||
|
};
|
||||||
|
|
||||||
|
static const int spdif_channel_bits_bm[][10] = {
|
||||||
|
{2, 20, 32, 33, 35, -1}, //44.1 kHz
|
||||||
|
{2, 20, 25, 32, 33, 35, -1}, //48 kHz
|
||||||
|
{2, 20, 27, 32, 33, 35, -1}, //88.2 kHz
|
||||||
|
{2, 20, 25, 27, 32, 33, 35, -1} //96 kHz
|
||||||
|
};
|
||||||
|
|
||||||
|
static const int spdif_channel_bits_w[][10] = {
|
||||||
|
{2, 21, 32, 33, 35, -1}, //44.1 kHz
|
||||||
|
{2, 21, 25, 32, 33, 35, -1}, //48 kHz
|
||||||
|
{2, 21, 27, 32, 33, 35, -1}, //88.2 kHz
|
||||||
|
{2, 21, 25, 27, 32, 33, 35, -1} //96 kHz
|
||||||
|
};
|
||||||
|
|
||||||
|
static const u64 analog_formats =
|
||||||
|
SNDRV_PCM_FMTBIT_S16_LE |
|
||||||
|
SNDRV_PCM_FMTBIT_S24_LE |
|
||||||
|
SNDRV_PCM_FMTBIT_S32_LE;
|
||||||
|
static const u64 spdif_formats =
|
||||||
|
SNDRV_PCM_FMTBIT_S16_LE |
|
||||||
|
SNDRV_PCM_FMTBIT_S24_LE |
|
||||||
|
SNDRV_PCM_FMTBIT_S32_LE;
|
||||||
|
|
||||||
|
static void usb6fire_substream_a2d_s16le(struct substream_copier *copier, u8 *di, int frame_count)
|
||||||
|
{
|
||||||
|
u8 *si = copier->dma_ptr;
|
||||||
|
|
||||||
|
di += copier->urb_frame_offset;
|
||||||
|
while (frame_count--) {
|
||||||
|
di[0] = 0x00;
|
||||||
|
di[1] = si[0];
|
||||||
|
di[2] = si[1];
|
||||||
|
di[3] = 0x40;
|
||||||
|
si += copier->dma_frame_size;
|
||||||
|
if (si == copier->dma_end)
|
||||||
|
si = copier->dma_begin;
|
||||||
|
di += copier->urb_frame_size;
|
||||||
|
}
|
||||||
|
copier->dma_ptr = si;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usb6fire_substream_a2d_s24le(struct substream_copier *copier, u8 *di, int frame_count)
|
||||||
|
{
|
||||||
|
u8 *si = copier->dma_ptr;
|
||||||
|
|
||||||
|
di += copier->urb_frame_offset;
|
||||||
|
while (frame_count--) {
|
||||||
|
di[0] = si[0];
|
||||||
|
di[1] = si[1];
|
||||||
|
di[2] = si[2];
|
||||||
|
di[3] = 0x40;
|
||||||
|
si += copier->dma_frame_size;
|
||||||
|
if (si == copier->dma_end)
|
||||||
|
si = copier->dma_begin;
|
||||||
|
di += copier->urb_frame_size;
|
||||||
|
}
|
||||||
|
copier->dma_ptr = si;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usb6fire_substream_a2d_s32le(struct substream_copier *copier, u8 *di, int frame_count)
|
||||||
|
{
|
||||||
|
u8 *si = copier->dma_ptr;
|
||||||
|
|
||||||
|
di += copier->urb_frame_offset;
|
||||||
|
while (frame_count--) {
|
||||||
|
di[0] = si[1];
|
||||||
|
di[1] = si[2];
|
||||||
|
di[2] = si[3];
|
||||||
|
di[3] = 0x40;
|
||||||
|
si += copier->dma_frame_size;
|
||||||
|
if (si == copier->dma_end)
|
||||||
|
si = copier->dma_begin;
|
||||||
|
di += copier->urb_frame_size;
|
||||||
|
}
|
||||||
|
copier->dma_ptr = si;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usb6fire_substream_d2a_s16le(struct substream_copier *copier, u8 *si, int frame_count)
|
||||||
|
{
|
||||||
|
u8 *di = copier->dma_ptr;
|
||||||
|
|
||||||
|
si += copier->urb_frame_offset;
|
||||||
|
while (frame_count--) {
|
||||||
|
di[0] = si[1];
|
||||||
|
di[1] = si[2];
|
||||||
|
di += copier->dma_frame_size;
|
||||||
|
if (di == copier->dma_end)
|
||||||
|
di = copier->dma_begin;
|
||||||
|
si += copier->urb_frame_size;
|
||||||
|
}
|
||||||
|
copier->dma_ptr = di;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usb6fire_substream_d2a_s24le(struct substream_copier *copier, u8 *si, int frame_count)
|
||||||
|
{
|
||||||
|
u8 *di = copier->dma_ptr;
|
||||||
|
|
||||||
|
si += copier->urb_frame_offset;
|
||||||
|
while (frame_count--) {
|
||||||
|
di[0] = si[0];
|
||||||
|
di[1] = si[1];
|
||||||
|
di[2] = si[2];
|
||||||
|
di[3] = 0x00;
|
||||||
|
di += copier->dma_frame_size;
|
||||||
|
if (di == copier->dma_end)
|
||||||
|
di = copier->dma_begin;
|
||||||
|
si += copier->urb_frame_size;
|
||||||
|
}
|
||||||
|
copier->dma_ptr = di;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usb6fire_substream_d2a_s32le(struct substream_copier *copier, u8 *si, int frame_count)
|
||||||
|
{
|
||||||
|
u8 *di = copier->dma_ptr;
|
||||||
|
|
||||||
|
si += copier->urb_frame_offset;
|
||||||
|
while (frame_count--) {
|
||||||
|
di[0] = 0x00;
|
||||||
|
di[1] = si[0];
|
||||||
|
di[2] = si[1];
|
||||||
|
di[3] = si[2];
|
||||||
|
di += copier->dma_frame_size;
|
||||||
|
if (di == copier->dma_end)
|
||||||
|
di = copier->dma_begin;
|
||||||
|
si += copier->urb_frame_size;
|
||||||
|
}
|
||||||
|
copier->dma_ptr = di;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usb6fire_substream_s2d_s16le(struct substream_copier *copier, u8 *di, int frame_count)
|
||||||
|
{
|
||||||
|
u8 *si = copier->dma_ptr;
|
||||||
|
struct substream_spdif_data *spdif_data = copier->user;
|
||||||
|
|
||||||
|
di += copier->urb_frame_offset;
|
||||||
|
while (frame_count--) {
|
||||||
|
di[0] = 0x00;
|
||||||
|
di[1] = si[0];
|
||||||
|
di[2] = si[1];
|
||||||
|
di[3] = 0x00;
|
||||||
|
di[3] |= spdif_data->channel_bits[spdif_data->frame_index];
|
||||||
|
di[3] |= parity_table[di[0] ^ di[1] ^ di[2] ^ di[3]];
|
||||||
|
if (spdif_data->frame_index)
|
||||||
|
di[3] |= spdif_data->bmw[1];
|
||||||
|
else
|
||||||
|
di[3] |= spdif_data->bmw[0];
|
||||||
|
spdif_data->frame_index++;
|
||||||
|
spdif_data->frame_index %= 192;
|
||||||
|
|
||||||
|
si += copier->dma_frame_size;
|
||||||
|
if (si == copier->dma_end)
|
||||||
|
si = copier->dma_begin;
|
||||||
|
di += copier->urb_frame_size;
|
||||||
|
}
|
||||||
|
copier->dma_ptr = si;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usb6fire_substream_s2d_s24le(struct substream_copier *copier, u8 *di, int frame_count)
|
||||||
|
{
|
||||||
|
u8 *si = copier->dma_ptr;
|
||||||
|
struct substream_spdif_data *spdif_data = copier->user;
|
||||||
|
|
||||||
|
di += copier->urb_frame_offset;
|
||||||
|
while (frame_count--) {
|
||||||
|
di[0] = si[0];
|
||||||
|
di[1] = si[1];
|
||||||
|
di[2] = si[2];
|
||||||
|
di[3] = 0x00;
|
||||||
|
di[3] |= spdif_data->channel_bits[spdif_data->frame_index];
|
||||||
|
di[3] |= parity_table[di[0] ^ di[1] ^ di[2] ^ di[3]];
|
||||||
|
if (likely(spdif_data->frame_index))
|
||||||
|
di[3] |= spdif_data->bmw[1];
|
||||||
|
else
|
||||||
|
di[3] |= spdif_data->bmw[0];
|
||||||
|
spdif_data->frame_index++;
|
||||||
|
spdif_data->frame_index %= 192;
|
||||||
|
|
||||||
|
si += copier->dma_frame_size;
|
||||||
|
if (si == copier->dma_end)
|
||||||
|
si = copier->dma_begin;
|
||||||
|
di += copier->urb_frame_size;
|
||||||
|
}
|
||||||
|
copier->dma_ptr = si;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usb6fire_substream_s2d_s32le(struct substream_copier *copier, u8 *di, int frame_count)
|
||||||
|
{
|
||||||
|
u8 *si = copier->dma_ptr;
|
||||||
|
struct substream_spdif_data *spdif_data = copier->user;
|
||||||
|
|
||||||
|
di += copier->urb_frame_offset;
|
||||||
|
while (frame_count--) {
|
||||||
|
di[0] = si[1];
|
||||||
|
di[1] = si[2];
|
||||||
|
di[2] = si[3];
|
||||||
|
di[3] = 0x00;
|
||||||
|
di[3] |= spdif_data->channel_bits[spdif_data->frame_index];
|
||||||
|
di[3] |= parity_table[di[0] ^ di[1] ^ di[2] ^ di[3]];
|
||||||
|
if (likely(spdif_data->frame_index))
|
||||||
|
di[3] |= spdif_data->bmw[1];
|
||||||
|
else
|
||||||
|
di[3] |= spdif_data->bmw[0];
|
||||||
|
spdif_data->frame_index++;
|
||||||
|
spdif_data->frame_index %= 192;
|
||||||
|
|
||||||
|
si += copier->dma_frame_size;
|
||||||
|
if (si == copier->dma_end)
|
||||||
|
si = copier->dma_begin;
|
||||||
|
di += copier->urb_frame_size;
|
||||||
|
}
|
||||||
|
copier->dma_ptr = si;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usb6fire_substream_ad_mute(struct substream_muter *muter, u8 *di, int frame_count)
|
||||||
|
{
|
||||||
|
di += muter->urb_frame_offset;
|
||||||
|
while (frame_count--) {
|
||||||
|
di[0] = 0x00;
|
||||||
|
di[1] = 0x00;
|
||||||
|
di[2] = 0x00;
|
||||||
|
di[3] = 0x40;
|
||||||
|
di += muter->urb_frame_size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usb6fire_substream_a_mute(struct substream_muter *muter, u8 *si, int frame_count)
|
||||||
|
{}
|
||||||
|
|
||||||
|
static void usb6fire_substream_sd_mute(struct substream_muter *muter, u8 *di, int frame_count)
|
||||||
|
{
|
||||||
|
di += muter->urb_frame_offset;
|
||||||
|
while (frame_count--) {
|
||||||
|
di[0] = 0xff;
|
||||||
|
di[1] = 0xff;
|
||||||
|
di[2] = 0xff;
|
||||||
|
di[3] = 0xff;
|
||||||
|
di += muter->urb_frame_size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usb6fire_substream_ad_reset(struct substream_resetter *resetter, unsigned int sample_rate)
|
||||||
|
{}
|
||||||
|
|
||||||
|
static void usb6fire_substream_a_reset(struct substream_resetter *resetter, unsigned int sample_rate)
|
||||||
|
{}
|
||||||
|
|
||||||
|
static void usb6fire_substream_sd_reset(struct substream_resetter *resetter, unsigned int sample_rate)
|
||||||
|
{
|
||||||
|
struct substream_spdif_data *spdif_data = resetter->user;
|
||||||
|
int rate_id;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
|
||||||
|
spdif_data[0].bmw[0] = 0x30;
|
||||||
|
spdif_data[0].bmw[1] = 0x10;
|
||||||
|
spdif_data[1].bmw[0] = 0x00;
|
||||||
|
spdif_data[1].bmw[1] = 0x00;
|
||||||
|
spdif_data[0].frame_index = 0;
|
||||||
|
spdif_data[1].frame_index = 0;
|
||||||
|
memset(spdif_data[0].channel_bits, 0, 192);
|
||||||
|
memset(spdif_data[1].channel_bits, 0, 192);
|
||||||
|
rate_id = rate_to_id(sample_rate);
|
||||||
|
if (rates_spdif_possible[rate_id]) {
|
||||||
|
for(i = 0; spdif_channel_bits_bm[rate_id][i] != -1; i++)
|
||||||
|
spdif_data[0].channel_bits[spdif_channel_bits_bm[rate_id][i]] = 0x04;
|
||||||
|
for(i = 0; spdif_channel_bits_w[rate_id][i] != -1; i++)
|
||||||
|
spdif_data[1].channel_bits[spdif_channel_bits_w[rate_id][i]] = 0x04;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct substream_copier usb6fire_substream_get_a2d_copier(struct substream_runtime *rt, struct substream_descriptor *desc, int channel)
|
||||||
|
{
|
||||||
|
struct substream_copier copier;
|
||||||
|
struct snd_pcm_runtime *alsa_rt = desc->alsa_sub->runtime;
|
||||||
|
|
||||||
|
copier.dma_begin = desc->alsa_sub->runtime->dma_area;
|
||||||
|
copier.dma_end = copier.dma_begin + desc->alsa_sub->runtime->dma_bytes;
|
||||||
|
copier.dma_begin += channel * desc->alsa_sub->runtime->sample_bits / 8;
|
||||||
|
copier.dma_end += channel * desc->alsa_sub->runtime->sample_bits / 8;
|
||||||
|
copier.dma_ptr = copier.dma_begin;
|
||||||
|
|
||||||
|
copier.user = NULL;
|
||||||
|
copier.urb_frame_size = rt->out_urb_frame_size;
|
||||||
|
copier.urb_frame_offset = channel * 4;
|
||||||
|
copier.dma_frame_size = desc->alsa_sub->runtime->frame_bits / 8;
|
||||||
|
|
||||||
|
if (alsa_rt->format == SNDRV_PCM_FORMAT_S16_LE)
|
||||||
|
copier.exec = usb6fire_substream_a2d_s16le;
|
||||||
|
else if (alsa_rt->format == SNDRV_PCM_FORMAT_S24_LE)
|
||||||
|
copier.exec = usb6fire_substream_a2d_s24le;
|
||||||
|
else if (alsa_rt->format == SNDRV_PCM_FORMAT_S32_LE)
|
||||||
|
copier.exec = usb6fire_substream_a2d_s32le;
|
||||||
|
else
|
||||||
|
copier.exec = NULL;
|
||||||
|
return copier;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct substream_copier usb6fire_substream_get_d2a_copier(struct substream_runtime *rt, struct substream_descriptor *desc, int channel)
|
||||||
|
{
|
||||||
|
struct substream_copier copier;
|
||||||
|
struct snd_pcm_runtime *alsa_rt = desc->alsa_sub->runtime;
|
||||||
|
|
||||||
|
copier.dma_begin = desc->alsa_sub->runtime->dma_area;
|
||||||
|
copier.dma_end = copier.dma_begin + desc->alsa_sub->runtime->dma_bytes;
|
||||||
|
copier.dma_begin += channel * desc->alsa_sub->runtime->sample_bits / 8;
|
||||||
|
copier.dma_end += channel * desc->alsa_sub->runtime->sample_bits / 8;
|
||||||
|
copier.dma_ptr = copier.dma_begin;
|
||||||
|
|
||||||
|
copier.user = NULL;
|
||||||
|
copier.urb_frame_size = rt->in_urb_frame_size;
|
||||||
|
copier.urb_frame_offset = channel * 4;
|
||||||
|
copier.dma_frame_size = desc->alsa_sub->runtime->frame_bits / 8;
|
||||||
|
|
||||||
|
if (alsa_rt->format == SNDRV_PCM_FORMAT_S16_LE)
|
||||||
|
copier.exec = usb6fire_substream_d2a_s16le;
|
||||||
|
else if (alsa_rt->format == SNDRV_PCM_FORMAT_S24_LE)
|
||||||
|
copier.exec = usb6fire_substream_d2a_s24le;
|
||||||
|
else if (alsa_rt->format == SNDRV_PCM_FORMAT_S32_LE)
|
||||||
|
copier.exec = usb6fire_substream_d2a_s32le;
|
||||||
|
else
|
||||||
|
copier.exec = NULL;
|
||||||
|
return copier;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct substream_copier usb6fire_substream_get_s2d_copier(struct substream_runtime *rt, struct substream_descriptor *desc, int channel)
|
||||||
|
{
|
||||||
|
struct substream_copier copier;
|
||||||
|
struct snd_pcm_runtime *alsa_rt = desc->alsa_sub->runtime;
|
||||||
|
|
||||||
|
copier.dma_begin = desc->alsa_sub->runtime->dma_area;
|
||||||
|
copier.dma_end = copier.dma_begin + desc->alsa_sub->runtime->dma_bytes;
|
||||||
|
copier.dma_begin += channel * desc->alsa_sub->runtime->sample_bits / 8;
|
||||||
|
copier.dma_end += channel * desc->alsa_sub->runtime->sample_bits / 8;
|
||||||
|
copier.dma_ptr = copier.dma_begin;
|
||||||
|
|
||||||
|
copier.user = rt->spdif_data + channel;
|
||||||
|
copier.urb_frame_size = rt->out_urb_frame_size;
|
||||||
|
copier.urb_frame_offset = channel * 4;
|
||||||
|
if (PCM_MODE_USES_ANALOG(rt->chip->pcm_mode))
|
||||||
|
copier.urb_frame_offset += ANALOG_OUT_N_CHANNELS * 4;
|
||||||
|
copier.dma_frame_size = desc->alsa_sub->runtime->frame_bits / 8;
|
||||||
|
|
||||||
|
if (alsa_rt->format == SNDRV_PCM_FORMAT_S16_LE)
|
||||||
|
copier.exec = usb6fire_substream_s2d_s16le;
|
||||||
|
else if (alsa_rt->format == SNDRV_PCM_FORMAT_S24_LE)
|
||||||
|
copier.exec = usb6fire_substream_s2d_s24le;
|
||||||
|
else if (alsa_rt->format == SNDRV_PCM_FORMAT_S32_LE)
|
||||||
|
copier.exec = usb6fire_substream_s2d_s32le;
|
||||||
|
else
|
||||||
|
copier.exec = NULL;
|
||||||
|
return copier;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct substream_muter usb6fire_substream_get_ad_muter(struct substream_runtime *rt, int channel)
|
||||||
|
{
|
||||||
|
struct substream_muter muter;
|
||||||
|
|
||||||
|
muter.exec = usb6fire_substream_ad_mute;
|
||||||
|
muter.user = NULL;
|
||||||
|
muter.urb_frame_size = rt->out_urb_frame_size;
|
||||||
|
muter.urb_frame_offset = channel * 4;
|
||||||
|
return muter;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct substream_muter usb6fire_substream_get_a_muter(struct substream_runtime *rt, int channel)
|
||||||
|
{
|
||||||
|
struct substream_muter muter;
|
||||||
|
|
||||||
|
muter.exec = usb6fire_substream_a_mute;
|
||||||
|
muter.user = NULL;
|
||||||
|
muter.urb_frame_size = rt->in_urb_frame_size;
|
||||||
|
muter.urb_frame_offset = channel * 4;
|
||||||
|
return muter;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct substream_muter usb6fire_substream_get_sd_muter(struct substream_runtime *rt, int channel)
|
||||||
|
{
|
||||||
|
struct substream_muter muter;
|
||||||
|
|
||||||
|
muter.exec = usb6fire_substream_sd_mute;
|
||||||
|
muter.user = rt->spdif_data + channel;
|
||||||
|
muter.urb_frame_size = rt->out_urb_frame_size;
|
||||||
|
muter.urb_frame_offset = channel * 4;
|
||||||
|
if (PCM_MODE_USES_ANALOG(rt->chip->pcm_mode))
|
||||||
|
muter.urb_frame_offset += ANALOG_OUT_N_CHANNELS * 4;
|
||||||
|
return muter;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct substream_resetter usb6fire_substream_get_ad_resetter(struct substream_runtime *rt)
|
||||||
|
{
|
||||||
|
struct substream_resetter resetter;
|
||||||
|
|
||||||
|
resetter.user = NULL;
|
||||||
|
resetter.exec = usb6fire_substream_ad_reset;
|
||||||
|
return resetter;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct substream_resetter usb6fire_substream_get_a_resetter(struct substream_runtime *rt)
|
||||||
|
{
|
||||||
|
struct substream_resetter resetter;
|
||||||
|
|
||||||
|
resetter.user = NULL;
|
||||||
|
resetter.exec = usb6fire_substream_a_reset;
|
||||||
|
return resetter;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct substream_resetter usb6fire_substream_get_sd_resetter(struct substream_runtime *rt)
|
||||||
|
{
|
||||||
|
struct substream_resetter resetter;
|
||||||
|
|
||||||
|
resetter.user = rt->spdif_data;
|
||||||
|
resetter.exec = usb6fire_substream_sd_reset;
|
||||||
|
return resetter;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct substream_descriptor usb6fire_substream_get_descriptor(
|
||||||
|
struct substream_runtime *rt, struct snd_pcm_substream *alsa_sub)
|
||||||
|
{
|
||||||
|
struct substream_descriptor desc;
|
||||||
|
int cur_sub = 0;
|
||||||
|
int cur_dev = 0;
|
||||||
|
unsigned int rate_id;
|
||||||
|
|
||||||
|
desc.alsa_sub = alsa_sub;
|
||||||
|
|
||||||
|
desc.input = false;
|
||||||
|
desc.index = -EINVAL;
|
||||||
|
desc.max_channels = 0;
|
||||||
|
desc.rate_min = 0;
|
||||||
|
desc.rate_max = 0;
|
||||||
|
desc.rate_bits = 0;
|
||||||
|
|
||||||
|
for (rate_id = 0; rate_id < N_RATES; rate_id++)
|
||||||
|
if (rates_analog_possible[rate_id] && rates_spdif_possible[rate_id]) {
|
||||||
|
desc.rate_bits |= rates_alsa_id[rate_id];
|
||||||
|
if (desc.rate_min > rates[rate_id] || !desc.rate_min)
|
||||||
|
desc.rate_min = rates[rate_id];
|
||||||
|
if (desc.rate_max < rates[rate_id] || !desc.rate_max)
|
||||||
|
desc.rate_max = rates[rate_id];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PCM_MODE_USES_ANALOG(rt->chip->pcm_mode)) {
|
||||||
|
if (alsa_sub->pcm->device == cur_dev++) {
|
||||||
|
if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||||
|
desc.input = false;
|
||||||
|
desc.index = cur_sub;
|
||||||
|
desc.max_channels = ANALOG_OUT_N_CHANNELS;
|
||||||
|
}
|
||||||
|
if (alsa_sub->stream == SNDRV_PCM_STREAM_CAPTURE) {
|
||||||
|
desc.input = true;
|
||||||
|
desc.index = cur_sub + 1;
|
||||||
|
desc.max_channels = ANALOG_IN_N_CHANNELS;
|
||||||
|
}
|
||||||
|
if (PCM_MODE_ALLOWS_ANALOG_ONLY_RATES(rt->chip->pcm_mode))
|
||||||
|
for (rate_id = 0; rate_id < N_RATES; rate_id++)
|
||||||
|
if (rates_analog_possible[rate_id] && !rates_spdif_possible[rate_id]) {
|
||||||
|
desc.rate_bits |= rates_alsa_id[rate_id];
|
||||||
|
if (desc.rate_min > rates[rate_id] || !desc.rate_min)
|
||||||
|
desc.rate_min = rates[rate_id];
|
||||||
|
if (desc.rate_max < rates[rate_id] || !desc.rate_max)
|
||||||
|
desc.rate_max = rates[rate_id];
|
||||||
|
}
|
||||||
|
desc.formats = analog_formats;
|
||||||
|
}
|
||||||
|
cur_sub += 2;
|
||||||
|
}
|
||||||
|
if (PCM_MODE_USES_SPDIF(rt->chip->pcm_mode)) {
|
||||||
|
if (alsa_sub->pcm->device == cur_dev++) {
|
||||||
|
if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||||
|
desc.input = false;
|
||||||
|
desc.index = cur_sub;
|
||||||
|
desc.max_channels = SPDIF_OUT_N_CHANNELS;
|
||||||
|
}
|
||||||
|
if (PCM_MODE_ALLOWS_SPDIF_ONLY_RATES(rt->chip->pcm_mode))
|
||||||
|
for (rate_id = 0; rate_id < N_RATES; rate_id++)
|
||||||
|
if (rates_spdif_possible[rate_id] && !rates_analog_possible[rate_id]) {
|
||||||
|
desc.rate_bits |= rates_alsa_id[rate_id];
|
||||||
|
if (desc.rate_min > rates[rate_id] || !desc.rate_min)
|
||||||
|
desc.rate_min = rates[rate_id];
|
||||||
|
if (desc.rate_max < rates[rate_id] || !desc.rate_max)
|
||||||
|
desc.rate_max = rates[rate_id];
|
||||||
|
}
|
||||||
|
desc.formats = spdif_formats;
|
||||||
|
}
|
||||||
|
cur_sub++;
|
||||||
|
}
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct substream_definition usb6fire_substream_get_definition(struct substream_runtime *rt, int substream)
|
||||||
|
{
|
||||||
|
struct substream_definition def;
|
||||||
|
int cur = 0;
|
||||||
|
int out_urb_frame_size = 0;
|
||||||
|
int in_urb_frame_size = 0;
|
||||||
|
|
||||||
|
def.in = false;
|
||||||
|
def.max_channels = 0;
|
||||||
|
def.sample_size = 0;
|
||||||
|
def.urb_frame_offset = 0;
|
||||||
|
def.get_copier = NULL;
|
||||||
|
def.get_muter = NULL;
|
||||||
|
def.get_resetter = NULL;
|
||||||
|
|
||||||
|
if (PCM_MODE_USES_ANALOG(rt->chip->pcm_mode)) {
|
||||||
|
if (substream == cur++) {
|
||||||
|
def.urb_frame_offset = out_urb_frame_size;
|
||||||
|
def.max_channels = ANALOG_OUT_N_CHANNELS;
|
||||||
|
def.sample_size = 4;
|
||||||
|
def.in = false;
|
||||||
|
def.get_copier = usb6fire_substream_get_a2d_copier;
|
||||||
|
def.get_muter = usb6fire_substream_get_ad_muter;
|
||||||
|
def.get_resetter = usb6fire_substream_get_ad_resetter;
|
||||||
|
} else if (substream == cur++) {
|
||||||
|
def.urb_frame_offset = in_urb_frame_size;
|
||||||
|
def.max_channels = ANALOG_IN_N_CHANNELS;
|
||||||
|
def.sample_size = 4;
|
||||||
|
def.in = true;
|
||||||
|
def.get_copier = usb6fire_substream_get_d2a_copier;
|
||||||
|
def.get_muter = usb6fire_substream_get_a_muter;
|
||||||
|
def.get_resetter = usb6fire_substream_get_a_resetter;
|
||||||
|
}
|
||||||
|
out_urb_frame_size += ANALOG_OUT_N_CHANNELS * 4;
|
||||||
|
in_urb_frame_size += ANALOG_IN_N_CHANNELS * 4;
|
||||||
|
}
|
||||||
|
if(PCM_MODE_USES_SPDIF(rt->chip->pcm_mode)) {
|
||||||
|
if (substream == cur++) {
|
||||||
|
def.urb_frame_offset = out_urb_frame_size;
|
||||||
|
def.max_channels = SPDIF_OUT_N_CHANNELS;
|
||||||
|
def.sample_size = 4;
|
||||||
|
def.in = false;
|
||||||
|
def.get_copier = usb6fire_substream_get_s2d_copier;
|
||||||
|
def.get_muter = usb6fire_substream_get_sd_muter;
|
||||||
|
def.get_resetter = usb6fire_substream_get_sd_resetter;
|
||||||
|
}
|
||||||
|
out_urb_frame_size += SPDIF_OUT_N_CHANNELS * 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
|
||||||
|
int usb6fire_substream_init(struct sfire_chip *chip)
|
||||||
|
{
|
||||||
|
struct substream_runtime *rt =
|
||||||
|
kzalloc(sizeof(struct substream_runtime), GFP_KERNEL);
|
||||||
|
int ret;
|
||||||
|
struct snd_pcm *device;
|
||||||
|
|
||||||
|
if(!rt)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
rt->chip = chip;
|
||||||
|
rt->get_descriptor = usb6fire_substream_get_descriptor;
|
||||||
|
rt->get_definition = usb6fire_substream_get_definition;
|
||||||
|
|
||||||
|
if (PCM_MODE_USES_ANALOG(rt->chip->pcm_mode)) {
|
||||||
|
ret = snd_pcm_new(chip->card, "6fire Analog", rt->n_devices,
|
||||||
|
1, 1, &device);
|
||||||
|
if (ret < 0) {
|
||||||
|
kfree(rt);
|
||||||
|
snd_printk(KERN_ERR PREFIX "cannot create pcm instance.\n");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
strcpy(device->name, "DMX 6Fire USB Analog");
|
||||||
|
rt->devices[rt->n_devices] = device;
|
||||||
|
rt->n_devices++;
|
||||||
|
rt->n_substreams += 2;
|
||||||
|
rt->out_urb_frame_size += ANALOG_OUT_N_CHANNELS * 4;
|
||||||
|
rt->in_urb_frame_size += ANALOG_IN_N_CHANNELS * 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PCM_MODE_USES_SPDIF(rt->chip->pcm_mode)) {
|
||||||
|
ret = snd_pcm_new(chip->card, "6fire Digital Out", rt->n_devices,
|
||||||
|
1, 0, &device);
|
||||||
|
if (ret < 0) {
|
||||||
|
kfree(rt);
|
||||||
|
snd_printk(KERN_ERR PREFIX "cannot create pcm instance.\n");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
strcpy(device->name, "DMX 6Fire USB Digital Playback");
|
||||||
|
rt->devices[rt->n_devices] = device;
|
||||||
|
rt->spdif_data = kzalloc(SPDIF_OUT_N_CHANNELS
|
||||||
|
* sizeof(struct substream_spdif_data), GFP_KERNEL);
|
||||||
|
rt->n_devices++;
|
||||||
|
rt->n_substreams++;
|
||||||
|
rt->out_urb_frame_size += SPDIF_OUT_N_CHANNELS * 4;
|
||||||
|
if (!PCM_MODE_USES_ANALOG(rt->chip->pcm_mode)) /* we need at least some input data to calculate number of frames for output per isopacket */
|
||||||
|
rt->in_urb_frame_size += SPDIF_IN_N_CHANNELS * 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
chip->substream = rt;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void usb6fire_substream_abort(struct sfire_chip *chip)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void usb6fire_substream_destroy(struct sfire_chip *chip)
|
||||||
|
{
|
||||||
|
kfree(chip->substream);
|
||||||
|
chip->substream = NULL;
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
/*
|
||||||
|
* Linux driver for TerraTec DMX 6Fire USB
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
#ifndef USB6FIRE_SUBSTREAM_H
|
||||||
|
#define USB6FIRE_SUBSTREAM_H
|
||||||
|
|
||||||
|
#include <sound/pcm.h>
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
enum {
|
||||||
|
SUBSTREAM_MAX_COUNT = 3,
|
||||||
|
SUBSTREAM_MAX_IN_SAMPLES = 4,
|
||||||
|
SUBSTREAM_MAX_OUT_SAMPLES = 6,
|
||||||
|
SUBSTREAM_DEVICES_MAX = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
struct substream_spdif_data {
|
||||||
|
u8 channel_bits[192];
|
||||||
|
int frame_index;
|
||||||
|
u8 bmw[2];
|
||||||
|
/* u8 subcode_bits[1176];
|
||||||
|
int subcode_index;*/
|
||||||
|
};
|
||||||
|
|
||||||
|
/* copies samples device<->urb */
|
||||||
|
struct substream_copier {
|
||||||
|
u8 *dma_begin;
|
||||||
|
u8 *dma_end;
|
||||||
|
u8 *dma_ptr;
|
||||||
|
int dma_frame_size;
|
||||||
|
int urb_frame_size;
|
||||||
|
int urb_frame_offset;
|
||||||
|
|
||||||
|
void *user;
|
||||||
|
|
||||||
|
void (*exec)(struct substream_copier *copier, u8 *buffer, int frame_count);
|
||||||
|
};
|
||||||
|
|
||||||
|
/* mutes samples in an urb */
|
||||||
|
struct substream_muter {
|
||||||
|
void *user;
|
||||||
|
int urb_frame_size;
|
||||||
|
int urb_frame_offset;
|
||||||
|
|
||||||
|
void (*exec)(struct substream_muter *muter, u8 *buffer, int frame_count);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct substream_resetter {
|
||||||
|
void *user;
|
||||||
|
|
||||||
|
void (*exec)(struct substream_resetter *resetter, unsigned int sample_rate);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct substream_descriptor {
|
||||||
|
struct snd_pcm_substream *alsa_sub;
|
||||||
|
int index;
|
||||||
|
bool input;
|
||||||
|
int max_channels;
|
||||||
|
unsigned int rate_bits;
|
||||||
|
unsigned int rate_min;
|
||||||
|
unsigned int rate_max;
|
||||||
|
u64 formats;
|
||||||
|
/* bool analog;*/
|
||||||
|
};
|
||||||
|
|
||||||
|
struct substream_definition {
|
||||||
|
bool in;
|
||||||
|
int max_channels;
|
||||||
|
int sample_size;
|
||||||
|
int urb_frame_offset;
|
||||||
|
|
||||||
|
struct substream_copier (*get_copier)(struct substream_runtime *rt, struct substream_descriptor *descriptor, int channel);
|
||||||
|
struct substream_muter (*get_muter)(struct substream_runtime *rt, int channel);
|
||||||
|
struct substream_resetter (*get_resetter)(struct substream_runtime *rt);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct substream_runtime {
|
||||||
|
struct sfire_chip *chip;
|
||||||
|
|
||||||
|
struct snd_pcm *devices[SUBSTREAM_DEVICES_MAX];
|
||||||
|
int n_devices;
|
||||||
|
int n_substreams;
|
||||||
|
int out_urb_frame_size;
|
||||||
|
int in_urb_frame_size;
|
||||||
|
|
||||||
|
struct substream_descriptor (*get_descriptor)(struct substream_runtime *rt, struct snd_pcm_substream *alsa_sub);
|
||||||
|
struct substream_definition (*get_definition)(struct substream_runtime *rt, int substream);
|
||||||
|
struct substream_spdif_data *spdif_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
int usb6fire_substream_init(struct sfire_chip *chip);
|
||||||
|
void usb6fire_substream_abort(struct sfire_chip *chip);
|
||||||
|
void usb6fire_substream_destroy(struct sfire_chip *chip);
|
||||||
|
#endif /* USB6FIRE_SUBSTREAM_H */
|
||||||
|
|
|
@ -0,0 +1,647 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
/*
|
||||||
|
* Linux driver for TerraTec DMX 6Fire USB
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef USB6FIRE_URBS_H
|
||||||
|
#define USB6FIRE_URBS_H
|
||||||
|
|
||||||
|
#include <linux/atomic.h>
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "substream.h"
|
||||||
|
|
||||||
|
enum {
|
||||||
|
URBS_COUNT = 4
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
URBS_SUBSTATE_DISABLED,
|
||||||
|
URBS_SUBSTATE_SUSPENDED,
|
||||||
|
URBS_SUBSTATE_ENABLED
|
||||||
|
};
|
||||||
|
|
||||||
|
struct urbs_substream {
|
||||||
|
struct snd_pcm_substream *alsa_sub;
|
||||||
|
spinlock_t lock;
|
||||||
|
int state;
|
||||||
|
bool active;
|
||||||
|
bool trigger_active;
|
||||||
|
|
||||||
|
snd_pcm_uframes_t dma_off;
|
||||||
|
snd_pcm_uframes_t dma_size;
|
||||||
|
snd_pcm_uframes_t period_off;
|
||||||
|
snd_pcm_uframes_t period_size;
|
||||||
|
atomic_t dma_off_public;
|
||||||
|
bool in;
|
||||||
|
int max_channels;
|
||||||
|
int channels;
|
||||||
|
struct substream_copier *copier; /* size: max_channels */
|
||||||
|
struct substream_muter *muter; /* size: max_channels */
|
||||||
|
struct substream_resetter resetter;
|
||||||
|
struct substream_copier (*get_copier)(struct substream_runtime *rt, struct substream_descriptor *descriptor, int channel);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct urbs_queue {
|
||||||
|
struct urbs_runtime *urbs;
|
||||||
|
|
||||||
|
atomic_t head;
|
||||||
|
atomic_t tail;
|
||||||
|
struct urb *node[URBS_COUNT + 1]; /* +1: full/empty distinction */
|
||||||
|
};
|
||||||
|
|
||||||
|
struct urbs_runtime {
|
||||||
|
struct sfire_chip *chip;
|
||||||
|
|
||||||
|
struct tasklet_struct tasklet;
|
||||||
|
bool tasklet_enabled;
|
||||||
|
bool use_tasklet;
|
||||||
|
|
||||||
|
struct urb *in_urbs[URBS_COUNT];
|
||||||
|
struct urb *out_urbs[URBS_COUNT];
|
||||||
|
struct urbs_queue in_queue;
|
||||||
|
struct urbs_queue out_queue;
|
||||||
|
struct urbs_substream substreams[SUBSTREAM_MAX_COUNT];
|
||||||
|
int n_substreams;
|
||||||
|
|
||||||
|
atomic_t state;
|
||||||
|
unsigned int rate;
|
||||||
|
unsigned int packets_per_urb;
|
||||||
|
|
||||||
|
int (*set_state)(struct urbs_runtime *rt, struct substream_descriptor *desc, int state); /* not thread safe lock externally */
|
||||||
|
int (*get_min_period_size)(struct urbs_runtime *rt, unsigned int rate);
|
||||||
|
int (*set_active)(struct urbs_runtime *rt, struct substream_descriptor *desc, bool active); /* thread safe */
|
||||||
|
snd_pcm_uframes_t (*get_pointer)(struct urbs_runtime *rt, struct substream_descriptor *desc); /* thread safe */
|
||||||
|
|
||||||
|
int in_frame_size;
|
||||||
|
int out_frame_size;
|
||||||
|
wait_queue_head_t stream_wait_queue;
|
||||||
|
};
|
||||||
|
|
||||||
|
int usb6fire_urbs_init(struct sfire_chip *chip);
|
||||||
|
void usb6fire_urbs_abort(struct sfire_chip *chip);
|
||||||
|
void usb6fire_urbs_destroy(struct sfire_chip *chip);
|
||||||
|
|
||||||
|
#endif /* USB6FIRE_URBS_H */
|
||||||
|
|
Loading…
Reference in New Issue