commit 3cd3904bf8e83e764b834e7afe8d57f543e0933a Author: crt0mega Date: Tue Aug 24 12:56:33 2021 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2653b76 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/debian/README.Debian b/debian/README.Debian new file mode 100644 index 0000000..894f837 --- /dev/null +++ b/debian/README.Debian @@ -0,0 +1,6 @@ +sixfireusb-dkms for Debian +------------------------- + +You will need firmware files for this driver, so ... + + -- crt0mega Tue, 24 Aug 2021 10:32:20 +0200 diff --git a/debian/README.source b/debian/README.source new file mode 100644 index 0000000..26d234a --- /dev/null +++ b/debian/README.source @@ -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 Tue, 24 Aug 2021 10:32:20 +0200 + diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..5dc0f37 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +sixfireusb-dkms (0.6.2-1) unstable; urgency=medium + + * Initial release (Closes: #nnnn) + + -- crt0mega Tue, 24 Aug 2021 10:32:20 +0200 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..4bfaa45 --- /dev/null +++ b/debian/control @@ -0,0 +1,27 @@ +Source: sixfireusb-dkms +Section: kernel +Priority: optional +Maintainer: crt0mega +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 diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..529621b --- /dev/null +++ b/debian/copyright @@ -0,0 +1,39 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: sixfireusb-dkms +Upstream-Contact: +Source: + +Files: * +Copyright: + +License: GPL-2.0+ + +Files: debian/* +Copyright: 2021 crt0mega +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 + . + 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/ diff --git a/debian/dkms b/debian/dkms new file mode 100644 index 0000000..131102e --- /dev/null +++ b/debian/dkms @@ -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" diff --git a/debian/patches/chip.patch b/debian/patches/chip.patch new file mode 100644 index 0000000..3ee2f71 --- /dev/null +++ b/debian/patches/chip.patch @@ -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 + + MODULE_AUTHOR("Torsten Schenk "); +-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 */ diff --git a/debian/patches/control.patch b/debian/patches/control.patch new file mode 100644 index 0000000..7f3898c --- /dev/null +++ b/debian/patches/control.patch @@ -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++; diff --git a/debian/patches/pcm.patch b/debian/patches/pcm.patch new file mode 100644 index 0000000..94f2083 --- /dev/null +++ b/debian/patches/pcm.patch @@ -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; diff --git a/debian/patches/series b/debian/patches/series new file mode 100644 index 0000000..dd2c666 --- /dev/null +++ b/debian/patches/series @@ -0,0 +1,3 @@ +pcm.patch +chip.patch +control.patch diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..e013f07 --- /dev/null +++ b/debian/rules @@ -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) diff --git a/debian/sixfireusb-dkms-docs.docs b/debian/sixfireusb-dkms-docs.docs new file mode 100644 index 0000000..efea0a6 --- /dev/null +++ b/debian/sixfireusb-dkms-docs.docs @@ -0,0 +1,2 @@ +README.Debian +README.source diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..163aaf8 --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/sixfireusb.conf b/sixfireusb.conf new file mode 100644 index 0000000..80c806a --- /dev/null +++ b/sixfireusb.conf @@ -0,0 +1 @@ +blacklist snd_usb_6fire diff --git a/src/CHANGES b/src/CHANGES new file mode 100644 index 0000000..6dfe2ae --- /dev/null +++ b/src/CHANGES @@ -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 diff --git a/src/GPL b/src/GPL new file mode 100644 index 0000000..ce992b2 --- /dev/null +++ b/src/GPL @@ -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 + diff --git a/src/INSTALL b/src/INSTALL new file mode 100644 index 0000000..c6fc2da --- /dev/null +++ b/src/INSTALL @@ -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. + diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..d8b806c --- /dev/null +++ b/src/Makefile @@ -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 + diff --git a/src/README b/src/README new file mode 100644 index 0000000..4af9ed1 --- /dev/null +++ b/src/README @@ -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. + diff --git a/src/chip.c b/src/chip.c new file mode 100644 index 0000000..0dae7ef --- /dev/null +++ b/src/chip.c @@ -0,0 +1,280 @@ +/* + * Linux driver for TerraTec DMX 6Fire USB + * + * Main routines and module definitions. + * + * 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. + */ + +#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 +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Torsten Schenk "); +MODULE_DESCRIPTION("TerraTec DMX 6Fire USB audio driver, version 0.6.1"); +MODULE_LICENSE("GPL v2"); +MODULE_SUPPORTED_DEVICE("{{TerraTec, DMX 6Fire USB}}"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable card */ +static struct sfire_chip *chips[SNDRV_CARDS] = SNDRV_DEFAULT_PTR; +static struct usb_device *devices[SNDRV_CARDS] = SNDRV_DEFAULT_PTR; + +static int tasklet_thresh[SNDRV_CARDS] = { PCM_TASKLET_THRESH_DEFAULT }; +static int pcm_mode[SNDRV_CARDS] = { PCM_MODE_DEFAULT }; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for the 6fire sound device"); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for the 6fire sound device."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable the 6fire sound device."); +module_param_array(tasklet_thresh, int, NULL, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); +MODULE_PARM_DESC(tasklet_thresh, "Tasklet threshold (packets/urb, default = 128)."); +module_param_array(pcm_mode, int, NULL, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); +MODULE_PARM_DESC(pcm_mode, "PCM mode (default = 3)."); + +static DEFINE_MUTEX(register_mutex); + +static void usb6fire_chip_abort(struct sfire_chip *chip) +{ + if (chip) { + if (chip->card) + snd_card_disconnect(chip->card); + if (chip->midi) + usb6fire_midi_abort(chip); + if (chip->comm) + usb6fire_comm_abort(chip); + if (chip->control) + usb6fire_control_abort(chip); + if (chip->pcm) + usb6fire_pcm_abort(chip); + if (chip->urbs) + usb6fire_urbs_abort(chip); + if (chip->substream) + usb6fire_substream_abort(chip); + if (chip->card) { + snd_card_free_when_closed(chip->card); + chip->card = NULL; + } + } +} + +static void usb6fire_chip_destroy(struct sfire_chip *chip) +{ + if (chip) { + if (chip->midi) + usb6fire_midi_destroy(chip); + if (chip->comm) + usb6fire_comm_destroy(chip); + if (chip->control) + usb6fire_control_destroy(chip); + if (chip->pcm) + usb6fire_pcm_destroy(chip); + if (chip->urbs) + usb6fire_urbs_destroy(chip); + if (chip->substream) + usb6fire_substream_destroy(chip); + if (chip->card) + snd_card_free(chip->card); + } +} + +static int usb6fire_chip_probe(struct usb_interface *intf, + const struct usb_device_id *usb_id) +{ + int ret; + int i; + struct sfire_chip *chip = NULL; + struct usb_device *device = interface_to_usbdev(intf); + int regidx = -1; /* index in module parameter array */ + struct snd_card *card = NULL; + + /* check, if firmware is present on device, upload it if not */ + ret = usb6fire_fw_init(intf); + if (ret < 0) + return ret; + else if (ret == FW_NOT_READY) /* firmware update performed */ + return 0; + + /* look if we already serve this card and return if so */ + mutex_lock(®ister_mutex); + for (i = 0; i < SNDRV_CARDS; i++) { + if (devices[i] == device) { + if (chips[i]) + chips[i]->intf_count++; + usb_set_intfdata(intf, chips[i]); + mutex_unlock(®ister_mutex); + return 0; + } else if (regidx < 0) + regidx = i; + } + if (regidx < 0) { + mutex_unlock(®ister_mutex); + snd_printk(KERN_ERR PREFIX "too many cards registered.\n"); + return -ENODEV; + } + devices[regidx] = device; + mutex_unlock(®ister_mutex); + + /* if we are here, card can be registered in alsa. */ + if (usb_set_interface(device, 0, 0) != 0) { + snd_printk(KERN_ERR PREFIX "can't set first interface.\n"); + return -EIO; + } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 15, 0) + ret = snd_card_new(&intf->dev, index[regidx], id[regidx], + THIS_MODULE, sizeof(struct sfire_chip), &card); +#else + ret = snd_card_create(index[regidx], id[regidx], + THIS_MODULE, sizeof(struct sfire_chip), &card); +#endif + if (ret < 0) { + snd_printk(KERN_ERR PREFIX "cannot create alsa card.\n"); + return ret; + } + strcpy(card->driver, "6FireUSB"); + strcpy(card->shortname, "TerraTec DMX6FireUSB"); + sprintf(card->longname, "%s at %d:%d", card->shortname, + device->bus->busnum, device->devnum); + snd_card_set_dev(card, &intf->dev); + + chip = card->private_data; + chips[regidx] = chip; + chip->tasklet_thresh = tasklet_thresh[regidx]; + if (chip->tasklet_thresh < 0) + chip->tasklet_thresh = PCM_TASKLET_THRESH_DEFAULT; + chip->pcm_mode = pcm_mode[regidx]; + if (chip->pcm_mode < 0 || chip->pcm_mode >= PCM_MODES) + chip->pcm_mode = PCM_MODE_DEFAULT; + chip->dev = device; + chip->regidx = regidx; + chip->intf_count = 1; + chip->card = card; + + ret = usb6fire_comm_init(chip); + if (ret < 0) { + usb6fire_chip_destroy(chip); + return ret; + } + + ret = usb6fire_midi_init(chip); + if (ret < 0) { + usb6fire_chip_destroy(chip); + return ret; + } + + ret = usb6fire_control_init(chip); + if (ret < 0) { + usb6fire_chip_destroy(chip); + return ret; + } + + if (chip->pcm_mode != PCM_MODE_OFF) { + ret = usb6fire_substream_init(chip); + if (ret < 0) { + usb6fire_chip_destroy(chip); + return ret; + } + + ret = usb6fire_urbs_init(chip); + if (ret < 0) { + usb6fire_chip_destroy(chip); + return ret; + } + + ret = usb6fire_pcm_init(chip); + if (ret < 0) { + usb6fire_chip_destroy(chip); + return ret; + } + } + + ret = snd_card_register(card); + if (ret < 0) { + snd_printk(KERN_ERR PREFIX "cannot register card."); + usb6fire_chip_destroy(chip); + return ret; + } + usb_set_intfdata(intf, chip); + return 0; +} + +static void usb6fire_chip_disconnect(struct usb_interface *intf) +{ + struct sfire_chip *chip; + struct snd_card *card; + + chip = usb_get_intfdata(intf); + if (chip) { /* if !chip, fw upload has been performed */ + card = chip->card; + chip->intf_count--; + if (!chip->intf_count) { + mutex_lock(®ister_mutex); + devices[chip->regidx] = NULL; + chips[chip->regidx] = NULL; + mutex_unlock(®ister_mutex); + + chip->shutdown = true; + usb6fire_chip_abort(chip); + usb6fire_chip_destroy(chip); + } + } +} + +static struct usb_device_id device_table[] = { + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x0ccd, + .idProduct = 0x0080 + }, + {} +}; + +MODULE_DEVICE_TABLE(usb, device_table); + +static struct usb_driver usb_driver = { + .name = "snd-usb-6fire", + .probe = usb6fire_chip_probe, + .disconnect = usb6fire_chip_disconnect, + .id_table = device_table, +}; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 0) +module_usb_driver(usb_driver); +#else +static int __init usb6fire_chip_init(void) +{ + return usb_register(&usb_driver); +} + +static void __exit usb6fire_chip_cleanup(void) +{ + usb_deregister(&usb_driver); +} + +module_init(usb6fire_chip_init); +module_exit(usb6fire_chip_cleanup); +#endif + diff --git a/src/chip.h b/src/chip.h new file mode 100644 index 0000000..338ae23 --- /dev/null +++ b/src/chip.h @@ -0,0 +1,55 @@ +/* + * 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_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 */ + diff --git a/src/comm.c b/src/comm.c new file mode 100644 index 0000000..18cea6a --- /dev/null +++ b/src/comm.c @@ -0,0 +1,201 @@ +/* + * Linux driver for TerraTec DMX 6Fire USB + * + * Device communications + * + * 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. + */ + +#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; +} + diff --git a/src/comm.h b/src/comm.h new file mode 100644 index 0000000..5fc74d3 --- /dev/null +++ b/src/comm.h @@ -0,0 +1,38 @@ +/* + * 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_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 */ + diff --git a/src/common.h b/src/common.h new file mode 100644 index 0000000..cfb3c24 --- /dev/null +++ b/src/common.h @@ -0,0 +1,31 @@ +/* + * 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_COMMON_H +#define USB6FIRE_COMMON_H + +#include +#include +#include + +#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 */ + diff --git a/src/control.c b/src/control.c new file mode 100644 index 0000000..c9a9568 --- /dev/null +++ b/src/control.c @@ -0,0 +1,662 @@ +/* + * Linux driver for TerraTec DMX 6Fire USB + * + * Mixer control + * + * Author: Torsten Schenk + * 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 +#include +#include + +#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; +} + diff --git a/src/control.h b/src/control.h new file mode 100644 index 0000000..1d20ab5 --- /dev/null +++ b/src/control.h @@ -0,0 +1,45 @@ +/* + * Linux driver for TerraTec DMX 6Fire USB + * + * Author: Torsten Schenk + * 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 */ + diff --git a/src/firmware.c b/src/firmware.c new file mode 100644 index 0000000..7fa28dd --- /dev/null +++ b/src/firmware.c @@ -0,0 +1,448 @@ +/* + * Linux driver for TerraTec DMX 6Fire USB + * + * Firmware loader + * + * 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. + */ + +#include +#include +#include +#include + +#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; +} + diff --git a/src/firmware.h b/src/firmware.h new file mode 100644 index 0000000..c109c4f --- /dev/null +++ b/src/firmware.h @@ -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 */ + diff --git a/src/midi.c b/src/midi.c new file mode 100644 index 0000000..e142a29 --- /dev/null +++ b/src/midi.c @@ -0,0 +1,215 @@ +/* + * Linux driver for TerraTec DMX 6Fire USB + * + * Rawmidi driver + * + * 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. + */ + +#include + +#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; +} diff --git a/src/midi.h b/src/midi.h new file mode 100644 index 0000000..84851b9 --- /dev/null +++ b/src/midi.h @@ -0,0 +1,41 @@ +/* + * 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_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 */ + diff --git a/src/pcm.c b/src/pcm.c new file mode 100644 index 0000000..02c778e --- /dev/null +++ b/src/pcm.c @@ -0,0 +1,243 @@ +/* + * Linux driver for TerraTec DMX 6Fire USB + * + * ALSA PCM interface. + * + * 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. + */ + +//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; +} + diff --git a/src/pcm.h b/src/pcm.h new file mode 100644 index 0000000..d1c595f --- /dev/null +++ b/src/pcm.h @@ -0,0 +1,31 @@ +/* + * 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_PCM_H +#define USB6FIRE_PCM_H + +#include "common.h" + +#include + +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 */ + diff --git a/src/rates.h b/src/rates.h new file mode 100644 index 0000000..412499f --- /dev/null +++ b/src/rates.h @@ -0,0 +1,95 @@ +/* + * Linux driver for TerraTec DMX 6Fire USB + * + * Author: Torsten Schenk + * 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 + +#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 */ + diff --git a/src/substream.c b/src/substream.c new file mode 100644 index 0000000..d025a78 --- /dev/null +++ b/src/substream.c @@ -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 + * 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; +} diff --git a/src/substream.h b/src/substream.h new file mode 100644 index 0000000..9f109ca --- /dev/null +++ b/src/substream.h @@ -0,0 +1,105 @@ +/* + * 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_SUBSTREAM_H +#define USB6FIRE_SUBSTREAM_H + +#include + +#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 */ + diff --git a/src/urbs.c b/src/urbs.c new file mode 100644 index 0000000..e825434 --- /dev/null +++ b/src/urbs.c @@ -0,0 +1,647 @@ +/* + * Linux driver for TerraTec DMX 6Fire USB + * + * URBS manager: manages usb stream + * + * 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. + */ + +#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; +} diff --git a/src/urbs.h b/src/urbs.h new file mode 100644 index 0000000..e60cf69 --- /dev/null +++ b/src/urbs.h @@ -0,0 +1,94 @@ +/* + * 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_URBS_H +#define USB6FIRE_URBS_H + +#include + +#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 */ +