Initial commit

This commit is contained in:
crt0mega 2021-08-24 12:56:33 +02:00
commit 3cd3904bf8
Signed by: crt0mega
GPG Key ID: DCAA09100B14F420
38 changed files with 4671 additions and 0 deletions

12
.gitignore vendored Normal file
View File

@ -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

6
debian/README.Debian vendored Normal file
View File

@ -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

9
debian/README.source vendored Normal file
View File

@ -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

5
debian/changelog vendored Normal file
View File

@ -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

27
debian/control vendored Normal file
View File

@ -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

39
debian/copyright vendored Normal file
View File

@ -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/

8
debian/dkms vendored Normal file
View File

@ -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"

14
debian/patches/chip.patch vendored Normal file
View File

@ -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 */

12
debian/patches/control.patch vendored Normal file
View File

@ -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++;

29
debian/patches/pcm.patch vendored Normal file
View File

@ -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;

3
debian/patches/series vendored Normal file
View File

@ -0,0 +1,3 @@
pcm.patch
chip.patch
control.patch

32
debian/rules vendored Executable file
View File

@ -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)

2
debian/sixfireusb-dkms-docs.docs vendored Normal file
View File

@ -0,0 +1,2 @@
README.Debian
README.source

1
debian/source/format vendored Normal file
View File

@ -0,0 +1 @@
3.0 (quilt)

1
sixfireusb.conf Normal file
View File

@ -0,0 +1 @@
blacklist snd_usb_6fire

127
src/CHANGES Normal file
View File

@ -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

281
src/GPL Normal file
View File

@ -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

41
src/INSTALL Normal file
View File

@ -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.

18
src/Makefile Normal file
View File

@ -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

95
src/README Normal file
View File

@ -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.

280
src/chip.c Normal file
View File

@ -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(&register_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(&register_mutex);
return 0;
} else if (regidx < 0)
regidx = i;
}
if (regidx < 0) {
mutex_unlock(&register_mutex);
snd_printk(KERN_ERR PREFIX "too many cards registered.\n");
return -ENODEV;
}
devices[regidx] = device;
mutex_unlock(&register_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(&register_mutex);
devices[chip->regidx] = NULL;
chips[chip->regidx] = NULL;
mutex_unlock(&register_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

55
src/chip.h Normal file
View File

@ -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 */

201
src/comm.c Normal file
View File

@ -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;
}

38
src/comm.h Normal file
View File

@ -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 */

31
src/common.h Normal file
View File

@ -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 */

662
src/control.c Normal file
View File

@ -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;
}

45
src/control.h Normal file
View File

@ -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 */

448
src/firmware.c Normal file
View File

@ -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;
}

27
src/firmware.h Normal file
View File

@ -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 */

215
src/midi.c Normal file
View File

@ -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;
}

41
src/midi.h Normal file
View File

@ -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 */

243
src/pcm.c Normal file
View File

@ -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;
}

31
src/pcm.h Normal file
View File

@ -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 */

95
src/rates.h Normal file
View File

@ -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 */

651
src/substream.c Normal file
View File

@ -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;
}

105
src/substream.h Normal file
View File

@ -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 */

647
src/urbs.c Normal file
View File

@ -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;
}

94
src/urbs.h Normal file
View File

@ -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 */