pax_global_header00006660000000000000000000000064150603705130014512gustar00rootroot0000000000000052 comment=084c518866bafb71de5888405c6131e380c07ce5 snd-hdspe-1.0.2/000077500000000000000000000000001506037051300133775ustar00rootroot00000000000000snd-hdspe-1.0.2/.gitignore000066400000000000000000000003601506037051300153660ustar00rootroot00000000000000# OS files .DS_Store # Temp and object files deps modules.order Module.symvers *.cmd .module-common.o sound/pci/hdsp/hdspe/*.o sound/pci/hdsp/hdspe/*.ko sound/pci/hdsp/hdspe/*.mod sound/pci/hdsp/hdspe/*.mod.c # IDE files .idea/ .vscode/ snd-hdspe-1.0.2/LICENSE000066400000000000000000001045151506037051300144120ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. 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 them 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 prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. 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. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey 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; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If 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 convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU 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 that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. 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. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 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. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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 3 of the License, or (at your option) any later version. This program 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 . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . snd-hdspe-1.0.2/Makefile000066400000000000000000000020621506037051300150370ustar00rootroot00000000000000obj-m += sound/pci/hdsp/ # The runtime of DKMS has this environment variable to build for several versions of Linux kernel. ifndef KERNELRELEASE KERNELRELEASE := $(shell uname -r) endif KDIR ?= /lib/modules/${KERNELRELEASE}/build PWD := $(shell pwd) EXTRA_CFLAGS += -DDEBUG -DCONFIG_SND_DEBUG # Force to build the module as loadable kernel module. # Keep in mind that this configuration sound be in 'sound/pci/Kconfig' when upstreaming. export CONFIG_SND_HDSPE=m default: depend $(MAKE) W=1 -C $(KDIR) M=$(PWD) modules clean: $(MAKE) W=1 -C $(KDIR) M=$(PWD) clean -rm *~ -touch deps insert: default -rmmod snd-hdspm insmod sound/pci/hdsp/hdspe/snd-hdspe.ko remove: rmmod snd-hdspe install: default -rmmod snd-hdspm -ln -s $(pwd) /usr/src/alsa-hdspe-0.0 dkms install alsa-hdspe/0.0 uninstall: dkms remove alsa-hdspe/0.0 --all list-controls: -rm asound.state alsactl -f asound.state store show-controls: list-controls less asound.state enable-debug-log: echo 8 > /proc/sys/kernel/printk depend: gcc -MM sound/pci/hdsp/hdspe/hdspe*.c > deps snd-hdspe-1.0.2/README.md000066400000000000000000000131731506037051300146630ustar00rootroot00000000000000# snd-hdspe New linux kernel ALSA driver for [RME](http://www.rme-audio.com) HDSPe MADI / AES / RayDAT / AIO and AIO Pro sound cards and extension modules. Forked from [snd-hdspe](https://github.com/PhilippeBekaert/snd-hdspe) which is no longer maintained as Prof. Dr. Philippe Bekaert passed away in 2022. This fork's `main` branch contains kernel compatibility updates for newer kernels (v5.12, v6.2). The `develop` branch contains WIP features like suspend/resume support, improved logging (debug/production levels) and fixes various compiler warnings. **Original README text** New linux kernel ALSA driver for [RME](http://www.rme-audio.com) HDSPe MADI / AES / RayDAT / AIO and AIO Pro sound cards and extension modules. In addition to the functionality offered by the stock snd-hdspm linux driver, this driver provides support for the HDSPe AIO Pro card and TCO module LTC output and fixes issues such as double/quad speed support of AIO cards. Programmers will appreciate the completely updated control interface, enabling access to the advanced features of these professional audio cards though generic ALSA control mechanisms instead of ad-hoc ioctl's. Users will appreciate [hdspeconf](https://github.com/PhilippeBekaert/hdspeconf), the friendly user space HDSPe sound card configuration tool that builds upon this driver. The driver is compatible with the stock linux ALSA hdspmixer application for controlling the hardware mixer. **Supported hardware** - The snd-hdspe driver focusses on the current (2021) range of RME HDSPe PCIe cards, except MADI-FX: MADI, AES, RayDAT, AIO Pro. AIO is supported as well. - The RME HDSPe MADI-FX is a different beast and is not supported by this driver. See [Adrian Knoths MADI-FX driver work in progress](https://github.com/adiknoth/madifx). **Building the driver** The driver is provided here in source code, to be compiled out of kernel tree. - Install the kernel headers and software development toolchain for your linux distribution. On ubuntu: sudo apt-get install linux-headers-$(uname -r) - Clone this repository. cd to your clone copy folder and type make This builds the snd-hdspe.ko kernel driver in the sound/pci/hdsp/hdspe subdirectory. - You may need to blacklist the stock snd-hdspm driver as this driver targets the same devices. Create a file /usr/lib/modprobe.d/hdspe.conf with the following content: blacklist snd-hdspm **Trying out the driver** - Manually installing the snd-hdspe.ko driver: If installed, remove the snd-hdspm.ko driver (the old driver for RME HDSPe cards) first: sudo -s rmmod snd-hdspm insmod sound/pci/hdsp/hdspe/snd-hdspe.ko or sudo make insert You need to stop all (audio) applications using the snd-hdspm driver before, in particular PulseAudio and the jack audio server. When manually inserting a non-signed kernel module like this, you may need to disable secure boot in your systems BIOS. See below for how to install the kernel module using DKMS. Installing with DKMS is preferred if you plan to use this module on a regular basis instead of just testing it once. - In case you would love or need to see the debug messages spit out by the snd-hdspe.ko module, enable debug log output: sudo echo 8 > /proc/sys/kernel/printk or sudo make enable-debug-log - Removing the snd-hdspe.ko driver and re-installing the default snd-hdspm driver: sudo -s rmmod snd-hdspe.ko modprobe snd-hdspm or sude make remove - Viewing ALSA controls: alsactl -f asound.state store less asound.state or make show-controls - Cleaning up your repository clone folder: make clean **Installing the driver with DKMS build** [Dynamic Kernel Module System (DKMS)](https://github.com/dell/dkms) makes it easy to maintain out-of-tree kernel modules. It's preferable to use DKMS instead of manually installation since it assists module signing for secure boot. - Install DKMS package. On Ubuntu: sudo apt install dkms - For preparation, execute cd to your clone copy folder, then type sudo ln -s $(pwd) /usr/src/alsa-hdspe-0.0 - For installing, type sudo dkms install alsa-hdspe/0.0 - For uninstalling, type sudo dkms remove alsa-hdspe/0.0 - Or simply: make install or make uninstall **Documentation** - [ALSA control elements provided by this driver](doc/controls.md) **Status** At this time (March, 30 2022), the driver is still work in progress. - AES, AIO, AIO Pro, MADI, RayDAT and TCO control and PCM capture and playback, and MIDI support is ready for beta testing. - It is developed on ubuntu studio 20.04 and has only been tested on that distribution so far. - Other TODOs: mixer interface, loopback, power suspend and resume, ... As the code is still work in progress, the control interface may change at any time and features may be added or updated. Inform the author if you are relying on the control interface or features as they are now. **Acknowledgements** - This work builds on previous work by the hdspm linux kernel driver authors over the last nearly 20 years: Winfried Ritsch, Paul Davis, Marcus Andersson, Thomas Charbonnel, Remy Bruno, Florian Faber and Adrian Knoth. (Blame [the author](mailto:linux@panokkel.be) for bugs in the new driver.) - Thanks to [RME](http://www.rme-audio.com) for providing the necessary information and code for writing this driver. - Thanks to [Amptec Belgium](http://www.amptec.be) for hardware support. **License and (No) Warranty** See [LICENSE](https://github.com/PhilippeBekaert/snd-hdspe/blob/main/LICENSE). **Author** [Philippe Bekaert](mailto:linux@panokkel.be), March 2022. snd-hdspe-1.0.2/dkms.conf000066400000000000000000000003021506037051300151770ustar00rootroot00000000000000PACKAGE_NAME="alsa-hdspe" PACKAGE_VERSION="0.0" BUILT_MODULE_LOCATION[0]="./sound/pci/hdsp/hdspe" BUILT_MODULE_NAME[0]="snd-hdspe" DEST_MODULE_LOCATION[0]="/updates/dkms/" AUTOINSTALL="yes" snd-hdspe-1.0.2/doc/000077500000000000000000000000001506037051300141445ustar00rootroot00000000000000snd-hdspe-1.0.2/doc/asound.state000066400000000000000000000254771506037051300165160ustar00rootroot00000000000000state.HDSPe24052016 { control.1 { iface CARD name 'Card Revision' value 212 comment { access read type INTEGER count 1 range '0 - 0 (step 1)' } } control.2 { iface CARD name 'Firmware Build' value 21 comment { access read type INTEGER count 1 range '0 - 0 (step 1)' } } control.3 { iface CARD name Serial value 24052016 comment { access read type INTEGER count 1 range '0 - 0 (step 1)' } } control.4 { iface CARD name 'TCO Present' value true comment { access read type BOOLEAN count 1 } } control.5 { iface CARD name 'Capture PID' value -1 comment { access 'read volatile' type INTEGER count 1 range '0 - 0 (step 1)' } } control.6 { iface CARD name 'Playback PID' value -1 comment { access 'read volatile' type INTEGER count 1 range '0 - 0 (step 1)' } } control.7 { iface CARD name Running value false comment { access 'read volatile' type BOOLEAN count 1 } } control.8 { iface CARD name 'Buffer Size' value 4096 comment { access 'read volatile' type INTEGER count 1 range '64 - 4096 (step 1)' } } control.9 { iface CARD name 'Status Polling' value 10 comment { access 'read write' type INTEGER count 1 range '0 - 1000 (step 1)' } } control.10 { iface HWDEP name 'Raw Sample Rate' value.0 104857600000000 value.1 2377740584 comment { access 'read volatile' type INTEGER64 count 2 range '0 - 0' } } control.11 { iface HWDEP name DDS value 2377723356 comment { access 'read write' type INTEGER count 1 range '2026233816 - 3883614814' } } control.12 { iface CARD name 'Internal Frequency' value '44.1 KHz' comment { access 'read write' type ENUMERATED count 1 item.0 '32 KHz' item.1 '44.1 KHz' item.2 '48 KHz' item.3 '64 KHz' item.4 '88.2 KHz' item.5 '96 KHz' item.6 '128 KHz' item.7 '176.4 KHz' item.8 '192 KHz' } } control.13 { iface CARD name 'Current AutoSync Reference' value TCO comment { access 'read volatile' type ENUMERATED count 1 item.0 WordClk item.1 AES item.2 S/PDIF item.3 ADAT item.4 TCO item.5 SyncIn item.6 Intern } } control.14 { iface CARD name 'External Frequency' value '44.1 KHz' comment { access 'read volatile' type ENUMERATED count 1 item.0 '' item.1 '32 KHz' item.2 '44.1 KHz' item.3 '48 KHz' item.4 '64 KHz' item.5 '88.2 KHz' item.6 '96 KHz' item.7 '128 KHz' item.8 '176.4 KHz' item.9 '192 KHz' } } control.15 { iface CARD name 'Clock Mode' value AutoSync comment { access 'read write' type ENUMERATED count 1 item.0 AutoSync item.1 Master } } control.16 { iface CARD name 'Preferred AutoSync Reference' value TCO comment { access 'read write' type ENUMERATED count 1 item.0 WordClk item.1 AES item.2 S/PDIF item.3 ADAT item.4 TCO item.5 SyncIn } } control.17 { iface CARD name 'AutoSync Status' value.0 N/A value.1 'No Lock' value.2 'No Lock' value.3 'No Lock' value.4 Sync value.5 'No Lock' comment { access 'read volatile' type ENUMERATED count 6 item.0 'No Lock' item.1 Lock item.2 Sync item.3 N/A } } control.18 { iface CARD name 'AutoSync Frequency' value.0 '' value.1 '' value.2 '48 KHz' value.3 '' value.4 '44.1 KHz' value.5 '' comment { access 'read volatile' type ENUMERATED count 6 item.0 '' item.1 '32 KHz' item.2 '44.1 KHz' item.3 '48 KHz' item.4 '64 KHz' item.5 '88.2 KHz' item.6 '96 KHz' item.7 '128 KHz' item.8 '176.4 KHz' item.9 '192 KHz' } } control.19 { iface CARD name 'S/PDIF In' value Coaxial comment { access 'read write' type ENUMERATED count 1 item.0 Optical item.1 Coaxial item.2 Internal } } control.20 { iface CARD name 'S/PDIF Out Optical' value false comment { access 'read write' type BOOLEAN count 1 } } control.21 { iface CARD name 'S/PDIF Out Professional' value false comment { access 'read write' type BOOLEAN count 1 } } control.22 { iface CARD name 'ADAT Internal' value false comment { access 'read write' type BOOLEAN count 1 } } control.23 { iface CARD name 'Single Speed WordClk Out' value false comment { access 'read write' type BOOLEAN count 1 } } control.24 { iface CARD name 'Clear TMS' value false comment { access 'read write' type BOOLEAN count 1 } } control.25 { iface CARD name 'Input Level' value '+13 dBu' comment { access 'read write' type ENUMERATED count 1 item.0 '+4 dBu' item.1 '+13 dBu' item.2 '+19 dBu' item.3 '+24 dBu' } } control.26 { iface CARD name 'Output Level' value '+13 dBu XLR' comment { access 'read write' type ENUMERATED count 1 item.0 '-2 dBu RCA' item.1 '+4 dBu RCA' item.2 '+13 dBu RCA' item.3 '+19 dBu RCA' item.4 '+4 dBu XLR' item.5 '+13 dBu XLR' item.6 '+19 dBu XLR' item.7 '+24 dBu XLR' } } control.27 { iface CARD name 'Phones Level' value 'Lo Power' comment { access 'read write' type ENUMERATED count 1 item.0 'Lo Power' item.1 'Hi Power' } } control.28 { iface HWDEP name Mixer value.0 0 value.1 0 value.2 0 comment { access 'read write volatile' type INTEGER count 3 range '0 - 65535 (step 1)' } } control.29 { iface MIXER name Chn index 1 value 64 comment { access 'read write volatile' type INTEGER count 1 range '0 - 64 (step 1)' } } control.30 { iface MIXER name Chn index 2 value 64 comment { access 'read write volatile' type INTEGER count 1 range '0 - 64 (step 1)' } } control.31 { iface MIXER name Chn index 3 value 64 comment { access 'read write volatile' type INTEGER count 1 range '0 - 64 (step 1)' } } control.32 { iface MIXER name Chn index 4 value 64 comment { access 'read write volatile' type INTEGER count 1 range '0 - 64 (step 1)' } } control.33 { iface MIXER name Chn index 5 value 0 comment { access 'read write volatile' type INTEGER count 1 range '0 - 64 (step 1)' } } control.34 { iface MIXER name Chn index 6 value 0 comment { access 'read write volatile' type INTEGER count 1 range '0 - 64 (step 1)' } } control.35 { iface MIXER name Chn index 7 value 64 comment { access 'read write volatile' type INTEGER count 1 range '0 - 64 (step 1)' } } control.36 { iface MIXER name Chn index 8 value 64 comment { access 'read write volatile' type INTEGER count 1 range '0 - 64 (step 1)' } } control.37 { iface MIXER name Chn index 9 value 64 comment { access 'read write volatile' type INTEGER count 1 range '0 - 64 (step 1)' } } control.38 { iface MIXER name Chn index 10 value 64 comment { access 'read write volatile' type INTEGER count 1 range '0 - 64 (step 1)' } } control.39 { iface MIXER name Chn index 11 value 64 comment { access 'read write volatile' type INTEGER count 1 range '0 - 64 (step 1)' } } control.40 { iface MIXER name Chn index 12 value 64 comment { access 'read write volatile' type INTEGER count 1 range '0 - 64 (step 1)' } } control.41 { iface MIXER name Chn index 13 value 64 comment { access 'read write volatile' type INTEGER count 1 range '0 - 64 (step 1)' } } control.42 { iface MIXER name Chn index 14 value 64 comment { access 'read write volatile' type INTEGER count 1 range '0 - 64 (step 1)' } } control.43 { iface MIXER name Chn index 15 value 64 comment { access 'read write volatile' type INTEGER count 1 range '0 - 64 (step 1)' } } control.44 { iface MIXER name Chn index 16 value 64 comment { access 'read write volatile' type INTEGER count 1 range '0 - 64 (step 1)' } } control.45 { iface CARD name 'LTC In' value.0 72060905474621443 value.1 15439372236 comment { access 'read volatile' type INTEGER64 count 2 range '0 - 0' } } control.46 { iface CARD name 'LTC In Valid' value true comment { access 'read volatile' type BOOLEAN count 1 } } control.47 { iface CARD name 'LTC In Frame Rate' value '24 fps' comment { access 'read volatile' type ENUMERATED count 1 item.0 '24 fps' item.1 '25 fps' item.2 '29.97 fps' item.3 '30 fps' } } control.48 { iface CARD name 'LTC In Drop Frame' value false comment { access 'read volatile' type BOOLEAN count 1 } } control.49 { iface CARD name 'LTC In Pull Factor' value 1000 comment { access 'read volatile' type INTEGER count 1 range '0 - 0' } } control.50 { iface CARD name 'TCO Video Format' value 'No video' comment { access 'read volatile' type ENUMERATED count 1 item.0 'No video' item.1 NTSC item.2 PAL } } control.51 { iface CARD name 'TCO WordClk Valid' value false comment { access 'read volatile' type BOOLEAN count 1 } } control.52 { iface CARD name 'TCO WordClk Speed' value 'Single Speed' comment { access 'read volatile' type ENUMERATED count 1 item.0 'Single Speed' item.1 'Double Speed' item.2 'Quad Speed' } } control.53 { iface CARD name 'TCO Lock' value true comment { access 'read volatile' type BOOLEAN count 1 } } control.54 { iface CARD name 'LTC Run' value true comment { access 'read write' type BOOLEAN count 1 } } control.55 { iface CARD name 'TCO Sample Rate' value 'from App' comment { access 'read write' type ENUMERATED count 1 item.0 '44.1 KHz' item.1 '48 KHz' item.2 'from App' } } control.56 { iface CARD name 'TCO Pull' value '0' comment { access 'read write' type ENUMERATED count 1 item.0 '0' item.1 '+0.1 %' item.2 '-0.1 %' item.3 '+4 %' item.4 '-4 %' } } control.57 { iface CARD name 'TCO WCK Conversion' value '1:1' comment { access 'read write' type ENUMERATED count 1 item.0 '1:1' item.1 '44.1 KHz -> 48 KHz' item.2 '48 KHz -> 44.1 KHz' } } control.58 { iface CARD name 'TCO Frame Rate' value '24 fps' comment { access 'read write' type ENUMERATED count 1 item.0 '24 fps' item.1 '25 fps' item.2 '29.97 fps' item.3 '29.97 dfps' item.4 '30 fps' item.5 '30 dfps' } } control.59 { iface CARD name 'TCO Sync Source' value LTC comment { access 'read write' type ENUMERATED count 1 item.0 WordClk item.1 Video item.2 LTC } } control.60 { iface CARD name 'TCO Word Term' value true comment { access 'read write' type BOOLEAN count 1 } } } snd-hdspe-1.0.2/doc/controls.md000066400000000000000000000430251506037051300163350ustar00rootroot00000000000000ALSA control elements ===================== As usual alsactl -f asound.state store stores the actual ALSA control elements with all metadata and exact enumeration values in the file name asound.state. See [hdspeconf](https://github.com/PhilippeBekaert/hdspeconf) for an example application using (and showing how to use) these elements. The controls correspond for most part with the similarly named controls in the Windows and MAC OSX control applications provided by RME. See the RME HDSPe sound card user guides, and [hdspeconf](https://github.com/PhilippeBekaert/hdspeconf) documentation for more information on these controls. **Acces modes**: The access values in the tables below are a combination of the following symbols: | Symbol | Meaning | | :- | :- | | R | Control element is readable | | W | Control element is writable | | V | Control element is volatile | Controls common to all supported cards -------------------------------------- | Interface | Name | Access | Value Type | Description | | :- | :- | :- | :- | :- | | CARD | Card Revision | R | Int | PCI class revision. Uniquely identifies card model. | | CARD | Firmware Build | R | Int | Firmware version. | | CARD | Serial | R | Int | Card serial number. | | CARD | TCO Present | R | Bool | Whether or not TCO module is present. | | CARD | Capture PID | RV | Int | Current capture process ID, or -1. | | CARD | Playback PID | RV | Int | Current playback process ID, or -1. | | CARD | Running | RV | Bool | Whether or not some process is capturing or playing back. | | CARD | Buffer Size | RV | Int | Sample buffer size, in frames. | | CARD | Status Polling | RWV | Int | See below **Status Polling** | | HWDEP | DDS | RW | Int | See below **DDS** | | HWDEP | Raw Sample Rate | RV | Int64 | See below **DDS** | | CARD | Clock Mode | RW | Enum | Master or AutoSync. | | CARD | Preferred AutoSync Reference | RW | Enum | Preferred clock source, if in AutoSync mode. | | CARD | Current AutoSync Reference | RV | Enum | Current clock source. | | CARD | AutoSync Status | RV | Enum | AutoSync clock status: N/A, No Lock, Lock or Sync, for all sources. | | CARD | AutoSync Frequency | RV | Enum | Current clock source sample rate class, for all sources: 32 KHz, 44.1 KHz, 48 KHz, 64 KHz, 88.2 KHz, 96 KHz, 128 KHz 176.4 KHz 192 KHz. Note: MADI cards only report this for the MADI input and not for the other sources. | | CARD | Internal Frequency | RW | Enum | Internal sampling rate class: 32 KHz, 44.1 KHz, 48 KHz etc.... | **Status Polling** Use this control element to enable or disable kernel space status polling. The value of this element is the frequency at which to perform status polling in the driver, or 0 to disable the feature. If non-zero, the driver will poll for card changes in the value of volatile control elements at approximately the indicated frequency. A notification event is generated on any status ALSA control elements that has changed. If any have changed, the value of the Status Polling control element is reset to 0, notifying client applications, and effectively disabling status polling until a client application enables it again by setting a non-zero value. Status polling is also automatically disabled after a few seconds. When automatically disabled, a notification is sent as well, so that client applications can re-enable it. Whenever an application receives a "Status Polling" event notification, it shall set the value of this control element to the maximum of its reported value and the applications desired value to avoid ping-ponging changes with other applications. **DDS** The HDSPe cards report effective sampling frequency as a ratio of a fixed frequency constant typical for the card, and the content of a register. This ratio is returned in the "Raw Sample Rate" control element. The numerator is the first value, and is a true 64-bit value. The denominator is a 32-bit value, and provided as the second value. The "DDS" control element enables setting the DDS register, determining internal sample rate to sub-Hz accuracy. The DDS register value is the denominator of the desired sample rate, given as a ratio with same numerator as the "Raw Sample Rate" control element, i.o.w. the numerator is the first value of the "Raw Sample Rate" control element. This can be used to synchronise the cards internal clock to e.g. a system clock. TCO controls ------------ | Interface | Name | Access | Value Type | Description | | :- | :- | :- | :- | :- | | CARD | TCO Firmware | RO | Int | TCO module firmware version | | CARD | LTC In | RV | Int64 | Incoming LTC code - see below **LTC control** | | CARD | LTC In Drop Frame | RV | Bool | Whether incoming LTC is drop frame format or not | | CARD | LTC In Frame Rate | RV | Enum | Incoming **LTC frame rate**: 24, 25 or 30 fps | | CARD | LTC In Pull Factor | RV | Int | Incoming **LTC frame rate** deviation from standard | | CARD | LTC In Valid | RV | Bool | Whether or not valid LTC input is detected | | CARD | LTC Out | W | Int64 | LTC output control - see below **LTC control** | | CARD | LTC Time | RV | Int64 | Current periods end LTC time - see below **LTC control** | | CARD | LTC Run | RW | Bool | Pauze / restart LTC output | | CARD | LTC Frame Rate | RW | Enum | TCO LTC engine frame rate: 24, 25, 29.97, 29.97 DF or 30 fps | | CARD | LTC Sample Rate | RW | Enum | TCO LTC engine audio sample rate: 44.1 KHz, 48 KHz, **From App** | | CARD | TCO Lock | RV | Bool | Whether or not the TCO is locked to LTC, Video or Word Clock | | CARD | TCO Pull | RW | Enum | Pull Up / Pull Down factor | | CARD | TCO Sync Source | RW | Enum | TCO preferred synchronisation source: LTC, Video or Word Clock | | CARD | TCO Video Format | RV | Enum | Video format reference signal detected: PAL or NTSC blackburst. Firmware 11 or higher detect SDI reference signals of potential other frame rates, use TCO Video Frame Rate control if TCO firmware >- 11| | CARD | TCO Video Frame Rate | RV | Enum | Video frame rate detected (meaningful only if TCO firmware >= 11) | | CARD | TCO WordClk Conversion | RW | Enum | Word clock rate conversion 1:1, 44.1 -> 48 KHz, 48 -> 44.1 KHz | | CARD | TCO WordClk Term | RW | Bool | Whether or not to 75 Ohm terminate the word clock/video input BNC | | CARD | TCO WordClk Valid | RV | Bool | Whether or not a valid word clock signal is detected | | CARD | TCO WordClk Speed | RV | Enum | Detected input word clock speed | | CARD | TCO WorldClk Out Speed | RW | Enum | Output word clock speed | **LTC Control** If a valid LTC signal is presented to the TCO module, the 'LTC In' control will report two 64-bit values: the current LTC 64-bit code and the LTC time at which that current code started. The current LTC code is the code of the last fully received LTC frame, incremented by one frame. The current LTC start time in fact is the time at which that last fully received LTC frame ended. This is correct, and what a user expects, for continuous and forward running LTC. For stationary, jumping, or backward running LTC, the code shall be corrected appropriately. LTC output is started by writing the 'LTC Out' control. The 'LTC Out' control contains two 64-bit values similar to the 'LTC In' control element: the LTC 64-bit code to start the LTC output with, and the LTC time at which that code shall be started. If the indicated time is in the past or further in the future than a few LTC frame durations, the start time and time code are adapted accordingly to create output that would result if the indicated time code were started at the indicated time (if in the past) or that will result in the indicated time code being generated at the indicated time (if in the future). The SMPTE 12-1 standard defines LTC as a 80-bit code, with 64 data bits and 16 synchronisation bits. The 64-bit LTC code mentioned above corresponds to the 64 data bits of an SMPTE 12-1 80-bit code. The data bits are described in the [SMPTE 12-1 standard](https://ieeexplore.ieee.org/document/7291029/definitions?anchor=definitions) and on [wikipedia](https://en.wikipedia.org/wiki/Linear_timecode). The LTC time is the number of audio frames processed since the snd-hdspe driver was started. The 'LTC Time' control allows to read the LTC time corresponding to the end of the current period. In a jack audio connection kit application process callback, an application would query the current period jack audio frame counter using the jack_get_cycle_times() call and add the period size, on the one hand side. The application would read the LTC time from the 'LTC Time' control on the other hand size. The thus obtained counters represent the same moment in time, and allow to convert the time reported in a 'LTC In' control to a jack audio frame count, or to calculate the LTC time at which LTC output shall be started from a jack audio frame count. *Note* ALSA probably provides mechanisms to query clocks, such as the here described LTC time, more efficiently than through a control element. In future, the 'LTC Time' control element may be replaced by such more efficient mechanism. Examples: - positional time code is generated by starting code 00:00:00:00 at time 0. The output time code will reflect the time since the start of the driver. - jam sync: set 'LTC Out' time code and time to the current 'LTC In' time code and time. Outputting wall clock LTC: set 'LTC Out' time code to the special value -1, and the time to the number of seconds east of GMT corresponding to the time zone, and corrected for daylight saving time if in effect, if the computers real time clock is set to UTC and not to local time zone. The driver will replace a hexadecimal time code of 3f:7f:7f:3f by the exact value of the real-time clock at the time the request gets processed, and add the number of seconds indicated in the time field of the control. Such hexadecimal time code results from setting the first value of the 'LTC Out' control to -1. The deviation of local time w.r.t. UTC can be queried with the localtime_r() GNU libc call. **LTC frame rate** SMPTE 12-1 time codes contain control bits indicating the frame rate of the LTC: 24, 25 or 30 fps. The frame rate standard of incoming LTC is reported in the 'LTC In Frame Rate' control. The effective frame rate may however deviate from what the frame rate bits in the LTC codes indicate. For instance, NTSC 29.97 fps is reported as 30 fps. The deviation between actual and standard frame rate is reported in the 'LTC In Pull Factor' control. This control returns a value of 1000 for nominal speed, less than 1000 for slower rates and greater than 1000 for higher effective rate. The value results from measuring the actual LTC frame duration in the driver. Example: 29.97 NTSC pull down LTC will be reported with a pull factor of 999. The 'LTC Frame Rate' property controls the TCO LTC engine frame rate. Usually, 'LTC Frame Rate' and 'TCO Pull' shall be set to match the incoming LTC effective frame rate, in order to produce a clean 44.1 KHz or 48 KHz sample clock synchronisation. But it also sets the frame rate for LTC output. **From App** The 'From App' LTC sample rate setting will set the TCO LTC engine sample rate to match the audio card sample rate class: 44.1 KHz if the sound card is running at 44.1 KHz, and 48 KHz otherwise (the TCO does not support 32 KHz sample rate). AES controls: ------------- | Interface | Name | Access | Value Type | Description | | :- | :- | :- | :- | :- | | CARD | Double Speed Mode | RW | Enum | Double speed mode: Single Wire or Double Wire | | CARD | Quad Speed Mode | RW | Enum | Quad speed mode: Single Wire, Double Wire or Quad Wire | | CARD | Professional | RW | Bool | If true, output professional mode AES (5V, professional mode status bits). If false, outputs 2V and consumer status bits, compatible with S/PDIF HiFi equipment e.g. | | CARD | Emphasis | RW | Bool | Enable high frequency emphasis status bit in output. | | CARD | Non Audio | RW | Bool | Enable non-audio (dolby/AC3) status bits in output. | | CARD | Line Out | RW | Bool | On by default. Disable for AC3 output. | | CARD | Single Speed WordClk Out | RW | Bool | Output single-speed word clock signal, also when running in double or quad speed mode | | CARD | Clear TMS | RW | Bool | Clear track-marker and status bits from AES and ADAT audio samples. If not set, these bits are available as the least significant bits of PCM data. | AIO controls ------------ | Interface | Name | Access | Value Type | Description | | :- | :- | :- | :- | :- | | CARD | Input Level | RW | Enum | Analog audio input reference level: -10 dBV (with 12 dB headroom), +4 dBu (with 9dB headroom), Lo Gain (+4 dBu with 15 dB headroom) | | CARD | Output Level | RW | Enum | Analog audio output reference level: -10 dBV (with 12 dB headroom), +4 dBu (with 9dB headroom), Hi Gain (+4 dBu with 15 dB headroom) | | CARD | XLR Breakout Cable | RW | Enum | Analog output breakout cable: XLR or RCA. -6 dB gain correction on XLR for correct reference level | | CARD | Phones Level | RW | Enum | Headphones output level: same options as Output Level | | CARD | S/PDIF In | RW | Enum | S/PDIF input connector: coaxial, optical or internal | | CARD | S/PDIF Out Optical | RW | Bool | Output S/PDIF over TOSLINK | | CARD | S/PDIF Out Professional | RW | Bool | Output professional mode S/PDIF | | CARD | ADAT Internal | RW | Bool | Use the internal connector for ADAT, with AEB or TEB expansion board | | CARD | Single Speed WordClk Out | RW | Bool | Output single-speed word clock signal, also when running in double or quad speed mode | | CARD | Clear TMS | RW | Bool | Clear track-marker and status bits from AES and ADAT audio samples. If not set, these bits are available as the least significant bits of PCM data. | | CARD | AO4S Present | RO | Bool | AO4S-192 analog output extension board present | | CARD | AI4S Present | RO | Bool | AI4S-192 analog input extension board present | AIO Pro controls ---------------- | Interface | Name | Access | Value Type | Description | | :- | :- | :- | :- | :- | | CARD | Input Level | RW | Enum | Analog audio **input level** | | CARD | Output Level | RW | Enum | Analog audio **output level** | | CARD | Phones Level | RW | Enum | Headphones output level: High power or Low power | | CARD | S/PDIF In | RW | Enum | S/PDIF input connector: coaxial, optical or internal | | CARD | S/PDIF Out Optical | RW | Bool | Output S/PDIF over TOSLINK | | CARD | S/PDIF Out Professional | RW | Bool | Output professional mode S/PDIF | | CARD | ADAT Internal | RW | Bool | Use the internal connector for ADAT, with AEB or TEB expansion board | | CARD | Single Speed WordClk Out | RW | Bool | Output single-speed word clock signal, also when running in double or quad speed mode | | CARD | Clear TMS | RW | Bool | Clear track-marker and status bits from AES and ADAT audio samples. If not set, these bits are available as the least significant bits of PCM data. | **Input level** Full scale PCM input data for analog input coresponds to +4, +13, +19 or +24 dBu level. **Output level** Full scale PCM output data for analog output corresponds to +4, +13, +19 or +24 dBu level if outputting balanced audio (using the XLR breakout cable), or -2, +4, +13 or +19 dBu level if outputting unbalanced audio (using the RCA breakout cable). MADI controls ------------- | Interface | Name | Access | Value Type | Description | | :- | :- | :- | :- | :- | | CARD | External Frequency | RV | Enum | Frequency class of the current autosync reference: 32KHz, 44.1KHz, 48KHz, etc... (MADI cards do not report the frequency class of each autosync reference individually, like other cards do.) | | CARD | Preferred Input | RW | Enum | Preferred MADI input: Optical, Coaxial | | CARD | Autoselect Input | RW | Bool | Whether or not to automatically switch over input if preferred input is not available (a.k.a. safe mode) | | CARD | Current Input | RV | Enum | Current MADI input: Optical, Coaxial | | CARD | RX 64 Channels Mode | RV | Bool | Whether or not we're currently receiving 64 channels mode (true) or 56 channels mode (false) MADI input | | CARD | TX 64 Channels Mode | RW | Bool | Transmit 64 channels mode (true) or 56 channels mode (false) | | CARD | Double Wire Mode | RW | Bool | Double speed mode: 48K frame mode (= S/MUX or double wire mode) if true, 96K frame (single wire) mode if false | | CARD | Line Out | RW | Bool | Enable/disable headphone output | | CARD | Single Speed WordClk Out | RW | Bool | Output single-speed word clock signal, also when running in double or quad speed mode | | CARD | Clear TMS | RW | Bool | Clear track-marker and status bits from MADI audio samples. If not set, these bits are available as the least significant bits of PCM data. | RayDAT controls --------------- | Interface | Name | Access | Value Type | Description | | :- | :- | :- | :- | :- | | CARD | S/PDIF In | RW | Enum | S/PDIF input connector: coaxial, optical from ADAT4 connector, or internal | | CARD | S/PDIF Out Optical | RW | Bool | Output S/PDIF on ADAT4 connector | | CARD | S/PDIF Out Professional | RW | Bool | Output professional mode S/PDIF | | CARD | ADAT1 Internal | RW | Bool | Use the internal ADAT1 connector instead of optical, for AEB or TEB expansion board | | CARD | ADAT2 Internal | RW | Bool | Use the internal ADAT2 connector instead of optical, for AEB or TEB expansion board | | CARD | Single Speed WordClk Out | RW | Bool | Output single-speed word clock signal, also when running in double or quad speed mode | | CARD | Clear TMS | RW | Bool | Clear track-marker and status bits from AES and ADAT audio samples. If not set, these bits are available as the least significant bits of PCM data. | snd-hdspe-1.0.2/sound/000077500000000000000000000000001506037051300145275ustar00rootroot00000000000000snd-hdspe-1.0.2/sound/pci/000077500000000000000000000000001506037051300153025ustar00rootroot00000000000000snd-hdspe-1.0.2/sound/pci/hdsp/000077500000000000000000000000001506037051300162405ustar00rootroot00000000000000snd-hdspe-1.0.2/sound/pci/hdsp/Makefile000066400000000000000000000001061506037051300176750ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_SND_HDSPE) += hdspe/ snd-hdspe-1.0.2/sound/pci/hdsp/hdspe/000077500000000000000000000000001506037051300173435ustar00rootroot00000000000000snd-hdspe-1.0.2/sound/pci/hdsp/hdspe/Makefile000066400000000000000000000004271506037051300210060ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_SND_HDSPE) += snd-hdspe.o snd-hdspe-objs := hdspe_core.o hdspe_pcm.o hdspe_midi.o hdspe_hwdep.o \ hdspe_proc.o hdspe_control.o hdspe_mixer.o hdspe_tco.o \ hdspe_common.o hdspe_madi.o hdspe_aes.o hdspe_raio.o \ hdspe_ltc_math.o snd-hdspe-1.0.2/sound/pci/hdsp/hdspe/hdspe.h000066400000000000000000000713271506037051300206310ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note /** * @file hdspe.h * @brief RME HDSPe driver user space API. * * Note: The definitions and structs defined in this header file are used * within the driver as well as in IOCTLs. As they are used within the * driver, they are up to date. However, the IOCTLs should be considered * obsolete: the same information on the driver can be obtained via * standard ALSA control mechanisms, except for the mixer and level * meters at this time. To my knowledge (PhB), the only (public) application * still using the IOCTL interface is hdspmixer. hdspeconf is fully based on * the ALSA control mechanism. Do not use the IOCTLs for new development - * inform Philippe.Bekaert@uhasselt.be in case you wouldn't be able to do * without. * * 20210728 ... 0813 - Philippe.Bekaert@uhasselt.be * 20220329,30 - PhB : API version 3 (TCO related additions) * * Based on earlier work by Winfried Ritsch (IEM, 2003) and * Thomas Charbonnel (thomas@undata.org), * information kindly made available by RME (www.rme-audio.com), * and hardware kindly made available by Amptec Belgium (www.amptec.be). */ #ifndef __SOUND_HDSPE_H #define __SOUND_HDSPE_H #include #include /* User space API version. * Structs returned by the HDSPe driver ioctls contain the API version with which the * kernel driver has been compiled. API users should check that version against * HDSPE_VERSION and take appropriate action in case versions differ. */ #define HDSPE_VERSION 3 /* Maximum hardware input, software playback and hardware output * channels is 64 even on 56Mode you have 64playbacks to matrix. */ #define HDSPE_MAX_CHANNELS 64 /* Card model */ enum hdspe_io_type { HDSPE_MADI = 0, HDSPE_MADIFACE = 1, HDSPE_AIO = 2, HDSPE_AES = 3, HDSPE_RAYDAT = 4, HDSPE_AIO_PRO = 5, HDSPE_IO_TYPE_COUNT = 6, HDSPE_IO_TYPE_INVALID = 7, HDSPE_IO_TYPE_FORCE_32BIT = 0xffffffff }; #define HDSPE_IO_TYPE_NAME(i) \ (i == HDSPE_MADI ? "MADI" : \ i == HDSPE_MADIFACE ? "MADIface" : \ i == HDSPE_AIO ? "AIO" : \ i == HDSPE_AES ? "AES" : \ i == HDSPE_RAYDAT ? "RayDAT" : \ i == HDSPE_AIO_PRO ? "AIO Pro" : \ "???") /* Clock mode */ enum hdspe_clock_mode { HDSPE_CLOCK_MODE_AUTOSYNC = 0, HDSPE_CLOCK_MODE_MASTER = 1, HDSPE_CLOCK_MODE_COUNT = 2, HDSPE_CLOCK_MODE_INVALID = 3, HDSPE_CLOCK_MODE_FORCE_32BIT = 0xffffffff }; #define HDSPE_CLOCK_MODE_NAME(i) \ (i == HDSPE_CLOCK_MODE_AUTOSYNC ? "AutoSync" : \ i == HDSPE_CLOCK_MODE_MASTER ? "Master" : \ "???") /* Speed mode */ enum hdspe_speed { HDSPE_SPEED_SINGLE = 0, HDSPE_SPEED_DOUBLE = 1, HDSPE_SPEED_QUAD = 2, HDSPE_SPEED_COUNT = 3, HDSPE_SPEED_INVALID = 4, HDSPE_SPEED_FORCE_32BIT = 0xffffffff }; #define HDSPE_SPEED_NAME(i) \ (i == HDSPE_SPEED_SINGLE ? "Single Speed" : \ i == HDSPE_SPEED_DOUBLE ? "Double Speed" : \ i == HDSPE_SPEED_QUAD ? "Quad Speed" : \ "???") /* frequency class */ enum hdspe_freq { HDSPE_FREQ_NO_LOCK = 0, HDSPE_FREQ_32KHZ = 1, HDSPE_FREQ_44_1KHZ = 2, HDSPE_FREQ_48KHZ = 3, HDSPE_FREQ_64KHZ = 4, HDSPE_FREQ_88_2KHZ = 5, HDSPE_FREQ_96KHZ = 6, HDSPE_FREQ_128KHZ = 7, HDSPE_FREQ_176_4KHZ = 8, HDSPE_FREQ_192KHZ = 9, HDSPE_FREQ_COUNT = 10, HDSPE_FREQ_INVALID = 11, HDSPE_FREQ_FORCE_32BIT= 0xffffffff }; #define HDSPE_FREQ_NAME(i) \ (i == HDSPE_FREQ_NO_LOCK ? "" : \ i == HDSPE_FREQ_32KHZ ? "32 KHz" : \ i == HDSPE_FREQ_44_1KHZ ? "44.1 KHz" : \ i == HDSPE_FREQ_48KHZ ? "48 KHz" : \ i == HDSPE_FREQ_64KHZ ? "64 KHz" : \ i == HDSPE_FREQ_88_2KHZ ? "88.2 KHz" : \ i == HDSPE_FREQ_96KHZ ? "96 KHz" : \ i == HDSPE_FREQ_128KHZ ? "128 KHz" : \ i == HDSPE_FREQ_176_4KHZ ? "176.4 KHz" : \ i == HDSPE_FREQ_192KHZ ? "192 KHz" : \ "???") #define HDSPE_FREQ_SAMPLE_RATE(i) \ (i == HDSPE_FREQ_NO_LOCK ? 0 : \ i == HDSPE_FREQ_32KHZ ? 32000 : \ i == HDSPE_FREQ_44_1KHZ ? 44100 : \ i == HDSPE_FREQ_48KHZ ? 48000 : \ i == HDSPE_FREQ_64KHZ ? 64000 : \ i == HDSPE_FREQ_88_2KHZ ? 88200 : \ i == HDSPE_FREQ_96KHZ ? 96000 : \ i == HDSPE_FREQ_128KHZ ? 128000 : \ i == HDSPE_FREQ_176_4KHZ ? 176400 : \ i == HDSPE_FREQ_192KHZ ? 192000 : \ 0) /* Clock source aka AutoSync references */ enum hdspe_clock_source { HDSPE_CLOCK_SOURCE_WORD = 0, // Word clock HDSPE_CLOCK_SOURCE_1 = 1, // Digital audio input 1 HDSPE_CLOCK_SOURCE_2 = 2, // Digital audio input 2 HDSPE_CLOCK_SOURCE_3 = 3, // Digital audio input 3 HDSPE_CLOCK_SOURCE_4 = 4, // Digital audio input 4 HDSPE_CLOCK_SOURCE_5 = 5, // Digital audio input 5 HDSPE_CLOCK_SOURCE_6 = 6, // Digital audio input 6 HDSPE_CLOCK_SOURCE_7 = 7, // Digital audio input 7 HDSPE_CLOCK_SOURCE_8 = 8, // Digital audio input 8 HDSPE_CLOCK_SOURCE_TCO = 9, // Time Code Option HDSPE_CLOCK_SOURCE_SYNC_IN = 10, // Internal Sync input HDSPE_CLOCK_SOURCE_11 = 11, // Unused HDSPE_CLOCK_SOURCE_12 = 12, // Unused HDSPE_CLOCK_SOURCE_13 = 13, // Unused HDSPE_CLOCK_SOURCE_14 = 14, // Unused HDSPE_CLOCK_SOURCE_INTERN = 15, // Internal clock ("master mode") HDSPE_CLOCK_SOURCE_COUNT = 16, HDSPE_CLOCK_SOURCE_INVALID = 17, HDSPE_CLOCK_SOURCE_FORCE_32BIT = 0xffffffff }; /* Synonyms for HDSP_CLOCK_SOURCE_1..8, for each card model: */ #define HDSPE_CLOCK_SOURCE_MADI HDSPE_CLOCK_SOURCE_1 /* MADI */ #define HDSPE_CLOCK_SOURCE_AES1 HDSPE_CLOCK_SOURCE_1 /* AES */ #define HDSPE_CLOCK_SOURCE_AES2 HDSPE_CLOCK_SOURCE_2 #define HDSPE_CLOCK_SOURCE_AES3 HDSPE_CLOCK_SOURCE_3 #define HDSPE_CLOCK_SOURCE_AES4 HDSPE_CLOCK_SOURCE_4 #define HDSPE_CLOCK_SOURCE_AES5 HDSPE_CLOCK_SOURCE_5 #define HDSPE_CLOCK_SOURCE_AES6 HDSPE_CLOCK_SOURCE_6 #define HDSPE_CLOCK_SOURCE_AES7 HDSPE_CLOCK_SOURCE_7 #define HDSPE_CLOCK_SOURCE_AES8 HDSPE_CLOCK_SOURCE_8 #define HDSPE_CLOCK_SOURCE_AES HDSPE_CLOCK_SOURCE_1 /* RayDAT/AIO/AIO Pro */ #define HDSPE_CLOCK_SOURCE_SPDIF HDSPE_CLOCK_SOURCE_2 #define HDSPE_CLOCK_SOURCE_ADAT HDSPE_CLOCK_SOURCE_3 /* AIO/AIO Pro */ #define HDSPE_CLOCK_SOURCE_ADAT1 HDSPE_CLOCK_SOURCE_3 /* RayDAT */ #define HDSPE_CLOCK_SOURCE_ADAT2 HDSPE_CLOCK_SOURCE_4 #define HDSPE_CLOCK_SOURCE_ADAT3 HDSPE_CLOCK_SOURCE_5 #define HDSPE_CLOCK_SOURCE_ADAT4 HDSPE_CLOCK_SOURCE_6 #define HDSPE_MADI_CLOCK_SOURCE_NAME(i) \ (i == HDSPE_CLOCK_SOURCE_WORD ? "WordClk": \ i == HDSPE_CLOCK_SOURCE_MADI ? "MADI" : \ i == HDSPE_CLOCK_SOURCE_TCO ? "TCO" : \ i == HDSPE_CLOCK_SOURCE_SYNC_IN ? "SyncIn" : \ i == HDSPE_CLOCK_SOURCE_INTERN ? "Intern" : \ "???") #define HDSPE_AES_CLOCK_SOURCE_NAME(i) \ (i == HDSPE_CLOCK_SOURCE_WORD ? "WordClk": \ i == HDSPE_CLOCK_SOURCE_AES1 ? "AES1" : \ i == HDSPE_CLOCK_SOURCE_AES2 ? "AES2" : \ i == HDSPE_CLOCK_SOURCE_AES3 ? "AES3" : \ i == HDSPE_CLOCK_SOURCE_AES4 ? "AES4" : \ i == HDSPE_CLOCK_SOURCE_AES5 ? "AES5" : \ i == HDSPE_CLOCK_SOURCE_AES6 ? "AES6" : \ i == HDSPE_CLOCK_SOURCE_AES7 ? "AES7" : \ i == HDSPE_CLOCK_SOURCE_AES8 ? "AES8" : \ i == HDSPE_CLOCK_SOURCE_TCO ? "TCO" : \ i == HDSPE_CLOCK_SOURCE_SYNC_IN ? "SyncIn" : \ i == HDSPE_CLOCK_SOURCE_INTERN ? "Intern" : \ "???") #define HDSPE_RAYDAT_CLOCK_SOURCE_NAME(i) \ (i == HDSPE_CLOCK_SOURCE_WORD ? "WordClk": \ i == HDSPE_CLOCK_SOURCE_AES ? "AES" : \ i == HDSPE_CLOCK_SOURCE_SPDIF ? "S/PDIF" : \ i == HDSPE_CLOCK_SOURCE_ADAT1 ? "ADAT1" : \ i == HDSPE_CLOCK_SOURCE_ADAT2 ? "ADAT2" : \ i == HDSPE_CLOCK_SOURCE_ADAT3 ? "ADAT3" : \ i == HDSPE_CLOCK_SOURCE_ADAT4 ? "ADAT4" : \ i == HDSPE_CLOCK_SOURCE_TCO ? "TCO" : \ i == HDSPE_CLOCK_SOURCE_SYNC_IN ? "SyncIn" : \ i == HDSPE_CLOCK_SOURCE_INTERN ? "Intern" : \ "???") /* AIO & AIO Pro */ #define HDSPE_AIO_CLOCK_SOURCE_NAME(i) \ (i == HDSPE_CLOCK_SOURCE_WORD ? "WordClk": \ i == HDSPE_CLOCK_SOURCE_AES ? "AES" : \ i == HDSPE_CLOCK_SOURCE_SPDIF ? "S/PDIF" : \ i == HDSPE_CLOCK_SOURCE_ADAT ? "ADAT" : \ i == HDSPE_CLOCK_SOURCE_TCO ? "TCO" : \ i == HDSPE_CLOCK_SOURCE_SYNC_IN ? "SyncIn" : \ i == HDSPE_CLOCK_SOURCE_INTERN ? "Intern" : \ "???") /* (Use this for initializations, or with compile time constants only) */ #define HDSPE_CLOCK_SOURCE_NAME(io_type, i) \ (io_type == HDSPE_MADI ? HDSPE_MADI_CLOCK_SOURCE_NAME(i) : \ io_type == HDSPE_MADIFACE ? HDSPE_MADI_CLOCK_SOURCE_NAME(i) : \ io_type == HDSPE_AES ? HDSPE_AES_CLOCK_SOURCE_NAME(i) : \ io_type == HDSPE_RAYDAT ? HDSPE_RAYDAT_CLOCK_SOURCE_NAME(i) : \ io_type == HDSPE_AIO ? HDSPE_AIO_CLOCK_SOURCE_NAME(i) : \ io_type == HDSPE_AIO_PRO ? HDSPE_AIO_CLOCK_SOURCE_NAME(i) : \ "???") /* SyncCheck Status: with each AutoSync reference, a lock and sync bit * are associated. They are converted to a single sync status value: */ enum hdspe_sync_status { HDSPE_SYNC_STATUS_NO_LOCK = 0, HDSPE_SYNC_STATUS_LOCK = 1, HDSPE_SYNC_STATUS_SYNC = 2, HDSPE_SYNC_STATUS_NOT_AVAILABLE = 3, HDSPE_SYNC_STATUS_COUNT = 4, HDSPE_SYNC_STATUS_INVALID = 5, HDSPE_SYNC_STATUS_FORCE_32BIT = 0xffffffff }; #define HDSPE_SYNC_STATUS_NAME(i) \ (i == HDSPE_SYNC_STATUS_NO_LOCK ? "No Lock" : \ i == HDSPE_SYNC_STATUS_LOCK ? "Lock" : \ i == HDSPE_SYNC_STATUS_SYNC ? "Sync" : \ i == HDSPE_SYNC_STATUS_NOT_AVAILABLE ? "N/A" : \ "???" ) /* Boolean setting / status */ enum hdspe_bool { HDSPE_BOOL_OFF = 0, HDSPE_BOOL_ON = 1, HDSPE_BOOL_COUNT = 2, HDSPE_BOOL_INVALID = 3, HDSPE_BOOL_FORCE_32BIT = 0xffffffff }; #define HDSPE_BOOL_NAME(i) \ (i == HDSPE_BOOL_OFF ? "Off" : \ i == HDSPE_BOOL_ON ? "On" : \ "???") /* MADI input source */ enum hdspe_madi_input { HDSPE_MADI_INPUT_OPTICAL = 0, HDSPE_MADI_INPUT_COAXIAL = 1, HDSPE_MADI_INPUT_COUNT = 2, HDSPE_MADI_INPUT_INVALID = 3, HDSPE_MADI_INPUT_FORCE_32_BIT = 0xffffffff }; #define HDSPE_MADI_INPUT_NAME(i) \ (i == HDSPE_MADI_INPUT_OPTICAL ? "Optical" : \ i == HDSPE_MADI_INPUT_COAXIAL ? "Coaxial" : \ "???") /* Double speed mode. Double wire means that two consecutive * channels in single speed mode are used for transmitting * a double speed audio signal (same as S/MUX for MADI). */ enum hdspe_ds_mode { HDSPE_DS_MODE_SINGLE_WIRE = 0, HDSPE_DS_MODE_DOUBLE_WIRE = 1, HDSPE_DS_MODE_COUNT = 2, HDSPE_DS_MODE_INVALID = 3, HDSPE_DS_MODE_FORCE_32_BIT = 0xffffffff }; #define HDSPE_DS_MODE_NAME(i) \ (i == HDSPE_DS_MODE_SINGLE_WIRE ? "Single Wire" : \ i == HDSPE_DS_MODE_DOUBLE_WIRE ? "Double Wire" : \ "???") /* Quad speed mode. Double wire means that two consecutive channels * at double speed mode are used to transfor a quad speed audio * signal. Quad wire means that four channels in single speed mode * are used to transfer a quad speed audio signal (same as SMUX/4 for MADI). */ enum hdspe_qs_mode { HDSPE_QS_MODE_SINGLE_WIRE = 0, HDSPE_QS_MODE_DOUBLE_WIRE = 1, HDSPE_QS_MODE_QUAD_WIRE = 2, HDSPE_QS_MODE_COUNT = 3, HDSPE_QS_MODE_INVALID = 4, HDSPE_QS_MODE_FORCE_32_BIT = 0xffffffff }; #define HDSPE_QS_MODE_NAME(i) \ (i == HDSPE_QS_MODE_SINGLE_WIRE ? "Single Wire" : \ i == HDSPE_QS_MODE_DOUBLE_WIRE ? "Double Wire" : \ i == HDSPE_QS_MODE_QUAD_WIRE ? "Quad Wire" : \ "???") /* RayDAT / AIO / AIO Pro S/PDIF input source */ enum hdspe_raio_spdif_input { HDSPE_RAIO_SPDIF_INPUT_OPTICAL = 0, HDSPE_RAIO_SPDIF_INPUT_COAXIAL = 1, HDSPE_RAIO_SPDIF_INPUT_INTERNAL = 2, HDSPE_RAIO_SPDIF_INPUT_COUNT = 3, HDSPE_RAIO_SPDIF_INPUT_INVALID = 4, HDSPE_RAIO_SPDIF_INPUT_FORCE_32BIT = 0xffffffff }; #define HDSPE_RAIO_SPDIF_INPUT_NAME(i) \ (i == HDSPE_RAIO_SPDIF_INPUT_OPTICAL ? "Optical" : \ i == HDSPE_RAIO_SPDIF_INPUT_COAXIAL ? "Coaxial" : \ i == HDSPE_RAIO_SPDIF_INPUT_INTERNAL ? "Internal" : \ "???") /* AIO Input / Output / Phones levels */ enum hdspe_aio_level { HDSPE_AIO_LEVEL_HI_GAIN = 0, HDSPE_AIO_LEVEL_PLUS_4_DBU = 1, HDSPE_AIO_LEVEL_MINUS_10_DBV = 2, HDSPE_AIO_LEVEL_COUNT = 3, HDSPE_AIO_LEVEL_INVALID = 4, HDSPE_AIO_LEVEL_FORCE_32BIT = 0xffffffff }; #define HDSPE_AIO_LEVEL_NAME(i) \ (i == HDSPE_AIO_LEVEL_HI_GAIN ? "Hi Gain" : \ i == HDSPE_AIO_LEVEL_PLUS_4_DBU ? "+4 dBu" : \ i == HDSPE_AIO_LEVEL_MINUS_10_DBV ? "-10 dBV" : \ "???") /* AIO Pro Input levels. Define sensitivity of the input circuitery: * the voltage that causes 0 dBFS digital input. */ enum hdspe_aio_pro_input_level { HDSPE_AIO_PRO_INPUT_LEVEL_PLUS_4_DBU = 0, HDSPE_AIO_PRO_INPUT_LEVEL_PLUS_13_DBU = 1, HDSPE_AIO_PRO_INPUT_LEVEL_PLUS_19_DBU = 2, HDSPE_AIO_PRO_INPUT_LEVEL_PLUS_24_DBU = 3, HDSPE_AIO_PRO_INPUT_LEVEL_COUNT = 4, HDSPE_AIO_PRO_INPUT_LEVEL_INVALID = 5, HDSPE_AIO_PRO_INPUT_LEVEL_FORCE_32BIT = 0xffffffff }; #define HDSPE_AIO_PRO_INPUT_LEVEL_NAME(i) \ (i == HDSPE_AIO_PRO_INPUT_LEVEL_PLUS_4_DBU ? "+4 dBu" : \ i == HDSPE_AIO_PRO_INPUT_LEVEL_PLUS_13_DBU ? "+13 dBu" : \ i == HDSPE_AIO_PRO_INPUT_LEVEL_PLUS_19_DBU ? "+19 dBu" : \ i == HDSPE_AIO_PRO_INPUT_LEVEL_PLUS_24_DBU ? "+24 dBu" : \ "???") /* AIO Pro Output levels. Defines the output voltage caused by * 0 dBFS digital output. */ enum hdspe_aio_pro_output_level { HDSPE_AIO_PRO_OUTPUT_LEVEL_MINUS_2_DBU_RCA = 0, HDSPE_AIO_PRO_OUTPUT_LEVEL_PLUS_4_DBU_RCA = 1, HDSPE_AIO_PRO_OUTPUT_LEVEL_PLUS_13_DBU_RCA = 2, HDSPE_AIO_PRO_OUTPUT_LEVEL_PLUS_19_DBU_RCA = 3, HDSPE_AIO_PRO_OUTPUT_LEVEL_PLUS_4_DBU_XLR = 4, HDSPE_AIO_PRO_OUTPUT_LEVEL_PLUS_13_DBU_XLR = 5, HDSPE_AIO_PRO_OUTPUT_LEVEL_PLUS_19_DBU_XLR = 6, HDSPE_AIO_PRO_OUTPUT_LEVEL_PLUS_24_DBU_XLR = 7, HDSPE_AIO_PRO_OUTPUT_LEVEL_COUNT = 8, HDSPE_AIO_PRO_OUTPUT_LEVEL_INVALID = 9, HDSPE_AIO_PRO_OUTPUT_LEVEL_FORCE_32BIT = 0xffffffff }; #define HDSPE_AIO_PRO_OUTPUT_LEVEL_NAME(i) \ (i == HDSPE_AIO_PRO_OUTPUT_LEVEL_MINUS_2_DBU_RCA ? "-2 dBu RCA" : \ i == HDSPE_AIO_PRO_OUTPUT_LEVEL_PLUS_4_DBU_RCA ? "+4 dBu RCA" : \ i == HDSPE_AIO_PRO_OUTPUT_LEVEL_PLUS_13_DBU_RCA ? "+13 dBu RCA" : \ i == HDSPE_AIO_PRO_OUTPUT_LEVEL_PLUS_19_DBU_RCA ? "+19 dBu RCA" : \ i == HDSPE_AIO_PRO_OUTPUT_LEVEL_PLUS_4_DBU_XLR ? "+4 dBu XLR" : \ i == HDSPE_AIO_PRO_OUTPUT_LEVEL_PLUS_13_DBU_XLR ? "+13 dBu XLR" : \ i == HDSPE_AIO_PRO_OUTPUT_LEVEL_PLUS_19_DBU_XLR ? "+19 dBu XLR" : \ i == HDSPE_AIO_PRO_OUTPUT_LEVEL_PLUS_24_DBU_XLR ? "+24 dBu XLR" : \ "???") /* AIO Pro Phones levels */ enum hdspe_aio_pro_phones_level { HDSPE_AIO_PRO_PHONES_LEVEL_LO_POWER = 0, HDSPE_AIO_PRO_PHONES_LEVEL_HI_POWER = 1, HDSPE_AIO_PRO_PHONES_LEVEL_COUNT = 2, HDSPE_AIO_PRO_PHONES_LEVEL_INVALID = 3, HDSPE_AIO_PRO_PHONES_LEVEL_FORCE_32BIT = 0xffffffff }; #define HDSPE_AIO_PRO_PHONES_LEVEL_NAME(i) \ (i == HDSPE_AIO_PRO_PHONES_LEVEL_LO_POWER ? "Lo Power" : \ i == HDSPE_AIO_PRO_PHONES_LEVEL_HI_POWER ? "Hi Power" : \ "???") /* -------------------- IOCTL Peak/RMS Meters -------------------- */ struct hdspe_peak_rms { uint32_t input_peaks[64]; uint32_t playback_peaks[64]; uint32_t output_peaks[64]; uint64_t input_rms[64]; uint64_t playback_rms[64]; uint64_t output_rms[64]; uint8_t speed; /* enum {ss, ds, qs} */ int32_t status2; }; #define SNDRV_HDSPE_IOCTL_GET_PEAK_RMS \ _IOR('H', 0x42, struct hdspe_peak_rms) /* ------------ CONFIG block IOCTL ---------------------- */ struct hdspe_config { unsigned char pref_sync_ref; unsigned char wordclock_sync_check; unsigned char madi_sync_check; unsigned int system_sample_rate; unsigned int autosync_sample_rate; unsigned char system_clock_mode; unsigned char clock_source; unsigned char autosync_ref; unsigned char line_out; unsigned int passthru; unsigned int analog_out; }; #define SNDRV_HDSPE_IOCTL_GET_CONFIG \ _IOR('H', 0x41, struct hdspe_config) /* ------------ Time Code Option (TCO) IOCTL --------------- */ enum hdspe_ltc_frame_rate { HDSPE_LTC_FRAME_RATE_24 =0, HDSPE_LTC_FRAME_RATE_25 =1, HDSPE_LTC_FRAME_RATE_29_97 =2, HDSPE_LTC_FRAME_RATE_30 =3, HDSPE_LTC_FRAME_RATE_COUNT =4, HDSPE_LTC_FRAME_RATE_FORCE_32BIT =0xffffffff }; #define HDSPE_LTC_FRAME_RATE_NAME(i) \ (i == HDSPE_LTC_FRAME_RATE_24 ? "24" : \ i == HDSPE_LTC_FRAME_RATE_25 ? "25" : \ i == HDSPE_LTC_FRAME_RATE_29_97 ? "29.97" : \ i == HDSPE_LTC_FRAME_RATE_30 ? "30" : \ "???") enum hdspe_video_format { HDSPE_VIDEO_FORMAT_NO_VIDEO =0, HDSPE_VIDEO_FORMAT_NTSC =1, HDSPE_VIDEO_FORMAT_PAL =2, HDSPE_VIDEO_FORMAT_COUNT =3, HDSPE_VIDEO_FORMAT_FORCE_32BIT =0xffffffff }; #define HDSPE_VIDEO_FORMAT_NAME(i) \ (i == HDSPE_VIDEO_FORMAT_NO_VIDEO ? "" : \ i == HDSPE_VIDEO_FORMAT_NTSC ? "NTSC" : \ i == HDSPE_VIDEO_FORMAT_PAL ? "PAL" : \ "???") enum hdspe_video_fps { /* TCO firmware version 11 and above */ HDSPE_VIDEO_FPS_NO_VIDEO =0, HDSPE_VIDEO_FPS_23_98 =1, HDSPE_VIDEO_FPS_24 =2, HDSPE_VIDEO_FPS_25 =3, HDSPE_VIDEO_FPS_29_97 =4, HDSPE_VIDEO_FPS_30 =5, HDSPE_VIDEO_FPS_47_95 =6, HDSPE_VIDEO_FPS_48 =7, HDSPE_VIDEO_FPS_50 =8, HDSPE_VIDEO_FPS_59_94 =9, HDSPE_VIDEO_FPS_60 =10, HDSPE_VIDEO_FPS_COUNT =11, HDSPE_VIDEO_FOS_FORCE_32BIT = 0xffffffff }; #define HDSPE_VIDEO_FPS_NAME(i) \ (i == HDSPE_VIDEO_FPS_NO_VIDEO ? "" : \ i == HDSPE_VIDEO_FPS_23_98 ? "23.98" : \ i == HDSPE_VIDEO_FPS_24 ? "24" : \ i == HDSPE_VIDEO_FPS_25 ? "25" : \ i == HDSPE_VIDEO_FPS_29_97 ? "29.97" : \ i == HDSPE_VIDEO_FPS_30 ? "30" : \ i == HDSPE_VIDEO_FPS_47_95 ? "47.95" : \ i == HDSPE_VIDEO_FPS_48 ? "48" : \ i == HDSPE_VIDEO_FPS_50 ? "50" : \ i == HDSPE_VIDEO_FPS_59_94 ? "59.94" : \ i == HDSPE_VIDEO_FPS_60 ? "60" : \ "???") enum hdspe_tco_source { HDSPE_TCO_SOURCE_WCK =0, HDSPE_TCO_SOURCE_VIDEO =1, HDSPE_TCO_SOURCE_LTC =2, HDSPE_TCO_SOURCE_COUNT =3, HDSPE_TCO_SOURCE_FORCE_32BIT =0xffffffff }; #define HDSPE_TCO_SOURCE_NAME(i) \ (i == HDSPE_TCO_SOURCE_WCK ? "Word Clk" : \ i == HDSPE_TCO_SOURCE_VIDEO ? "Video" : \ i == HDSPE_TCO_SOURCE_LTC ? "LTC" : \ "???") enum hdspe_pull { HDSPE_PULL_NONE =0, HDSPE_PULL_UP_0_1 =1, HDSPE_PULL_DOWN_0_1 =2, HDSPE_PULL_UP_4 =3, HDSPE_PULL_DOWN_4 =4, HDSPE_PULL_COUNT =5, HDSPE_PULL_FORCE_32BIT =0xffffffff }; #define HDSPE_PULL_NAME(i) \ (i == HDSPE_PULL_NONE ? "0" : \ i == HDSPE_PULL_UP_0_1 ? "+0.1 %" : \ i == HDSPE_PULL_DOWN_0_1 ? "-0.1 %" : \ i == HDSPE_PULL_UP_4 ? "+4 %" : \ i == HDSPE_PULL_DOWN_4 ? "-4 %" : \ "???") enum hdspe_tco_sample_rate { HDSPE_TCO_SAMPLE_RATE_44_1 =0, HDSPE_TCO_SAMPLE_RATE_48 =1, HDSPE_TCO_SAMPLE_RATE_FROM_APP =2, HDSPE_TCO_SAMPLE_RATE_COUNT =3, HDSPE_TCO_SAMPLE_RATE_FORCE_32BIT = 0xffffffff }; #define HDSPE_TCO_SAMPLE_RATE_NAME(i) \ (i == HDSPE_TCO_SAMPLE_RATE_44_1 ? "44.1 KHz" : \ i == HDSPE_TCO_SAMPLE_RATE_48 ? "48 KHz" : \ i == HDSPE_TCO_SAMPLE_RATE_FROM_APP ? "From App" : \ "???") enum hdspe_wck_conversion { HDSPE_WCK_CONVERSION_1_1 =0, HDSPE_WCK_CONVERSION_44_1_48 =1, HDSPE_WCK_CONVERSION_48_44_1 =2, HDSPE_WCK_CONVERSION_COUNT =3, HDSPE_WCK_CONVERSION_FORCE_32BIT =0xffffffff }; #define HDSPE_WCK_CONVERSION_NAME(i) \ (i == HDSPE_WCK_CONVERSION_1_1 ? "1:1" : \ i == HDSPE_WCK_CONVERSION_44_1_48 ? "44.1 KHz -> 48 KHz" : \ i == HDSPE_WCK_CONVERSION_48_44_1 ? "48 KHz -> 44.1 KHz" : \ "???") #ifdef NEVER #pragma scalar_storage_order little-endian /* Raw 32-bit HDSPe LTC code - does not include user bits (TCO doesn't * report user bits) */ struct hdspe_raw_ltc { uint32_t frame_units : 4, frame_tens : 2, drop_frame : 1, color_frame : 1, sec_units : 4, sec_tens : 3, flag1 : 1, min_units : 4, min_tens : 3, flag2 : 1, hour_units : 4, hour_tens : 2, clock_flag : 1, flag3 : 1; }; /* Generic 64-bit LTC code including user bits (all 0 for HDSPe TCO), * as returned by the "TCO LTC In" ALSA control. */ struct hdspe_ltc { uint64_t frame_units : 4, user1 : 4, frame_tens : 2, drop_frame : 1, color_frame : 1, user2 : 4, sec_units : 4, user3 : 4, sec_tens : 3, flag1 : 1, user4 : 4, min_units : 4, user5 : 4, min_tens : 3, flag2 : 1, user6 : 4, hour_units : 4, user7 : 4, hour_tens : 2, clock_flag : 1, flag3 : 1, user8 : 4; }; #pragma scalar_storage_order default #endif /*NEVER*/ struct hdspe_tco_status { uint32_t version; uint32_t ltc_in; uint32_t ltc_in_offset; enum hdspe_bool tco_lock; enum hdspe_bool ltc_valid; enum hdspe_ltc_frame_rate ltc_in_fps; enum hdspe_bool ltc_in_drop; enum hdspe_video_format video; enum hdspe_bool wck_valid; enum hdspe_speed wck_speed; enum hdspe_tco_source input; enum hdspe_ltc_frame_rate ltc_fps; enum hdspe_bool ltc_drop; enum hdspe_tco_sample_rate sample_rate; enum hdspe_pull pull; enum hdspe_wck_conversion wck_conversion; enum hdspe_bool term; // LTC output control enum hdspe_bool ltc_run; enum hdspe_bool ltc_flywheel; // HDSPE_VERSION 3: uint32_t fw_version; uint32_t fs_period_counter; enum hdspe_video_fps video_in_fps; // if fw_version >= 11 enum hdspe_speed wck_out_speed; }; #define SNDRV_HDSPE_IOCTL_GET_LTC _IOR('H', 0x46, struct hdspe_tco_status) /* ------------ STATUS block IOCTL ---------------------- */ /* * The status data reflects the device's current state * as determined by the card's configuration and * connection status. */ /* Device status */ struct hdspe_status { // API version (this structs size and layout may change with version) uint32_t version; // Exact system sample rate as a fraction of a 64-bit unsigned // integer over a 32-bit unsigned integer. The numerator is // the cards frequency constant adapted to the current speed // mode. The denominator is the content of the DDS hardware // status register. uint32_t sample_rate_denominator; uint64_t sample_rate_numerator; // Exact internal sample rate is sample_rate_numerator / // internal_sample_rate_denominator. internal_sample_rate_denominator // is the content of the DDS hardware control register. uint32_t internal_sample_rate_denominator; // Period buffer size in number of samples. uint32_t buffer_size; // Whether the card is running or not, and what processes // are capturing or playing back on the card (-1 if none). // Frequency shall not be changed by means of the snd_ctls // if running. enum hdspe_bool running; pid_t capture_pid; pid_t playback_pid; // Device clock mode: AutoSync (0) or Master (1). enum hdspe_clock_mode clock_mode; // Frequency class of the internal clock. The internal clock // is used when in Master clock mode, or no valid AutoSync // reference is detected. enum hdspe_freq internal_freq; // Preferred clock source for AutoSync clock mode. enum hdspe_clock_source preferred_ref; // Active clock source ("AutoSync reference"). enum hdspe_clock_source autosync_ref; // AutoSync clock source sync status. enum hdspe_sync_status sync[HDSPE_CLOCK_SOURCE_COUNT]; // AutoSync clock source frequency class. enum hdspe_freq freq[HDSPE_CLOCK_SOURCE_COUNT]; // Frequency class of the active AutoSync reference, if any. Used // if in AutoSync clock mode and a valid AutoSync reference is // available (autosync_ref != HDSPE_SYNC_INTERN). enum hdspe_freq external_freq; // Single-speed word clock output. If false, word clock output // is at the systems sample rate. If true, word clock output // frequency is the systems sample rate reduced to single speed. enum hdspe_bool wck48; // Current speed mode. enum hdspe_speed speed_mode; // Clear track markers. Affects channel status and track marker // bits in AES, S/PDIF, ADAT and MADI input. enum hdspe_bool clr_tms; // union hdspe_card_specific_status { // MADI specific status struct hdspe_status_madi { enum hdspe_madi_input input_select; enum hdspe_bool auto_select; enum hdspe_bool tx_64ch; enum hdspe_bool smux; enum hdspe_madi_input input_source; enum hdspe_bool rx_64ch; } madi; // AES specific status struct hdspe_status_aes { enum hdspe_bool pro; enum hdspe_bool emp; enum hdspe_bool dolby; enum hdspe_bool lineout; enum hdspe_ds_mode ds_mode; enum hdspe_qs_mode qs_mode; uint32_t aes_mode; } aes; // RayDAT / AIO / AIO Pro specific status struct hdspe_status_raio { // AO4S Output Audio Extension Board presence (AIO only) enum hdspe_bool aebo; // AI4S Input Audio Extension Board presence (AIO only) enum hdspe_bool aebi; // ADAT internal (for old AEB / TEB expansion boards) enum hdspe_bool aeb1, aeb2; // aeb2 is RayDAT only // S/PDIF input source. enum hdspe_raio_spdif_input spdif_in; // S/PDIF optical output. enum hdspe_bool spdif_opt; // S/PDIF professional format. enum hdspe_bool spdif_pro; union { // AIO analog I/O levels struct { enum hdspe_aio_level input_level; enum hdspe_aio_level output_level; enum hdspe_aio_level phones_level; enum hdspe_bool xlr; } aio; // AIO Pro analog I/O levels struct { enum hdspe_aio_pro_input_level input_level; enum hdspe_aio_pro_output_level output_level; enum hdspe_aio_pro_phones_level phones_level; uint32_t reserved; } aio_pro; }; } raio; // }; }; #define SNDRV_HDSPE_IOCTL_GET_STATUS \ _IOR('H', 0x49, struct hdspe_status) /* 47 is hdspm status - incompatible */ /* ------------- Card information --------------- */ /* * DEPRECATED (hdspm compatible) */ #define HDSPE_ADDON_TCO 1 struct hdspe_version { __u8 card_type; /* enum hdspe_io_type */ char cardname[20]; unsigned int serial; unsigned short firmware_rev; int addons; }; #define SNDRV_HDSPE_IOCTL_GET_VERSION _IOR('H', 0x48, struct hdspe_version) /* * Use the following in new developments. */ #define HDSPE_EXPANSION_TCO 0x01 #define HDSPE_EXPANSION_AI4S 0x02 #define HDSPE_EXPANSION_AO4S 0x04 #ifndef PCI_VENDOR_ID_XILINX #define PCI_VENDOR_ID_XILINX 0x10ee #endif #ifndef PCI_VENDOR_ID_RME #define PCI_VENDOR_ID_RME 0x1d18 #endif struct hdspe_card_info { // API version (this structs size and layout may change with version) uint32_t version; enum hdspe_io_type card_type; uint32_t serial; /* serial nr */ uint32_t fw_rev; // firmware revision uint32_t fw_build; // firmware build uint32_t irq; uint64_t port; uint32_t vendor_id; // PCI vendor ID: Xilinx or RME uint32_t expansion; }; #define SNDRV_HDSPE_IOCTL_GET_CARD_INFO \ _IOR('H', 0x45, struct hdspe_status) /* ------------- Matrix Mixer IOCTL --------------- */ /* MADI mixer: 64inputs+64playback in 64outputs = 8192 => *4Byte = * 32768 Bytes */ /* Organisation is 64 channelfader in a continuous memory block, * equivalent to hardware definition, maybe for future feature of mmap of * them. * Each of 64 outputs has 64 input faders and 64 software playback faders. * Input in to output out fader is mixer.ch[out].in[in]. * Software playback pb to output out fader is mixer.ch[out].pb[pb] */ /* Mixer Values */ #define HDSPE_UNITY_GAIN 32768 /* = 65536/2 */ #define HDSPE_MINUS_INFINITY_GAIN 0 #define HDSPE_MIXER_CHANNELS HDSPE_MAX_CHANNELS struct hdspe_channelfader { uint32_t in[HDSPE_MIXER_CHANNELS]; uint32_t pb[HDSPE_MIXER_CHANNELS]; }; struct hdspe_mixer { struct hdspe_channelfader ch[HDSPE_MIXER_CHANNELS]; }; struct hdspe_mixer_ioctl { struct hdspe_mixer *mixer; }; /* use indirect access due to the limit of ioctl bit size */ #define SNDRV_HDSPE_IOCTL_GET_MIXER _IOR('H', 0x44, struct hdspe_mixer_ioctl) /* typedefs for compatibility to user-space */ typedef struct hdspe_peak_rms hdspe_peak_rms_t; typedef struct hdspe_config_info hdspe_config_info_t; typedef struct hdspe_version hdspe_version_t; typedef struct hdspe_channelfader snd_hdspe_channelfader_t; typedef struct hdspe_mixer hdspe_mixer_t; #endif /* __SOUND_HDSPE_H */ snd-hdspe-1.0.2/sound/pci/hdsp/hdspe/hdspe_aes.c000066400000000000000000000422541506037051300214510ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later /** * hdspe_aes.c * @brief RME HDSPe AES driver methods. * * 20210728,1125 - Philippe.Bekaert@uhasselt.be * * Based on earlier work of the other MODULE_AUTHORs, * information kindly made available by RME (www.rme-audio.com), * and hardware kindly made available by Amptec Belgium (www.amptec.be). */ // TODO: // - Double Wire / Quad Wire mode: fewer channels. // - FormatAC3 // - status2 AES mode ??? // - WCLK termination #include "hdspe.h" #include "hdspe_core.h" /* Map AES WR_CONTROL / RD_STATUS0 sync ref 4-bit code to hdspe_clock_source * enum: identity mapping except for the unused codes. */ static enum hdspe_clock_source aes_autosync_ref[16] = { HDSPE_CLOCK_SOURCE_WORD, HDSPE_CLOCK_SOURCE_AES1, HDSPE_CLOCK_SOURCE_AES2, HDSPE_CLOCK_SOURCE_AES3, HDSPE_CLOCK_SOURCE_AES4, HDSPE_CLOCK_SOURCE_AES5, HDSPE_CLOCK_SOURCE_AES6, HDSPE_CLOCK_SOURCE_AES7, HDSPE_CLOCK_SOURCE_AES8, HDSPE_CLOCK_SOURCE_TCO, HDSPE_CLOCK_SOURCE_SYNC_IN, HDSPE_CLOCK_SOURCE_INTERN, // unused codes HDSPE_CLOCK_SOURCE_INTERN, HDSPE_CLOCK_SOURCE_INTERN, HDSPE_CLOCK_SOURCE_INTERN, HDSPE_CLOCK_SOURCE_INTERN // master clock mode }; const char* const hdspe_aes_clock_source_names[HDSPE_CLOCK_SOURCE_COUNT] = { HDSPE_CLOCK_SOURCE_NAME(HDSPE_AES, 0), HDSPE_CLOCK_SOURCE_NAME(HDSPE_AES, 1), HDSPE_CLOCK_SOURCE_NAME(HDSPE_AES, 2), HDSPE_CLOCK_SOURCE_NAME(HDSPE_AES, 3), HDSPE_CLOCK_SOURCE_NAME(HDSPE_AES, 4), HDSPE_CLOCK_SOURCE_NAME(HDSPE_AES, 5), HDSPE_CLOCK_SOURCE_NAME(HDSPE_AES, 6), HDSPE_CLOCK_SOURCE_NAME(HDSPE_AES, 7), HDSPE_CLOCK_SOURCE_NAME(HDSPE_AES, 8), HDSPE_CLOCK_SOURCE_NAME(HDSPE_AES, 9), HDSPE_CLOCK_SOURCE_NAME(HDSPE_AES, 10), HDSPE_CLOCK_SOURCE_NAME(HDSPE_AES, 11), HDSPE_CLOCK_SOURCE_NAME(HDSPE_AES, 12), HDSPE_CLOCK_SOURCE_NAME(HDSPE_AES, 13), HDSPE_CLOCK_SOURCE_NAME(HDSPE_AES, 14), HDSPE_CLOCK_SOURCE_NAME(HDSPE_AES, 15), }; /* Number of channels for different Speed Modes */ #define AES_CHANNELS 16 /* port names */ static const char * const texts_ports_aes[] = { "AES.1", "AES.2", "AES.3", "AES.4", "AES.5", "AES.6", "AES.7", "AES.8", "AES.9.", "AES.10", "AES.11", "AES.12", "AES.13", "AES.14", "AES.15", "AES.16" }; /* These tables map the ALSA channels 1..N to the channels that we need to use in order to find the relevant channel buffer. RME refers to this kind of mapping as between "the ADAT channel and the DMA channel." We index it using the logical audio channel, and the value is the DMA channel (i.e. channel buffer number) where the data for that channel can be read/written from/to. */ static const char channel_map_aes[HDSPE_MAX_CHANNELS] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; #ifdef CONFIG_SND_DEBUG /* WR_CONTROL register bit names for AES card */ static const char* const aes_control_bitNames[32] = { "START", "LAT_0", "LAT_1", "LAT_2", "Master", "IE_AUDIO", "freq0", "freq1", "freq2", "PRO", "EMP", "Dolby", "?12", "SyncRef2", "?14", "?15", "SyncRef0", "SyncRef1", "SMUX", "CLR_TMS", "WCK48", "IEN2", "IEN0", "IEN1", "LineOut", "SyncRef3", "DS_DoubleWire", "QS_DoubleWire", "QS_QuadWire", "?29", "AES_float_format", "freq3" }; /* RD_STATUS2 register bit names for AES */ static const char* const aes_status2_bitNames[32] = { "lock_aes8", "lock_aes7", "lock_aes6", "lock_aes5", "lock_aes4", "lock_aes3", "lock_aes2", "lock_aes1", "sync_aes8", "sync_aes7", "sync_aes6", "sync_aes5", "sync_aes4", "sync_aes3", "sync_aes2", "sync_aes1", "aes_mode0", "aes_mode1", "aes_mode2", "aes_mode3", "sync_in_lock", "sync_in_sync", "sync_in_freq0", "sync_in_freq1", "sync_in_freq2", "sync_in_freq3", "?26", "?27", "?28", "?29", "?30", "?31" }; #endif /*CONFIG_SND_DEBUG*/ static void hdspe_aes_read_status(struct hdspe* hdspe, struct hdspe_status* status) { int i; struct hdspe_control_reg_aes control = hdspe->reg.control.aes; struct hdspe_status0_reg_aes status0 = hdspe_read_status0(hdspe).aes; struct hdspe_status2_reg_aes status2 = hdspe_read_status2(hdspe).aes; u32 fbits = hdspe_read_fbits(hdspe); status->version = HDSPE_VERSION; hdspe_read_sample_rate_status(hdspe, status); status->clock_mode = control.Master; status->internal_freq = hdspe_internal_freq(hdspe); status->speed_mode = hdspe_speed_mode(hdspe); status->preferred_ref = (control.SyncRef3 << 3) | (control.SyncRef2 << 2) | (control.SyncRef1 << 1) | (control.SyncRef0 << 0); status->autosync_ref = status0.sync_ref; hdspe_set_sync_source(status, HDSPE_CLOCK_SOURCE_WORD, status0.wc_freq, status0.wc_lock, status0.wc_sync, true); for (i = 0; i < 8; i ++) { hdspe_set_sync_source(status, HDSPE_CLOCK_SOURCE_AES1+i, HDSPE_FBITS_FREQ(fbits, i), status2.lock & (0x80 >> i), status2.sync & (0x80 >> i), true); } hdspe_set_sync_source(status, HDSPE_CLOCK_SOURCE_TCO, status0.tco_freq, status0.tco_lock, status0.tco_sync, status0.tco_detect); hdspe_set_sync_source(status, HDSPE_CLOCK_SOURCE_SYNC_IN, status2.sync_in_freq, status2.sync_in_lock, status2.sync_in_sync, true); for (i = HDSPE_CLOCK_SOURCE_SYNC_IN+1; i < HDSPE_CLOCK_SOURCE_COUNT; i++) { hdspe_set_sync_source(status, i, 0, false, false, false); } status->external_freq = hdspe_speed_adapt( status->freq[status->autosync_ref], status->speed_mode); status->wck48 = control.WCK48; status->clr_tms = control.CLR_TMS; // AES specific settings status->aes.pro = control.PRO; status->aes.emp = control.EMP; status->aes.dolby = control.Dolby; status->aes.lineout = control.LineOut; status->aes.ds_mode = control.ds_mode; status->aes.qs_mode = control.qs_mode; // AES specific status status->aes.aes_mode = status2.aes_mode; } static void hdspe_aes_set_float_format(struct hdspe* hdspe, bool val) { hdspe->reg.control.aes.FloatFmt = val; hdspe_write_control(hdspe); } static bool hdspe_aes_get_float_format(struct hdspe* hdspe) { return hdspe->reg.control.aes.FloatFmt; } static enum hdspe_clock_mode hdspe_aes_get_clock_mode(struct hdspe *hdspe) { return hdspe->reg.control.aes.Master; } static void hdspe_aes_set_clock_mode(struct hdspe* hdspe, enum hdspe_clock_mode master) { hdspe->reg.control.aes.Master = master; hdspe_write_control(hdspe); } static enum hdspe_clock_source hdspe_aes_get_preferred_sync_ref( struct hdspe* hdspe) { struct hdspe_control_reg_aes control = hdspe->reg.control.aes; return aes_autosync_ref[(control.SyncRef3 << 3) | (control.SyncRef2 << 2) | (control.SyncRef1 << 1) | (control.SyncRef0 << 0)]; } static void hdspe_aes_set_preferred_sync_ref(struct hdspe* hdspe, enum hdspe_clock_source ref) { struct hdspe_control_reg_aes* control = &hdspe->reg.control.aes; if (aes_autosync_ref[ref] == HDSPE_CLOCK_SOURCE_INTERN) ref = 0; control->SyncRef3 = (ref>>3)&1; control->SyncRef2 = (ref>>2)&1; control->SyncRef1 = (ref>>1)&1; control->SyncRef0 = (ref>>0)&1; hdspe_write_control(hdspe); } static enum hdspe_clock_source hdspe_aes_get_autosync_ref(struct hdspe* hdspe) { return aes_autosync_ref[hdspe_read_status0(hdspe).aes.sync_ref]; } static void hdspe_aes_proc_read(struct snd_info_entry * entry, struct snd_info_buffer *buffer) { struct hdspe *hdspe = entry->private_data; struct hdspe_status s; hdspe_proc_read_common(buffer, hdspe, &s); snd_iprintf(buffer, "Professional\t\t: %d %s\n", s.aes.pro, HDSPE_BOOL_NAME(s.aes.pro)); snd_iprintf(buffer, "Emphasis\t\t: %d %s\n", s.aes.emp, HDSPE_BOOL_NAME(s.aes.emp)); snd_iprintf(buffer, "Non-audio\t\t: %d %s\n", s.aes.dolby, HDSPE_BOOL_NAME(s.aes.dolby)); snd_iprintf(buffer, "Line Out\t\t: %d %s\n", s.aes.lineout, HDSPE_BOOL_NAME(s.aes.lineout)); snd_iprintf(buffer, "Double speed mode\t: %d %s\n", s.aes.ds_mode, HDSPE_DS_MODE_NAME(s.aes.ds_mode)); snd_iprintf(buffer, "Quad speed mode\t\t: %d %s\n", s.aes.qs_mode, HDSPE_QS_MODE_NAME(s.aes.qs_mode)); snd_iprintf(buffer, "AES mode\t\t: %d %01x\n", s.aes.aes_mode, s.aes.aes_mode); snd_iprintf(buffer, "\n"); IPRINTREG(buffer, "CONTROL", hdspe->reg.control.raw, aes_control_bitNames); { union hdspe_status2_reg status2 = hdspe_read_status2(hdspe); u32 fbits = hdspe_read_fbits(hdspe); IPRINTREG(buffer, "STATUS2", status2.raw, aes_status2_bitNames); hdspe_iprint_fbits(buffer, "FBITS", fbits); } hdspe_proc_read_common2(buffer, hdspe, &s); #ifdef OLDSTUFF struct hdspe *hdspe = entry->private_data; unsigned int status; unsigned int status2; unsigned int timecode; unsigned int wcLock, wcSync; int pref_syncref; char *autosync_ref; int x; status = hdspe_read(hdspe, HDSPE_RD_STATUS0); status2 = hdspe_read(hdspe, HDSPE_RD_STATUS2); timecode = hdspe_read(hdspe, HDSPE_RD_FBITS); snd_iprintf(buffer, "%s (Card #%d) Rev.%x\n", hdspe->card_name, hdspe->card->number + 1, hdspe->firmware_rev); snd_iprintf(buffer, "IRQ: %d Registers bus: 0x%lx VM: 0x%lx\n", hdspe->irq, hdspe->port, (unsigned long)hdspe->iobase); snd_iprintf(buffer, "--- System ---\n"); snd_iprintf(buffer, "IRQ Pending: Audio=%d, MIDI0=%d, MIDI1=%d, IRQcount=%d\n", status & HDSPE_audioIRQPending, (status & HDSPE_midi0IRQPending) ? 1 : 0, (status & HDSPE_midi1IRQPending) ? 1 : 0, hdspe->irq_count); snd_iprintf(buffer, "HW pointer: id = %d, rawptr = %d (%d->%d) " "estimated= %ld (bytes)\n", ((status & HDSPE_BufferID) ? 1 : 0), (status & HDSPE_BufferPositionMask), (status & HDSPE_BufferPositionMask) % (2 * (int)hdspe->period_bytes), ((status & HDSPE_BufferPositionMask) - 64) % (2 * (int)hdspe->period_bytes), (long) hdspe_hw_pointer(hdspe) * 4); snd_iprintf(buffer, "MIDI FIFO: Out1=0x%x, Out2=0x%x, In1=0x%x, In2=0x%x \n", hdspe_read(hdspe, HDSPE_midiStatusOut0) & 0xFF, hdspe_read(hdspe, HDSPE_midiStatusOut1) & 0xFF, hdspe_read(hdspe, HDSPE_midiStatusIn0) & 0xFF, hdspe_read(hdspe, HDSPE_midiStatusIn1) & 0xFF); snd_iprintf(buffer, "MIDIoverMADI FIFO: In=0x%x, Out=0x%x \n", hdspe_read(hdspe, HDSPE_midiStatusIn2) & 0xFF, hdspe_read(hdspe, HDSPE_midiStatusOut2) & 0xFF); snd_iprintf(buffer, "Register: ctrl1=0x%x, status0=0x%x, " "status2=0x%x\n", hdspe->reg.control.raw, status, status2); snd_iprintf(buffer, "--- Settings ---\n"); x = hdspe_period_size(hdspe); snd_iprintf(buffer, "Size (Latency): %d samples (2 periods of %lu bytes)\n", x, (unsigned long) hdspe->period_bytes); snd_iprintf(buffer, "Line out: %s\n", (hdspe-> reg.control.raw & HDSPE_LineOut) ? "on " : "off"); snd_iprintf(buffer, "ClearTrackMarker %s, Emphasis %s, Dolby %s\n", (hdspe-> reg.control.raw & HDSPE_clr_tms) ? "on" : "off", (hdspe-> reg.control.raw & HDSPE_Emphasis) ? "on" : "off", (hdspe-> reg.control.raw & HDSPE_Dolby) ? "on" : "off"); pref_syncref = hdspe_aes_get_preferred_sync_ref(hdspe); snd_iprintf(buffer, "Preferred Sync Reference: %s\n", HDSPE_CLOCK_SOURCE_NAME(HDSPE_AES, pref_syncref)); snd_iprintf(buffer, "System Clock Frequency: %d\n", hdspe->system_sample_rate); snd_iprintf(buffer, "Double speed: %s\n", hdspe->reg.control.raw & HDSPE_DS_DoubleWire? "Double wire" : "Single wire"); snd_iprintf(buffer, "Quad speed: %s\n", hdspe->reg.control.raw & HDSPE_QS_DoubleWire? "Double wire" : hdspe->reg.control.raw & HDSPE_QS_QuadWire? "Quad wire" : "Single wire"); snd_iprintf(buffer, "--- Status:\n"); wcLock = status & HDSPE_AES_wcLock; wcSync = wcLock && (status & HDSPE_AES_wcSync); snd_iprintf(buffer, "Word: %s Frequency: %d\n", (wcLock) ? (wcSync ? "Sync " : "Lock ") : "No Lock", hdspe_freq_sample_rate(HDSPE_AES_wclk_freq(status))); for (x = 0; x < 8; x++) { snd_iprintf(buffer, "AES%d: %s Frequency: %d\n", x+1, (status2 & (HDSPE_LockAES >> x)) ? "Sync " : "No Lock", hdspe_freq_sample_rate((timecode >> (4*x)) & 0xF)); } switch (hdspe_aes_get_autosync_ref(hdspe)) { case HDSPE_CLOCK_SOURCE_INTERN: autosync_ref = "None"; break; case HDSPE_CLOCK_SOURCE_WORD: autosync_ref = "Word Clock"; break; case HDSPE_CLOCK_SOURCE_AES1: autosync_ref = "AES1"; break; case HDSPE_CLOCK_SOURCE_AES2: autosync_ref = "AES2"; break; case HDSPE_CLOCK_SOURCE_AES3: autosync_ref = "AES3"; break; case HDSPE_CLOCK_SOURCE_AES4: autosync_ref = "AES4"; break; case HDSPE_CLOCK_SOURCE_AES5: autosync_ref = "AES5"; break; case HDSPE_CLOCK_SOURCE_AES6: autosync_ref = "AES6"; break; case HDSPE_CLOCK_SOURCE_AES7: autosync_ref = "AES7"; break; case HDSPE_CLOCK_SOURCE_AES8: autosync_ref = "AES8"; break; case HDSPE_CLOCK_SOURCE_TCO: autosync_ref = "TCO"; break; case HDSPE_CLOCK_SOURCE_SYNC_IN: autosync_ref = "Sync In"; break; default: autosync_ref = "---"; break; } snd_iprintf(buffer, "AutoSync ref = %s\n", autosync_ref); snd_iprintf(buffer, "\n"); #endif /*OLDSTUFF*/ } static const struct hdspe_methods hdspe_aes_methods = { .get_card_info = hdspe_get_card_info, .read_status = hdspe_aes_read_status, .get_float_format = hdspe_aes_get_float_format, .set_float_format = hdspe_aes_set_float_format, .read_proc = hdspe_aes_proc_read, #ifdef OLDSTUFF .get_freq = hdspe_aes_get_freq, .get_external_freq = hdspe_aes_get_external_freq, #endif /*OLDSTUFF*/ .get_autosync_ref = hdspe_aes_get_autosync_ref, .get_clock_mode = hdspe_aes_get_clock_mode, .set_clock_mode = hdspe_aes_set_clock_mode, .get_pref_sync_ref = hdspe_aes_get_preferred_sync_ref, .set_pref_sync_ref = hdspe_aes_set_preferred_sync_ref, #ifdef OLDSTUFF .get_sync_status = hdspe_aes_get_sync_status, .has_status_changed = hdspe_aes_has_status_changed #endif /*OLDSTUFF*/ }; static const struct hdspe_tables hdspe_aes_tables = { .ss_in_channels = AES_CHANNELS, .ss_out_channels = AES_CHANNELS, .ds_in_channels = AES_CHANNELS, .ds_out_channels = AES_CHANNELS, .qs_in_channels = AES_CHANNELS, .qs_out_channels = AES_CHANNELS, .channel_map_in_ss = channel_map_aes, .channel_map_out_ss = channel_map_aes, .channel_map_in_ds = channel_map_aes, .channel_map_out_ds = channel_map_aes, .channel_map_in_qs = channel_map_aes, .channel_map_out_qs = channel_map_aes, .port_names_in_ss = texts_ports_aes, .port_names_out_ss = texts_ports_aes, .port_names_in_ds = texts_ports_aes, .port_names_out_ds = texts_ports_aes, .port_names_in_qs = texts_ports_aes, .port_names_out_qs = texts_ports_aes, .clock_source_names = hdspe_aes_clock_source_names }; static struct hdspe_midi hdspe_aes_midi_ports[3] = { {.portname = "MIDI 1", .dataIn = HDSPE_midiDataIn0, .statusIn = HDSPE_midiStatusIn0, .dataOut = HDSPE_midiDataOut0, .statusOut = HDSPE_midiStatusOut0, .ie = HDSPE_Midi0InterruptEnable, .irq = HDSPE_midi0IRQPending, }, {.portname = "MIDI 2", .dataIn = HDSPE_midiDataIn1, .statusIn = HDSPE_midiStatusIn1, .dataOut = HDSPE_midiDataOut1, .statusOut = HDSPE_midiStatusOut1, .ie = HDSPE_Midi1InterruptEnable, .irq = HDSPE_midi1IRQPending, }, {.portname = "MTC", .dataIn = HDSPE_midiDataIn2, .statusIn = HDSPE_midiStatusIn2, .dataOut = -1, .statusOut = -1, .ie = HDSPE_Midi2InterruptEnable, .irq = HDSPE_midi2IRQPendingAES, } }; int hdspe_init_aes(struct hdspe* hdspe) { hdspe->reg.control.aes.Master = true; // hdspe->reg.control.aes.SyncRef0 = 1; // preferred sync is AES1 hdspe->reg.control.aes.PRO = true; // Professional mode #ifdef FROM_WIN_DRIVER // TODO if (deviceExtension->Emphasis) deviceExtension->Register |= EMP; else deviceExtension->Register &= ~EMP; if (deviceExtension->Professional) deviceExtension->Register |= PRO; else deviceExtension->Register &= ~PRO; if (deviceExtension->NonAudio) deviceExtension->Register |= Dolby; else deviceExtension->Register &= ~Dolby; if (deviceExtension->DoubleSpeed) deviceExtension->Register |= DS_DoubleWire; else deviceExtension->Register &= ~DS_DoubleWire; deviceExtension->Register &= ~(QS_DoubleWire+QS_QuadWire); switch (deviceExtension->QuadSpeed) { case 1: deviceExtension->Register |= QS_DoubleWire; break; case 2: deviceExtension->Register |= QS_QuadWire; break; } if (deviceExtension->Frame) deviceExtension->Register |= MADI_WCK48; else deviceExtension->Register &= ~MADI_WCK48; if (deviceExtension->InputSource) deviceExtension->Register |= WCK_TERM; else deviceExtension->Register &= ~WCK_TERM; if (deviceExtension->FormatAC3 && deviceExtension->DeviceType != kRPM && deviceExtension->DeviceType != kHDSP_MADI) { deviceExtension->Register |= Dolby; deviceExtension->Register &= ~LineOut; } #endif hdspe_write_control(hdspe); hdspe->m = hdspe_aes_methods; hdspe->card_name = "RME AES32"; hdspe_init_midi(hdspe, 2 + (hdspe->tco ? 1 : 0), hdspe_aes_midi_ports); hdspe->t = hdspe_aes_tables; hdspe_init_autosync_tables( hdspe, ARRAY_SIZE(aes_autosync_ref), aes_autosync_ref); return 0; } void hdspe_terminate_aes(struct hdspe* hdspe) { #ifdef FROM_WIN_DRIVER // TODO if (!deviceExtension->NonAudio && deviceExtension->DeviceType != kRPM && deviceExtension->DeviceType != kHDSP_MADI) deviceExtension->Register &= ~Dolby; if (deviceExtension->FormatAC3) deviceExtension->Register |= LineOut; #endif } snd-hdspe-1.0.2/sound/pci/hdsp/hdspe/hdspe_common.c000066400000000000000000000307501506037051300221670ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later /** * hdspe_common.c * @brief RME HDSPe cards common driver methods. * * 20210727,28,29 - Philippe.Bekaert@uhasselt.be * * Based on earlier work of the other MODULE_AUTHORS, * information kindly made available by RME (www.rme-audio.com), * and hardware kindly made available by Amptec Belgium (www.amptec.be). */ #include "hdspe.h" #include "hdspe_core.h" #include #ifdef FROM_WIN_DRIVER //TODO // Loopback void hwRecordOutput(PDEVICE_EXTENSION deviceExtension, int channel, int value) { TRACE(TL_TRACE, ("hwRecordOutput: channel=%d, value=%d, numChannels=%d\n", channel, value, deviceExtension->NumChannels)); if (channel < deviceExtension->NumChannels) { deviceExtension->RecordOutput[channel] = value; if (deviceExtension->DeviceType < kHDSP_AES) { WriteRegister(deviceExtension, RECORD_OUTPUT+channel, value); } else { TRACE(TL_TRACE, ("...hwRecordOutput MADI\n")); WriteRegister(deviceExtension, MADI_RECORD_OUTPUT+channel, value); } } } #endif u32 hdspe_freq_sample_rate(enum hdspe_freq f) { static u32 rates[HDSPE_FREQ_COUNT] = { HDSPE_FREQ_SAMPLE_RATE(0), HDSPE_FREQ_SAMPLE_RATE(1), HDSPE_FREQ_SAMPLE_RATE(2), HDSPE_FREQ_SAMPLE_RATE(3), HDSPE_FREQ_SAMPLE_RATE(4), HDSPE_FREQ_SAMPLE_RATE(5), HDSPE_FREQ_SAMPLE_RATE(6), HDSPE_FREQ_SAMPLE_RATE(7), HDSPE_FREQ_SAMPLE_RATE(8), }; return (f >= 0 && f < HDSPE_FREQ_COUNT) ? rates[f] : 0; } /* Get the speed mode reflecting the sample rate f */ static enum hdspe_speed hdspe_sample_rate_speed_mode(u32 rate) { if (rate < 56000) return HDSPE_SPEED_SINGLE; else if (rate < 112000) return HDSPE_SPEED_DOUBLE; else return HDSPE_SPEED_QUAD; } /* Get the speed mode encoded in the frequency class */ static enum hdspe_speed hdspe_freq_speed(enum hdspe_freq f) { switch (f) { case 1: case 2: case 3: return HDSPE_SPEED_SINGLE; case 4: case 5: case 6: return HDSPE_SPEED_DOUBLE; case 7: case 8: case 9: return HDSPE_SPEED_QUAD; default: snd_BUG(); return HDSPE_SPEED_INVALID; }; } /* Get the frequency class best representing the given rate */ enum hdspe_freq hdspe_sample_rate_freq(u32 rate) { enum hdspe_freq f; int speed_coef = 0; if (rate >= 112000) { rate /= 4; speed_coef = 6; } else if (rate >= 56000) { rate /= 2; speed_coef = 3; } if (rate < 38050) f = HDSPE_FREQ_32KHZ; else if (rate < 46050) f = HDSPE_FREQ_44_1KHZ; else f = HDSPE_FREQ_48KHZ; return f + speed_coef; } /* Converts frequency class to speed mode */ enum hdspe_freq hdspe_speed_adapt(enum hdspe_freq f, enum hdspe_speed speed_mode) { switch (f) { case 0: break; /* This happens when autosync source looses sync e.g. */ case 1: case 2: case 3: switch (speed_mode) { case HDSPE_SPEED_DOUBLE: f += 3; break; case HDSPE_SPEED_QUAD : f += 6; break; default: {} } break; case 4: case 5: case 6: switch (speed_mode) { case HDSPE_SPEED_SINGLE: f -= 3; break; case HDSPE_SPEED_QUAD : f += 3; break; default: {} } break; case 7: case 8: case 9: switch (speed_mode) { case HDSPE_SPEED_SINGLE: f -= 6; break; case HDSPE_SPEED_DOUBLE: f -= 3; break; default: {} } break; default: snd_BUG(); return HDSPE_FREQ_INVALID; } return f; } /* Adapt the sample rate to the given speed mode */ static u32 hdspe_sample_rate_adapt(u32 rate, enum hdspe_speed speed_mode) { switch (hdspe_sample_rate_speed_mode(rate)) { case HDSPE_SPEED_SINGLE: switch (speed_mode) { case HDSPE_SPEED_DOUBLE: rate *= 2; break; case HDSPE_SPEED_QUAD : rate *= 4; break; default: {} } break; case HDSPE_SPEED_DOUBLE: switch (speed_mode) { case HDSPE_SPEED_SINGLE: rate /= 2; break; case HDSPE_SPEED_QUAD : rate *= 2; break; default: {} } break; case HDSPE_SPEED_QUAD: switch (speed_mode) { case HDSPE_SPEED_SINGLE: rate /= 4; break; case HDSPE_SPEED_DOUBLE: rate /= 2; break; default: {} } break; default: snd_BUG(); } return rate; } /* Get the current speed mode */ enum hdspe_speed hdspe_speed_mode(struct hdspe* hdspe) { struct hdspe_control_reg_common control = hdspe->reg.control.common; return control.qs ? HDSPE_SPEED_QUAD : control.ds ? HDSPE_SPEED_DOUBLE : HDSPE_SPEED_SINGLE; } int hdspe_speed_factor(struct hdspe* hdspe) { struct hdspe_control_reg_common control = hdspe->reg.control.common; return control.qs ? 4 : control.ds ? 2 : 1; } /* Get the current internal frequency class */ enum hdspe_freq hdspe_internal_freq(struct hdspe* hdspe) { struct hdspe_control_reg_common control = hdspe->reg.control.common; return control.freq + (control.qs ? 6 : control.ds ? 3 : 0); } /* Write the internal frequency (single speed frequency and speed * mode control register bits). */ int hdspe_write_internal_freq(struct hdspe* hdspe, enum hdspe_freq f) { enum hdspe_freq single_speed_freq = hdspe_speed_adapt(f, HDSPE_SPEED_SINGLE); enum hdspe_speed speed_mode = hdspe_freq_speed(f); dev_dbg(hdspe->card->dev, "%s(%d)\n", __func__, f); if (f == hdspe_internal_freq(hdspe)) return false; hdspe->reg.control.common.freq = single_speed_freq; hdspe->reg.control.common.ds = (speed_mode == HDSPE_SPEED_DOUBLE); hdspe->reg.control.common.qs = (speed_mode == HDSPE_SPEED_QUAD); hdspe_write_control(hdspe); /* Update TCO module "app" sample rate */ if (hdspe->tco) hdspe_tco_set_app_sample_rate(hdspe); return true; } /* PLL reference frequency constants */ static u64 freq_const[HDSPE_IO_TYPE_COUNT] = { 110069313433624ULL, // MADI 131072000000000ULL, // MADIface 104857600000000ULL, // AIO 110069313433624ULL, // AES 104857600000000ULL, // RayDAT 104857600000000ULL // AIO Pro }; /* Convert DDS register period to Parts Pro Milion pitch value, * relative to the control registers single speed internal frequency. */ static int hdspe_dds2ppm(struct hdspe* hdspe, u32 dds) { // ppm = 1000000 * refdds / dds - 1000000 // refdds = fconst / refrate struct hdspe_control_reg_common control = hdspe->reg.control.common; u32 refrate = hdspe_freq_sample_rate(control.freq); u64 fconst = freq_const[hdspe->io_type]; u64 refdds = 1000000ULL * div_u64(fconst, refrate); snd_BUG_ON(dds == 0); return dds != 0 ? (int)div_u64(refdds, dds) : 1000000; // - 1000000; } /* Convert Parts Pro Milion pitch value relative to the control registers * single speed internal frequency to DDS register period. */ static u32 hdspe_ppm2dds(struct hdspe* hdspe, int ppm) { // dds = 1000000 * refdds / (1000000 + ppm) // refdds = fconst / refrate struct hdspe_control_reg_common control = hdspe->reg.control.common; u32 refrate = hdspe_freq_sample_rate(control.freq); u64 fconst = freq_const[hdspe->io_type]; u64 refdds = div_u64(fconst, refrate); u64 refddsM = 1000000ULL * refdds; /* snd_BUG_ON(ppm == 0); */ return ppm != 0 ? (u32)div_u64(refddsM, ppm) : refdds; } /* Convert DDS value to sample rate, taking into account the current speed * mode. */ static u32 hdspe_dds_sample_rate(struct hdspe* hdspe, u32 dds) { struct hdspe_control_reg_common control = hdspe->reg.control.common; u64 fconst = freq_const[hdspe->io_type] * (control.qs ? 4 : control.ds ? 2 : 1); /* snd_BUG_ON(dds == 0); not a bug here, but need to catch it */ return dds != 0 ? (u32)div_u64(fconst, dds) : hdspe_freq_sample_rate(control.freq); } void hdspe_dds_range(struct hdspe* hdspe, u32* min, u32* max) { u64 fconst = freq_const[hdspe->io_type]; *min = (u32)div_u64(fconst, 51750); /* 207KHz / 4 */ *max = (u32)div_u64(fconst, 27000); } u32 hdspe_get_dds(struct hdspe* hdspe) { return le32_to_cpu(hdspe->reg.pll_freq); } int hdspe_write_dds(struct hdspe* hdspe, u32 dds) { __le32 dds_le = cpu_to_le32(dds); u32 ddsmin, ddsmax; int rc = 0; hdspe_dds_range(hdspe, &ddsmin, &ddsmax); if (dds < ddsmin || dds > ddsmax) { rc = -EINVAL; goto done; } if (dds_le == hdspe->reg.pll_freq) { rc = 0; goto done; } hdspe->reg.pll_freq = dds_le; hdspe_write_pll_freq(hdspe); rc = 1; done: dev_dbg(hdspe->card->dev, "%s() dds = %u sample_rate = %u rc = %d.\n", __func__, dds, hdspe_dds_sample_rate(hdspe, dds), rc); return rc; } u32 hdspe_internal_pitch(struct hdspe* hdspe) { return hdspe_dds2ppm(hdspe, hdspe_get_dds(hdspe)); } int hdspe_write_internal_pitch(struct hdspe* hdspe, int ppm) { return hdspe_write_dds(hdspe, hdspe_ppm2dds(hdspe, ppm)); } u32 hdspe_read_system_pitch(struct hdspe* hdspe) { return hdspe_dds2ppm(hdspe, hdspe_read_pll_freq(hdspe)); } u32 hdspe_read_system_sample_rate(struct hdspe* hdspe) { return hdspe_dds_sample_rate(hdspe, hdspe_read_pll_freq(hdspe)); } void hdspe_read_sample_rate_status(struct hdspe* hdspe, struct hdspe_status* status) { struct hdspe_control_reg_common control = hdspe->reg.control.common; status->sample_rate_numerator = freq_const[hdspe->io_type] * (control.qs ? 4 : control.ds ? 2 : 1); status->sample_rate_denominator = hdspe_read_pll_freq(hdspe); status->internal_sample_rate_denominator = le32_to_cpu(hdspe->reg.pll_freq); status->buffer_size = hdspe_period_size(hdspe); status->running = hdspe->running; status->capture_pid = hdspe->capture_pid; status->playback_pid = hdspe->playback_pid; } static int hdspe_write_system_sample_rate(struct hdspe* hdspe, u32 rate) { int changed = false; u32 single_speed_rate = hdspe_sample_rate_adapt(rate, HDSPE_SPEED_SINGLE); enum hdspe_freq freq = hdspe_sample_rate_freq(rate); u64 dds = div_u64(freq_const[hdspe->io_type], single_speed_rate); dev_dbg(hdspe->card->dev, "%s(%d) ...\n", __func__, rate); changed = hdspe_write_internal_freq(hdspe, freq); /* dds should be less than 2^32 for being written to FREQ register */ snd_BUG_ON(dds >> 32); if (hdspe_write_dds(hdspe, dds)) changed = true; return changed; } void hdspe_set_channel_map(struct hdspe* hdspe, enum hdspe_speed speed) { dev_dbg(hdspe->card->dev, "%s()\n", __func__); switch (speed) { case HDSPE_SPEED_SINGLE: hdspe->channel_map_in = hdspe->t.channel_map_in_ss; hdspe->channel_map_out = hdspe->t.channel_map_out_ss; hdspe->max_channels_in = hdspe->t.ss_in_channels; hdspe->max_channels_out = hdspe->t.ss_out_channels; hdspe->port_names_in = hdspe->t.port_names_in_ss; hdspe->port_names_out = hdspe->t.port_names_out_ss; break; case HDSPE_SPEED_DOUBLE: hdspe->channel_map_in = hdspe->t.channel_map_in_ds; hdspe->channel_map_out = hdspe->t.channel_map_out_ds; hdspe->max_channels_in = hdspe->t.ds_in_channels; hdspe->max_channels_out = hdspe->t.ds_out_channels; hdspe->port_names_in = hdspe->t.port_names_in_ds; hdspe->port_names_out = hdspe->t.port_names_out_ds; break; case HDSPE_SPEED_QUAD: hdspe->channel_map_in = hdspe->t.channel_map_in_qs; hdspe->channel_map_out = hdspe->t.channel_map_out_qs; hdspe->max_channels_in = hdspe->t.qs_in_channels; hdspe->max_channels_out = hdspe->t.qs_out_channels; hdspe->port_names_in = hdspe->t.port_names_in_qs; hdspe->port_names_out = hdspe->t.port_names_out_qs; break; default: {} }; hdspe_mixer_update_channel_map(hdspe); } int hdspe_set_sample_rate(struct hdspe * hdspe, u32 desired_rate) { /* TODO: Changing between Single, Double and Quad speed is not allowed if any substreams are open. This is because such a change causes a shift in the location of the DMA buffers and a reduction in the number of available buffers. Note that a similar but essentially insoluble problem exists for externally-driven rate changes. All we can do is to flag rate changes in the read/write routines. */ int changed; enum hdspe_speed desired_speed_mode = hdspe_sample_rate_speed_mode(desired_rate); dev_dbg(hdspe->card->dev, "%s(%d)\n", __func__, desired_rate); #ifdef NEVER /* locks up ubuntu desktop if we do that here. */ if (!snd_hdspe_use_is_exclusive(hdspe)) { dev_warn(hdspe->card->dev, "Cannot change sample rate: no exclusive use.\n"); return -EBUSY; } #endif /*NEVER*/ #ifdef NEVER /* This prevents sample rate changes also in cases where they * are perfectly fine. */ if (desired_speed_mode != current_speed_mode) { if (hdspe->capture_pid >= 0 || hdspe->playback_pid >= 0) { dev_err(hdspe->card->dev, "Cannot change from %s speed to %s speed mode" "(capture PID = %d, playback PID = %d)\n", HDSPE_SPEED_NAME(current_speed_mode), HDSPE_SPEED_NAME(desired_speed_mode), hdspe->capture_pid, hdspe->playback_pid); return -EBUSY; } } #endif /*NEVER*/ changed = hdspe_write_system_sample_rate(hdspe, desired_rate); /* if (changed) */ hdspe_set_channel_map(hdspe, desired_speed_mode); if (changed) { /* notify Internal Frequency and DDS control elements */ HDSPE_CTL_NOTIFY(internal_freq); HDSPE_CTL_NOTIFY(dds); } return changed; } snd-hdspe-1.0.2/sound/pci/hdsp/hdspe/hdspe_control.c000066400000000000000000000701321506037051300223550ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later /** * hdspe-control.c * @brief RME HDSPe sound card driver status and control interface. * * 20210727,28,29,30,0908,09,10,20220330 - Philippe.Bekaert@uhasselt.be * * Based on earlier work of the other MODULE_AUTHORS, * information kindly made available by RME (www.rme-audio.com), * and hardware kindly made available by Amptec Belgium (www.amptec.be). */ /* TODO: systematic value range checking */ #include "hdspe.h" #include "hdspe_core.h" #include "hdspe_control.h" /** * hdspe_init_autosync_tables: calculates tables needed for the * preferred sync and autosync ref properties below, given the list of * autosync clock sources for a card: * Tables calculated: * - hdspe::t.autosync_texts : a text label for each clock source, * plus HDSPE_CLOCK_SOURCE_INTERN at the end of the list. * - hdspe::t.autosync_idx2ref : maps a property index to a * hdspe_clock_source enum. * - hdspe::t.autosync_ref2idx : maps a hdspe_clock_source enum to a * property index. * The number of clock sources, including HDSPE_CLOCK_SOURCE_INTERN, * is stored in hdspe::t.autosync_count. * @nr_autosync_opts: the number of clock sources (size of autosync_opts array) * @autosync_opts: the autosync clock sources offered by the card. */ void hdspe_init_autosync_tables(struct hdspe* hdspe, int nr_autosync_opts, enum hdspe_clock_source* autosync_opts) { int i, n; struct hdspe_tables *t = &hdspe->t; /* Clear the tables to sane defaults. */ for (i=0; iautosync_texts[i] = ""; t->autosync_idx2ref[i] = 0; t->autosync_ref2idx[i] = 0; } for (i=0, n=0; iautosync_texts[n] = hdspe_clock_source_name(hdspe, ref); t->autosync_idx2ref[n] = ref; t->autosync_ref2idx[ref] = n; n++; } // Add HDSPE_CLOCK_SOURCE_INTERN as the last option // for Current AutoSync Reference property. t->autosync_texts[n] = hdspe_clock_source_name(hdspe, HDSPE_CLOCK_SOURCE_INTERN); t->autosync_idx2ref[n] = HDSPE_CLOCK_SOURCE_INTERN; t->autosync_ref2idx[HDSPE_CLOCK_SOURCE_INTERN] = n; n++; t->autosync_count = n; dev_dbg(hdspe->card->dev, "AutoSync tables: %d clock sources:\n", t->autosync_count); for (i=0; icard->dev, "Idx %2d idx2ref=%d texts='%s'\n", i, t->autosync_idx2ref[i], t->autosync_texts[i]); } for (i=0; icard->dev, "Ref %2d '%s' ref2idx=%d\n", i, hdspe_clock_source_name(hdspe, i), t->autosync_ref2idx[i]); } } const char* const hdspe_clock_source_name(struct hdspe* hdspe, int i) { return (i >= 0 && i < HDSPE_CLOCK_SOURCE_COUNT) ? hdspe->t.clock_source_names[i] : "???"; } /* ------------------------------------------------------- */ int hdspe_control_get(struct snd_kcontrol *kcontrol, int (*get)(struct hdspe*), bool lock_req, const char* propname) { int val; struct hdspe *hdspe = snd_kcontrol_chip(kcontrol); dev_dbg(hdspe->card->dev, "hdspe_control_get(%s) ...\n", propname); if (lock_req) spin_lock_irq(&hdspe->lock); val = get(hdspe); if (lock_req) spin_unlock_irq(&hdspe->lock); dev_dbg(hdspe->card->dev, "... = %d.\n", val); return val; } int hdspe_control_put(struct snd_kcontrol *kcontrol, int val, int (*get)(struct hdspe*), int (*put)(struct hdspe*, int val), bool lock_req, bool excl_req, const char* propname) { int oldval = -1, changed = 0, rc = 0; struct hdspe *hdspe = snd_kcontrol_chip(kcontrol); if (excl_req && !snd_hdspe_use_is_exclusive(hdspe)) { dev_dbg(hdspe->card->dev, "snd_hdspe_put(%s,%d): no exclusive access!\n", propname, val); return -EBUSY; } dev_dbg(hdspe->card->dev, "snd_hdspe_put(%s,%d) %s get() ...\n", propname, val, get ? "with" : "without"); if (lock_req) spin_lock_irq(&hdspe->lock); oldval = get ? get(hdspe) : val; changed = (val != oldval); if (!get || changed) rc = put(hdspe, val); if (lock_req) spin_unlock_irq(&hdspe->lock); dev_dbg(hdspe->card->dev, "... val = %d, oldval = %d, changed = %d, put rc = %d.\n", val, oldval, changed, rc); return rc ? rc : changed; } /* -------------- status polling ------------------- */ HDSPE_RW_INT1_HDSPE_METHODS(status_polling, 0, HZ, 1) void hdspe_status_work(struct work_struct *work) { struct hdspe *hdspe = container_of(work, struct hdspe, status_work); int64_t sr_delta; int i; bool changed = false; struct hdspe_status o = hdspe->last_status; struct hdspe_status n; hdspe->m.read_status(hdspe, &n); for (i = 0; i < HDSPE_CLOCK_SOURCE_COUNT; i++) { if (n.sync[i] != o.sync[i]) { dev_dbg(hdspe->card->dev, "sync source %d status changed %d -> %d.\n", i, o.sync[i], n.sync[i]); HDSPE_CTL_NOTIFY(autosync_status); changed = true; break; } } for (i = 0; i < HDSPE_CLOCK_SOURCE_COUNT; i++) { if (n.freq[i] != o.freq[i]) { dev_dbg(hdspe->card->dev, "sync source %d freq changed %d -> %d.\n", i, o.freq[i], n.freq[i]); HDSPE_CTL_NOTIFY(autosync_freq); changed = true; break; } } if (n.autosync_ref != o.autosync_ref) { dev_dbg(hdspe->card->dev, "autosync ref changed %d -> %d.\n", o.autosync_ref, n.autosync_ref); HDSPE_CTL_NOTIFY(autosync_ref); changed = true; } sr_delta = (int64_t)n.sample_rate_denominator - (int64_t)o.sample_rate_denominator; if (n.sample_rate_numerator != o.sample_rate_numerator || (sr_delta<0 ? -sr_delta : sr_delta) > (n.sample_rate_denominator / 1000000)) { dev_dbg(hdspe->card->dev, "sample rate changed %llu/%u -> %llu/%u.\n", o.sample_rate_numerator, o.sample_rate_denominator, n.sample_rate_numerator, n.sample_rate_denominator); HDSPE_CTL_NOTIFY(raw_sample_rate); changed = true; } if (hdspe->m.check_status_change && hdspe->m.check_status_change(hdspe, &o, &n)) changed = true; if (hdspe->tco && hdspe_tco_notify_status_change(hdspe)) { changed = true; } hdspe->last_status = n; if (hdspe->last_status_change_jiffies == 0) hdspe->last_status_change_jiffies = jiffies; if (changed) { hdspe->last_status_change_jiffies = 0; hdspe->status_polling = 0; /* disable - user must re-enable */ HDSPE_CTL_NOTIFY(status_polling); } else if (jiffies > hdspe->last_status_change_jiffies + 2*HZ) { dev_dbg(hdspe->card->dev, "%s: polling timeout expired: jiffies=%lu, last_status_change_jiffied=%lu, delta=%ld, 2*HZ=%d.\n", __func__, jiffies, hdspe->last_status_change_jiffies, jiffies - hdspe->last_status_change_jiffies, 2*HZ); hdspe->last_status_change_jiffies = 0; hdspe->status_polling = 0; /* disable - user must re-enable */ HDSPE_CTL_NOTIFY(status_polling); } } /* ------------------ raw sample rate and DDS -------------------- */ static int snd_hdspe_info_raw_sample_rate(struct snd_kcontrol* kcontrol, struct snd_ctl_elem_info *uinfo) { uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER64; uinfo->count = 2; return 0; } static int snd_hdspe_get_raw_sample_rate(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct hdspe *hdspe = snd_kcontrol_chip(kcontrol); struct hdspe_status s; hdspe_read_sample_rate_status(hdspe, &s); ucontrol->value.integer64.value[0] = s.sample_rate_numerator; ucontrol->value.integer64.value[1] = s.sample_rate_denominator; return 0; } static int snd_hdspe_info_dds(struct snd_kcontrol* kcontrol, struct snd_ctl_elem_info *uinfo) { struct hdspe *hdspe = snd_kcontrol_chip(kcontrol); u32 ddsmin, ddsmax; hdspe_dds_range(hdspe, &ddsmin, &ddsmax); uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; uinfo->count = 1; uinfo->value.integer.min = ddsmin; uinfo->value.integer.max = ddsmax; return 0; } static int hdspe_write_dds_i(struct hdspe* hdspe, int val) { return hdspe_write_dds(hdspe, (u32)val); } HDSPE_INT1_GET(dds, hdspe_get_dds, true) HDSPE_INT1_PUT(dds, NULL, hdspe_write_dds_i, true, false) /* -------------- system clock mode ----------------- */ static int snd_hdspe_info_clock_mode(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { static const char *const texts[] = { HDSPE_CLOCK_MODE_NAME(0), HDSPE_CLOCK_MODE_NAME(1) }; ENUMERATED_CTL_INFO(uinfo, texts); return 0; } static int hdspe_get_clock_mode(struct hdspe* hdspe) { return hdspe->m.get_clock_mode(hdspe); } static int hdspe_set_clock_mode(struct hdspe* hdspe, int val) { hdspe->m.set_clock_mode(hdspe, val); return 0; } HDSPE_RW_ENUM_METHODS(clock_mode, hdspe_get_clock_mode, hdspe_set_clock_mode, false); /* --------------- preferred sync reference ------------------ */ static int snd_hdspe_info_pref_sync_ref(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { struct hdspe *hdspe = snd_kcontrol_chip(kcontrol); // Last autosync option ("intern") does not apply here. snd_ctl_enum_info(uinfo, 1, hdspe->t.autosync_count-1, hdspe->t.autosync_texts); return 0; } static int hdspe_get_pref_sync_ref_idx(struct hdspe* hdspe) { return hdspe->t.autosync_ref2idx[hdspe->m.get_pref_sync_ref(hdspe)]; } static int hdspe_put_pref_sync_ref_idx(struct hdspe* hdspe, int idx) { hdspe->m.set_pref_sync_ref(hdspe, hdspe->t.autosync_idx2ref[idx]); return 0; } HDSPE_RW_ENUM_METHODS(pref_sync_ref, hdspe_get_pref_sync_ref_idx, hdspe_put_pref_sync_ref_idx, false); /* --------------- AutoSync reference ----------------- */ static int snd_hdspe_info_autosync_ref(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { struct hdspe *hdspe = snd_kcontrol_chip(kcontrol); snd_ctl_enum_info(uinfo, 1, hdspe->t.autosync_count, hdspe->t.autosync_texts); return 0; } static int hdspe_get_autosync_ref_idx(struct hdspe* hdspe) { return hdspe->t.autosync_ref2idx[hdspe->m.get_autosync_ref(hdspe)]; } HDSPE_RO_ENUM_METHODS(autosync_ref, hdspe_get_autosync_ref_idx); /* --------------- AutoSync Status ------------------- */ static int snd_hdspe_info_autosync_status(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { struct hdspe *hdspe = snd_kcontrol_chip(kcontrol); static const char *const texts[] = { HDSPE_SYNC_STATUS_NAME(0), HDSPE_SYNC_STATUS_NAME(1), HDSPE_SYNC_STATUS_NAME(2), HDSPE_SYNC_STATUS_NAME(3) }; ENUMERATED_CTL_INFO(uinfo, texts); uinfo->count = hdspe->t.autosync_count - 1; /* last item is "internal" */ return 0; } static int snd_hdspe_get_autosync_status(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct hdspe* hdspe = snd_kcontrol_chip(kcontrol); int i; struct hdspe_status s; hdspe->m.read_status(hdspe, &s); for (i=0; it.autosync_count-1; i++) { int ref = hdspe->t.autosync_idx2ref[i]; ucontrol->value.enumerated.item[i] = s.sync[ref]; } return 0; } /* -------------- AutoSync Frequency ------------ */ static const char *const texts_freq[HDSPE_FREQ_COUNT] = { HDSPE_FREQ_NAME(HDSPE_FREQ_NO_LOCK), HDSPE_FREQ_NAME(HDSPE_FREQ_32KHZ), HDSPE_FREQ_NAME(HDSPE_FREQ_44_1KHZ), HDSPE_FREQ_NAME(HDSPE_FREQ_48KHZ), HDSPE_FREQ_NAME(HDSPE_FREQ_64KHZ), HDSPE_FREQ_NAME(HDSPE_FREQ_88_2KHZ), HDSPE_FREQ_NAME(HDSPE_FREQ_96KHZ), HDSPE_FREQ_NAME(HDSPE_FREQ_128KHZ), HDSPE_FREQ_NAME(HDSPE_FREQ_176_4KHZ), HDSPE_FREQ_NAME(HDSPE_FREQ_192KHZ) }; const char* const hdspe_freq_name(enum hdspe_freq i) { return (i >= 0 && i < HDSPE_FREQ_COUNT) ? texts_freq[i] : "???"; } static int snd_hdspe_info_autosync_freq(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { struct hdspe *hdspe = snd_kcontrol_chip(kcontrol); ENUMERATED_CTL_INFO(uinfo, texts_freq); uinfo->count = hdspe->t.autosync_count - 1; /* skip last item "internal" */ return 0; } static int snd_hdspe_get_autosync_freq(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct hdspe* hdspe = snd_kcontrol_chip(kcontrol); int i; struct hdspe_status s; hdspe->m.read_status(hdspe, &s); for (i=0; it.autosync_count-1; i++) { int ref = hdspe->t.autosync_idx2ref[i]; ucontrol->value.enumerated.item[i] = s.freq[ref]; } return 0; } /* ---------------- Internal frequency ------------------ */ static int snd_hdspe_info_internal_freq(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { /* skip the first ("No Lock") frequency option */ return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(texts_freq)-1, texts_freq + 1); return 0; } static int hdspe_get_internal_freq_idx(struct hdspe* hdspe) { return hdspe_internal_freq(hdspe)-1; } static int hdspe_put_internal_freq_idx(struct hdspe* hdspe, int val) { int changed = 0; int pitch = hdspe_internal_pitch(hdspe); dev_dbg(hdspe->card->dev, "%s(%d): idx %d -> freq %d, pitch = %d\n", __func__, val, val, val+1, pitch); changed = hdspe_write_internal_freq(hdspe, val+1); /* Preserve pitch (essentially ratio of effective (internal) sample rate * over standard (internal) sample rate) */ if (hdspe_write_internal_pitch(hdspe, pitch)) { HDSPE_CTL_NOTIFY(dds); } return changed; } HDSPE_RW_ENUM_METHODS(internal_freq, hdspe_get_internal_freq_idx, hdspe_put_internal_freq_idx, false); /* ---------------------- MADI specific ------------------- */ HDSPE_RW_ENUM_REG_METHODS(madi_sswclk, control, madi, WCK48, false) HDSPE_RW_ENUM_REG_METHODS(madi_LineOut, control, madi, LineOut, false) HDSPE_RW_ENUM_REG_METHODS(madi_clr_tms, control, madi, CLR_TMS, false) HDSPE_RW_ENUM_REG_METHODS(madi_tx_64ch, control, madi, tx_64ch, false) HDSPE_RO_ENUM_REG_METHODS(madi_rx_64ch, status0, madi, rx_64ch) #define snd_hdspe_info_madi_rx_64ch snd_ctl_boolean_mono_info HDSPE_RW_ENUM_REG_METHODS(madi_smux, control, madi, SMUX, false) HDSPE_RW_ENUM_REG_METHODS(madi_autoinput, control, madi, AutoInp, false) static int snd_hdspe_info_madi_input_source(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { static const char *const texts[] = { HDSPE_MADI_INPUT_NAME(0), HDSPE_MADI_INPUT_NAME(1) }; ENUMERATED_CTL_INFO(uinfo, texts); return 0; } #define snd_hdspe_info_madi_input_select snd_hdspe_info_madi_input_source HDSPE_RW_ENUM_REG_METHODS(madi_input_select, control, madi, inp_0, false) HDSPE_RO_ENUM_REG_METHODS(madi_input_source, status0, madi, AB_int) static int snd_hdspe_info_external_freq(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { ENUMERATED_CTL_INFO(uinfo, texts_freq); return 0; } static enum hdspe_freq hdspe_get_external_freq(struct hdspe* hdspe) { return hdspe_madi_get_external_freq(hdspe); } HDSPE_RO_ENUM_METHODS(external_freq, hdspe_get_external_freq); /* ------------------------ AES specific ------------------- */ HDSPE_RW_ENUM_REG_METHODS(aes_ds_mode, control, aes, ds_mode, false) HDSPE_RW_ENUM_REG_METHODS(aes_qs_mode, control, aes, qs_mode, false) HDSPE_RW_ENUM_REG_METHODS(aes_emp, control, aes, EMP, false) HDSPE_RW_ENUM_REG_METHODS(aes_dolby, control, aes, Dolby, false) HDSPE_RW_ENUM_REG_METHODS(aes_pro, control, aes, PRO, false) HDSPE_RW_ENUM_REG_METHODS(aes_LineOut, control, aes, LineOut, false) HDSPE_RW_ENUM_REG_METHODS(aes_sswclk, control, aes, WCK48, false) HDSPE_RW_ENUM_REG_METHODS(aes_clr_tms, control, aes, CLR_TMS, false) static int snd_hdspe_info_aes_ds_mode(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { static const char *const texts[] = { HDSPE_DS_MODE_NAME(0), HDSPE_DS_MODE_NAME(1) }; ENUMERATED_CTL_INFO(uinfo, texts); return 0; } static int snd_hdspe_info_aes_qs_mode(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { static const char *const texts[HDSPE_QS_MODE_COUNT] = { HDSPE_QS_MODE_NAME(0), HDSPE_QS_MODE_NAME(1), HDSPE_QS_MODE_NAME(2) }; ENUMERATED_CTL_INFO(uinfo, texts); return 0; } /* ---------------------- RayDAT / AIO / AIO Pro ---------------- */ HDSPE_RW_ENUM_REG_METHODS(raio_spdif_opt, settings, raio, Spdif_Opt, false) HDSPE_RW_ENUM_REG_METHODS(raio_spdif_pro, settings, raio, Pro, false) HDSPE_RW_ENUM_REG_METHODS(raio_aeb1, settings, raio, AEB1, false) HDSPE_RW_ENUM_REG_METHODS(raio_aeb2, settings, raio, AEB2, false) HDSPE_RW_ENUM_REG_METHODS(raio_sswclk, settings, raio, Wck48, false) HDSPE_RW_ENUM_REG_METHODS(raio_clr_tms, settings, raio, clr_tms, false) HDSPE_RW_ENUM_REG_METHODS(aio_xlr, settings, raio, Sym6db, false) /* --------------------- S/PDIF input ---------------------- */ static int snd_hdspe_info_raio_spdif_in(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { static const char *const texts[HDSPE_RAIO_SPDIF_INPUT_COUNT] = { HDSPE_RAIO_SPDIF_INPUT_NAME(0), HDSPE_RAIO_SPDIF_INPUT_NAME(1), HDSPE_RAIO_SPDIF_INPUT_NAME(2) }; ENUMERATED_CTL_INFO(uinfo, texts); return 0; } HDSPE_RW_ENUM_REG_METHODS(raio_spdif_in, settings, raio, Input, false) /* --------------------- AIO AD / DA / Phones level ----------------- */ static int snd_hdspe_info_aio_input_level(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { static const char *const texts[HDSPE_AIO_LEVEL_COUNT] = { "Lo Gain", "+4 dBu", "-10 dBV" }; ENUMERATED_CTL_INFO(uinfo, texts); return 0; } static int snd_hdspe_info_aio_out_level(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { static const char *const texts[HDSPE_AIO_LEVEL_COUNT] = { "Hi Gain", "+4 dBu", "-10 dBV" }; ENUMERATED_CTL_INFO(uinfo, texts); return 0; } #define snd_hdspe_info_aio_output_level snd_hdspe_info_aio_out_level #define snd_hdspe_info_aio_phones_level snd_hdspe_info_aio_out_level HDSPE_RW_ENUM_REG_METHODS(aio_input_level, settings, raio, AD_GAIN, false) HDSPE_RW_ENUM_REG_METHODS(aio_output_level, settings, raio, DA_GAIN, false) HDSPE_RW_ENUM_REG_METHODS(aio_phones_level, settings, raio, PH_GAIN, false) /* ------------------ AIO PRO AD / DA / Phones level ---------------*/ static int snd_hdspe_info_aio_pro_input_level(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { static const char *const texts[HDSPE_AIO_PRO_INPUT_LEVEL_COUNT] = { HDSPE_AIO_PRO_INPUT_LEVEL_NAME(0), HDSPE_AIO_PRO_INPUT_LEVEL_NAME(1), HDSPE_AIO_PRO_INPUT_LEVEL_NAME(2), HDSPE_AIO_PRO_INPUT_LEVEL_NAME(3) }; ENUMERATED_CTL_INFO(uinfo, texts); return 0; } #define snd_hdspe_get_aio_pro_input_level snd_hdspe_get_aio_input_level #define snd_hdspe_put_aio_pro_input_level snd_hdspe_put_aio_input_level /* AIO Pro output level here combines DA_GAIN and Sym6db. The output * voltage range is no longer a simple 6dB different whether Sym6db * is on or off. */ static int snd_hdspe_info_aio_pro_output_level(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { static const char *const texts[HDSPE_AIO_PRO_OUTPUT_LEVEL_COUNT] = { HDSPE_AIO_PRO_OUTPUT_LEVEL_NAME(0), HDSPE_AIO_PRO_OUTPUT_LEVEL_NAME(1), HDSPE_AIO_PRO_OUTPUT_LEVEL_NAME(2), HDSPE_AIO_PRO_OUTPUT_LEVEL_NAME(3), HDSPE_AIO_PRO_OUTPUT_LEVEL_NAME(4), HDSPE_AIO_PRO_OUTPUT_LEVEL_NAME(5), HDSPE_AIO_PRO_OUTPUT_LEVEL_NAME(6), HDSPE_AIO_PRO_OUTPUT_LEVEL_NAME(7) }; ENUMERATED_CTL_INFO(uinfo, texts); return 0; } static int hdspe_get_aio_pro_output_level(struct hdspe* hdspe) { struct hdspe_settings_reg_raio settings = hdspe->reg.settings.raio; return settings.DA_GAIN + (settings.Sym6db ? 4 : 0); } static int hdspe_put_aio_pro_output_level(struct hdspe* hdspe, int val) { struct hdspe_settings_reg_raio *settings = &hdspe->reg.settings.raio; settings->DA_GAIN = val%4; settings->Sym6db = val/4; hdspe_write_settings(hdspe); return 0; } HDSPE_RW_ENUM_METHODS(aio_pro_output_level, hdspe_get_aio_pro_output_level, hdspe_put_aio_pro_output_level, false) static int snd_hdspe_info_aio_pro_phones_level(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { static const char *const texts[HDSPE_AIO_PRO_PHONES_LEVEL_COUNT] = { HDSPE_AIO_PRO_PHONES_LEVEL_NAME(0), HDSPE_AIO_PRO_PHONES_LEVEL_NAME(1) }; ENUMERATED_CTL_INFO(uinfo, texts); return 0; } #define snd_hdspe_get_aio_pro_phones_level snd_hdspe_get_aio_phones_level #define snd_hdspe_put_aio_pro_phones_level snd_hdspe_put_aio_phones_level static int hdspe_get_aio_ao4s(struct hdspe* hdspe) { return !hdspe_read_status2(hdspe).raio.AEBO_D; } HDSPE_RO_ENUM_METHODS(aio_ao4s, hdspe_get_aio_ao4s) static int hdspe_get_aio_ai4s(struct hdspe* hdspe) { return !hdspe_read_status2(hdspe).raio.AEBI_D; } HDSPE_RO_ENUM_METHODS(aio_ai4s, hdspe_get_aio_ai4s) /* --------------------------------------------------------- */ /* Common control elements, except those for which we need to keep the id. The * ones we need to keep the id for, are added explicitly in snd_hdspe_create_controls(). */ static const struct snd_kcontrol_new snd_hdspe_controls_common[] = { HDSPE_RW_KCTL(CARD, "Clock Mode", clock_mode), HDSPE_RW_KCTL(CARD, "Preferred AutoSync Reference", pref_sync_ref), }; static const struct snd_kcontrol_new snd_hdspe_controls_madi[] = { HDSPE_RW_BOOL_KCTL(CARD, "TX 64 Channels Mode", madi_tx_64ch), HDSPE_RW_BOOL_KCTL(CARD, "Double Wire Mode", madi_smux), HDSPE_RW_BOOL_KCTL(CARD, "Autoselect Input", madi_autoinput), HDSPE_RW_KCTL(CARD, "Preferred Input", madi_input_select), HDSPE_RW_BOOL_KCTL(CARD, "Line Out", madi_LineOut), HDSPE_RW_BOOL_KCTL(CARD, "Single Speed WordClk Out", madi_sswclk), HDSPE_RW_BOOL_KCTL(CARD, "Clear TMS", madi_clr_tms), }; static const struct snd_kcontrol_new snd_hdspe_controls_madiface[] = { HDSPE_RW_KCTL(CARD, "Clock Mode", clock_mode), #ifdef OLDSTUFF HDSPE_RV_KCTL(CARD, "Sample Rate", sample_rate), HDSPE_RW_KCTL(CARD, "Internal Frequency", internal_freq), HDSPE_RV_KCTL(CARD, "External Frequency", external_freq), // TODO HDSPE_SYNC_STATUS("MADI Status", HDSPE_CLOCK_SOURCE_MADI), #endif /*OLDSTUFF*/ HDSPE_RW_BOOL_KCTL(CARD, "TX 64 Channels Mode", madi_tx_64ch), HDSPE_RV_BOOL_KCTL(CARD, "RX 64 Channels Mode", madi_rx_64ch), HDSPE_RW_BOOL_KCTL(CARD, "Safe Mode", madi_autoinput), // HDSPE_RV_KCTL(CARD, "Speed Mode", speed_mode) }; static const struct snd_kcontrol_new snd_hdspe_controls_aes[] = { HDSPE_RW_KCTL(CARD, "Double Speed Mode", aes_ds_mode), HDSPE_RW_KCTL(CARD, "Quad Speed Mode", aes_qs_mode), HDSPE_RW_BOOL_KCTL(CARD, "Professional", aes_pro), HDSPE_RW_BOOL_KCTL(CARD, "Emphasis", aes_emp), HDSPE_RW_BOOL_KCTL(CARD, "Non Audio", aes_dolby), HDSPE_RW_BOOL_KCTL(CARD, "Line Out", aes_LineOut), HDSPE_RW_BOOL_KCTL(CARD, "Single Speed WordClk Out", aes_sswclk), HDSPE_RW_BOOL_KCTL(CARD, "Clear TMS", aes_clr_tms), }; static const struct snd_kcontrol_new snd_hdspe_controls_raydat[] = { HDSPE_RW_KCTL(CARD, "S/PDIF In", raio_spdif_in), HDSPE_RW_BOOL_KCTL(CARD, "S/PDIF Out Optical", raio_spdif_opt), HDSPE_RW_BOOL_KCTL(CARD, "S/PDIF Out Professional", raio_spdif_pro), HDSPE_RW_BOOL_KCTL(CARD, "ADAT1 Internal", raio_aeb1), HDSPE_RW_BOOL_KCTL(CARD, "ADAT2 Internal", raio_aeb2), HDSPE_RW_BOOL_KCTL(CARD, "Single Speed WordClk Out", raio_sswclk), HDSPE_RW_BOOL_KCTL(CARD, "Clear TMS", raio_clr_tms), }; static const struct snd_kcontrol_new snd_hdspe_controls_aio[] = { HDSPE_RO_BOOL_KCTL(CARD, "AO4S Present", aio_ao4s), HDSPE_RO_BOOL_KCTL(CARD, "AI4S Present", aio_ai4s), HDSPE_RW_KCTL(CARD, "S/PDIF In", raio_spdif_in), HDSPE_RW_BOOL_KCTL(CARD, "S/PDIF Out Optical", raio_spdif_opt), HDSPE_RW_BOOL_KCTL(CARD, "S/PDIF Out Professional", raio_spdif_pro), HDSPE_RW_BOOL_KCTL(CARD, "ADAT Internal", raio_aeb1), HDSPE_RW_BOOL_KCTL(CARD, "Single Speed WordClk Out", raio_sswclk), HDSPE_RW_BOOL_KCTL(CARD, "Clear TMS", raio_clr_tms), HDSPE_RW_BOOL_KCTL(CARD, "XLR Breakout Cable", aio_xlr), HDSPE_RW_KCTL(CARD, "Input Level", aio_input_level), HDSPE_RW_KCTL(CARD, "Output Level", aio_output_level), HDSPE_RW_KCTL(CARD, "Phones Level", aio_phones_level) }; static const struct snd_kcontrol_new snd_hdspe_controls_aio_pro[] = { HDSPE_RW_KCTL(CARD, "S/PDIF In", raio_spdif_in), HDSPE_RW_BOOL_KCTL(CARD, "S/PDIF Out Optical", raio_spdif_opt), HDSPE_RW_BOOL_KCTL(CARD, "S/PDIF Out Professional", raio_spdif_pro), HDSPE_RW_BOOL_KCTL(CARD, "ADAT Internal", raio_aeb1), HDSPE_RW_BOOL_KCTL(CARD, "Single Speed WordClk Out", raio_sswclk), HDSPE_RW_BOOL_KCTL(CARD, "Clear TMS", raio_clr_tms), HDSPE_RW_KCTL(CARD, "Input Level", aio_pro_input_level), HDSPE_RW_KCTL(CARD, "Output Level", aio_pro_output_level), HDSPE_RW_KCTL(CARD, "Phones Level", aio_pro_phones_level), }; HDSPE_RO_INT1_HDSPE_METHODS(firmware_rev, 0, 0, 1) HDSPE_RO_INT1_HDSPE_METHODS(fw_build, 0, 0, 1) HDSPE_RO_INT1_HDSPE_METHODS(serial, 0, 0, 1) #define snd_hdspe_info_running snd_ctl_boolean_mono_info HDSPE_RO_ENUM_HDSPE_METHODS(running) HDSPE_RO_INT1_HDSPE_METHODS(capture_pid, 0, 0, 1) HDSPE_RO_INT1_HDSPE_METHODS(playback_pid, 0, 0, 1) HDSPE_RO_INT1_METHODS(buffer_size, 32, 8192, 1, hdspe_period_size) static int hdspe_is_tco_present(struct hdspe* hdspe) { return hdspe->tco != NULL; } #define snd_hdspe_info_tco_present snd_ctl_boolean_mono_info HDSPE_RO_ENUM_METHODS(tco_present, hdspe_is_tco_present) static const struct snd_kcontrol_new snd_hdspe_controls_cardinfo[] = { HDSPE_RO_KCTL(CARD, "Card Revision", firmware_rev), HDSPE_RO_KCTL(CARD, "Firmware Build", fw_build), HDSPE_RO_KCTL(CARD, "Serial", serial), HDSPE_RO_KCTL(CARD, "TCO Present", tco_present), HDSPE_RV_KCTL(CARD, "Capture PID", capture_pid), HDSPE_RV_KCTL(CARD, "Playback PID", playback_pid), }; struct snd_kcontrol* hdspe_add_control(struct hdspe* hdspe, const struct snd_kcontrol_new* newctl) { struct snd_kcontrol* ctl = snd_ctl_new1(newctl, hdspe); int err = snd_ctl_add(hdspe->card, ctl); return (err < 0) ? ERR_PTR(err) : ctl; } int hdspe_add_controls(struct hdspe *hdspe, int count, const struct snd_kcontrol_new *list) { int i; for (i = 0; i < count; i++) { int err = snd_ctl_add(hdspe->card, snd_ctl_new1(&list[i], hdspe)); if (err < 0) return err; } return 0; } int hdspe_add_control_id(struct hdspe* hdspe, const struct snd_kcontrol_new* nctl, struct snd_ctl_elem_id** ctl_id) { struct snd_kcontrol* ctl = hdspe_add_control(hdspe, nctl); if (IS_ERR(ctl)) return -PTR_ERR(ctl); *ctl_id = &ctl->id; return 0; } int snd_hdspe_create_controls(struct snd_card *card, struct hdspe *hdspe) { unsigned int limit; int err; const struct snd_kcontrol_new *list = NULL; /* Card info controls */ err = hdspe_add_controls(hdspe, ARRAY_SIZE(snd_hdspe_controls_cardinfo), snd_hdspe_controls_cardinfo); if (err < 0) return err; HDSPE_ADD_RV_CONTROL_ID(CARD, "Running", running); HDSPE_ADD_RV_CONTROL_ID(CARD, "Buffer Size", buffer_size); HDSPE_ADD_RWV_CONTROL_ID(CARD, "Status Polling", status_polling); HDSPE_ADD_RV_CONTROL_ID(HWDEP, "Raw Sample Rate", raw_sample_rate); HDSPE_ADD_RW_CONTROL_ID(HWDEP, "DDS", dds); HDSPE_ADD_RW_CONTROL_ID(CARD, "Internal Frequency", internal_freq); /* Common controls: sample rate etc ... */ if (hdspe->io_type != HDSPE_MADIFACE) { HDSPE_ADD_RV_CONTROL_ID(CARD, "Current AutoSync Reference", autosync_ref); err = hdspe_add_controls(hdspe, ARRAY_SIZE(snd_hdspe_controls_common), snd_hdspe_controls_common); if (err < 0) return err; } /* AutoSync status and frequency */ if (hdspe->io_type != HDSPE_MADIFACE) { HDSPE_ADD_RV_CONTROL_ID(CARD, "AutoSync Status", autosync_status); HDSPE_ADD_RV_CONTROL_ID(CARD, "AutoSync Frequency", autosync_freq); } /* Card specific controls */ switch (hdspe->io_type) { case HDSPE_MADI: HDSPE_ADD_RV_CONTROL_ID(CARD, "External Frequency", external_freq); HDSPE_ADD_RV_CONTROL_ID(CARD, "Current Input", madi_input_source); HDSPE_ADD_RV_CONTROL_ID(CARD, "RX 64 Channels Mode", madi_rx_64ch); list = snd_hdspe_controls_madi; limit = ARRAY_SIZE(snd_hdspe_controls_madi); break; case HDSPE_MADIFACE: list = snd_hdspe_controls_madiface; limit = ARRAY_SIZE(snd_hdspe_controls_madiface); break; case HDSPE_AES: list = snd_hdspe_controls_aes; limit = ARRAY_SIZE(snd_hdspe_controls_aes); break; case HDSPE_RAYDAT: list = snd_hdspe_controls_raydat; limit = ARRAY_SIZE(snd_hdspe_controls_raydat); break; case HDSPE_AIO: list = snd_hdspe_controls_aio; limit = ARRAY_SIZE(snd_hdspe_controls_aio); break; case HDSPE_AIO_PRO: list = snd_hdspe_controls_aio_pro; limit = ARRAY_SIZE(snd_hdspe_controls_aio_pro); break; default: snd_BUG(); } err = hdspe_add_controls(hdspe, limit, list); if (err < 0) return err; /* Mixer controls, in hdspe_mixer.c */ err = hdspe_create_mixer_controls(hdspe); if (err < 0) return err; /* TCO controls, in hdspe_tco.c */ if (hdspe->tco) { err = hdspe_create_tco_controls(hdspe); if (err < 0) return err; } return 0; } snd-hdspe-1.0.2/sound/pci/hdsp/hdspe/hdspe_control.h000066400000000000000000000374271506037051300223740ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later /** * @file hdspe-control.h * @brief RME HDSPe sound card driver status and control interface helpers. * * 20210728,0907,08,09,10,20220330 - Philippe.Bekaert@uhasselt.be * * Based on earlier work of the other MODULE_AUTHORs, * information kindly made available by RME (www.rme-audio.com), * and hardware kindly made available by Amptec Belgium (www.amptec.be). */ #ifndef _HDSPE_CONTROL_H_ #define _HDSPE_CONTROL_H_ // TODO: when to use locking / use_is_eclusive() ? // TODO: put() argument range checking needed? // TODO: put() return value? always 0, or 1 when changed? #include /** * ENUMERATE_CTL_INFO - enumerated snd_kcontrol_new struct info method * helper. */ #define ENUMERATED_CTL_INFO(info, texts) \ snd_ctl_enum_info(info, 1, ARRAY_SIZE(texts), texts) /** * HDSPE_RO_KCTL - generate a snd_kcontrol_new struct for a read-only * non-volatile property. * @xface: MIXER, CARD, etc... * @xname: display name for the property. * @prop: source code name for the property. */ #define HDSPE_RO_KCTL(xface, xname, prop) \ { .iface = SNDRV_CTL_ELEM_IFACE_##xface, \ .name = xname, \ .access = SNDRV_CTL_ELEM_ACCESS_READ, \ .info = snd_hdspe_info_##prop, \ .get = snd_hdspe_get_##prop \ } /** * HDSPE_RV_KCTL - generate a snd_kcontrol_new struct for a read-only * volatile property. * @xface: MIXER, CARD, etc... * @xname: display name for the property. * @prop: source code name for the property. */ #define HDSPE_RV_KCTL(xface, xname, prop) \ { .iface = SNDRV_CTL_ELEM_IFACE_##xface, \ .name = xname, \ .access = SNDRV_CTL_ELEM_ACCESS_READ | \ SNDRV_CTL_ELEM_ACCESS_VOLATILE, \ .info = snd_hdspe_info_##prop, \ .get = snd_hdspe_get_##prop \ } /** * HDSPE_RW_KCTL - generate a snd_kcontrol_new struct for a read-write property. * @xface: MIXER, CARD, etc... * @xname: display name for the property. * @prop: source code name for the property. */ #define HDSPE_RW_KCTL(xface, xname, prop) \ { .iface = SNDRV_CTL_ELEM_IFACE_##xface, \ .name = xname, \ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ .info = snd_hdspe_info_##prop, \ .get = snd_hdspe_get_##prop, \ .put = snd_hdspe_put_##prop \ } /** * HDSPE_RWV_KCTL - generate a snd_kcontrol_new struct for a read-write * volatile property. * @xface: MIXER, CARD, etc... * @xname: display name for the property. * @prop: source code name for the property. */ #define HDSPE_RWV_KCTL(xface, xname, prop) \ { .iface = SNDRV_CTL_ELEM_IFACE_##xface, \ .name = xname, \ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \ SNDRV_CTL_ELEM_ACCESS_VOLATILE, \ .info = snd_hdspe_info_##prop, \ .get = snd_hdspe_get_##prop, \ .put = snd_hdspe_put_##prop \ } /** * HDSPE_WO_KCTL - generate a snd_kcontrol_new struct for a write-only property. * @xface: MIXER, CARD, etc... * @xname: display name for the property. * @prop: source code name for the property. */ #define HDSPE_WO_KCTL(xface, xname, prop) \ { .iface = SNDRV_CTL_ELEM_IFACE_##xface, \ .name = xname, \ .access = SNDRV_CTL_ELEM_ACCESS_WRITE, \ .info = snd_hdspe_info_##prop, \ .put = snd_hdspe_put_##prop \ } /** * HDSPE_RO_BOOL_KCTL - generate a snd_kcontrol_new struct for a * boolean read-only non-volatile property. * @xface: MIXER, CARD, etc... * @xname: display name for the property. * @prop: source code name for the property. */ #define HDSPE_RO_BOOL_KCTL(xface, xname, prop) \ { .iface = SNDRV_CTL_ELEM_IFACE_##xface, \ .name = xname, \ .access = SNDRV_CTL_ELEM_ACCESS_READ, \ .info = snd_ctl_boolean_mono_info, \ .get = snd_hdspe_get_##prop \ } /** * HDSPE_RV_BOOL_KCTL - generate a snd_kcontrol_new struct for a * boolean read-only volatile property. * @xface: MIXER, CARD, etc... * @xname: display name for the property. * @prop: source code name for the property. */ #define HDSPE_RV_BOOL_KCTL(xface, xname, prop) \ { .iface = SNDRV_CTL_ELEM_IFACE_##xface, \ .name = xname, \ .access = SNDRV_CTL_ELEM_ACCESS_READ | \ SNDRV_CTL_ELEM_ACCESS_VOLATILE, \ .info = snd_ctl_boolean_mono_info, \ .get = snd_hdspe_get_##prop \ } /** * HDSPE_RW_BOOL_KCTL - generate a snd_kcontrol_new struct for a * boolean read-write property. * @xface: MIXER, CARD, etc... * @xname: display name for the property. * @prop: source code name for the property. */ #define HDSPE_RW_BOOL_KCTL(xface, xname, prop) \ { .iface = SNDRV_CTL_ELEM_IFACE_##xface, \ .name = xname, \ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ .info = snd_ctl_boolean_mono_info, \ .get = snd_hdspe_get_##prop, \ .put = snd_hdspe_put_##prop \ } /** * Get the current value for a property * @kcontrol: control element. * @get: getter function - does the real work. * @lock_req: protect get() by spinlock, if true. * @propname: name of the property, for debugging * Returns what get() returns. */ extern int hdspe_control_get(struct snd_kcontrol *kcontrol, int (*get)(struct hdspe*), bool lock_req, const char* propname); /** * Set a property to a new value. * @kcontrol: control element. * @val: value to set. * @get: getter function - gets current value, for change detection. * If NULL, change detection is left to put(). * @put: putter function - does the real work of setting the new value. * @lock_req: protect get() and put() by spinlock, if true. * @excl_req: return -EBUSY if we have no exclusive access, if true. * @propname: name of the property, for debugging * Returns the return code of put() if nonzero. If put() returns zero, * returns 1 if the property changed and 0 otherwise. */ extern int hdspe_control_put(struct snd_kcontrol *kcontrol, int val, int (*get)(struct hdspe*), int (*put)(struct hdspe*, int val), bool lock_req, bool excl_req, const char* propnam); /** * HDSPE_GETTER: create code for a hdspe_get_(struct hdspe* hdspe) * getter function, simply returns hdspe->. * @prop: property name, same as struct hdspe int member name. */ #define HDSPE_GETTER(prop) \ static int hdspe_get_##prop(struct hdspe* hdspe) \ { \ return hdspe->prop; \ } /** * HDSPE_PUTTER: create code for a * hdspe_put_(struct hdspe* hdspe, int val) * putter function, simply setting hdspe-> and returning 1 if * its value changed and 0 if not. * @prop: property name, same as struct hdspe int member name. */ #define HDSPE_PUTTER(prop) \ static int hdspe_put_##prop(struct hdspe* hdspe, int val) \ { \ int changed = val != hdspe->prop; \ hdspe->prop = val; \ return changed; \ } /** * HDSPE_REG_GETTER helpers. */ static inline __attribute__((always_inline)) union hdspe_control_reg hdspe_read_control(struct hdspe* hdspe) { return hdspe->reg.control; } static inline __attribute__((always_inline)) union hdspe_settings_reg hdspe_read_settings(struct hdspe* hdspe) { return hdspe->reg.settings; } /** * HDSPE_REG_GETTER: creates code for a hdspe_get_(struct hdspe* hdspe) * getter function, simply reading hdspe->reg.... * @prop: property name (defines generated function name). * @regname: control, settings, or status0: a field of hdspe->reg. * @model: madi, aes or raio: a field in hdspe->reg.regname. * @field: a bitfield in hdspe->reg.regname.model. * @do_read: if true, read the register from hardware (for status0 register). */ #define HDSPE_REG_GETTER(prop, regname, model, field, do_read) \ static int hdspe_get_##prop(struct hdspe* hdspe) \ { \ if (do_read) hdspe_read_##regname(hdspe); \ return hdspe->reg.regname.model.field; \ } /** * HDSPE_REG_PUTTER: creates code for a * hdspe_put_(struct hdspe* hdspe,int val) putter * function, writing hdspe->reg.... if * it is not changing. * @prop: property name (defines generated function name). * @regname: control or settings: a field of hdspe->reg. (status registers * do not need putters). * @model: madi, aes or raio: a field in hdspe->reg.regname. * @field: a bitfield in hdspe->reg.regname.model. * Returns 1 if the value has changed and 0 if not. */ #define HDSPE_REG_PUTTER(prop, regname, model, field) \ static int hdspe_put_##prop(struct hdspe* hdspe, int val) \ { \ int oldval = hdspe->reg.regname.model.field; \ if (val != oldval) { \ hdspe->reg.regname.model.field = val; \ hdspe_write_##regname(hdspe); \ return 1; \ } \ return 0; \ } /** * HDSPE_ENUM_GET_REG - generate code for a snd_kcontrol_new .get method * named snd_hdspe_get_, essentially just reading a bitfield from a * register. * @prop: property name (defines generated function name). * @regname: control, settings, or status0: a field of hdspe->reg. * @model: madi, aes or raio: a field in hdspe->reg.regname. * @field: a bitfield in hdspe->reg.regname.model. * @lock_req: if nonzero, protect reading by spin_lock_irq(&hdspe->lock). * @do_read: if true, read the register from hardware (for status0 register). * The generated snd_hdspe_get_ method simply gets the indicated * bitfield from the register. */ #define HDSPE_ENUM_GET_REG(prop, regname, model, field, lock_req, do_read) \ HDSPE_REG_GETTER(prop, regname, model, field, do_read) \ static int snd_hdspe_get_##prop(struct snd_kcontrol *kcontrol, \ struct snd_ctl_elem_value *ucontrol) \ { \ ucontrol->value.enumerated.item[0] = \ hdspe_control_get(kcontrol, hdspe_get_##prop, lock_req, #prop); \ return 0; \ } /** * Same, but using a generic int (*getter)(struct hdspe* hdspe). */ #define HDSPE_ENUM_GET(prop, getter, lock_req) \ static int snd_hdspe_get_##prop(struct snd_kcontrol *kcontrol, \ struct snd_ctl_elem_value *ucontrol) \ { \ struct hdspe *hdspe = snd_kcontrol_chip(kcontrol); \ ucontrol->value.enumerated.item[0] = getter(hdspe); \ return 0; \ } /** * HDSPE_ENUM_PUT_REG - generate code for a snd_kcontrol_new .put method * names snd_hdspe_put_, essentially just writing a bitfield in a * register. * @regname: control, settings or status0, one of the fields of * hdspe->reg. * @model: madi, aes or raio, a field in hdspe->reg.regname. * @field: a bitfield in hdspe->reg.regname.card. * @lock_req: if nonzero, protect writing by spin_lock_irq(&hdspe->lock). * @excl_req: if nonzero, return -EBUSY immediately if we have no * exclusive control over the card. * The generated snd_hdspe_put_ method sets the indicated * bitfield in the register and writes the register to hardware if its * value has changed. * The method returns 1 if the value is changed and 0 if not. */ #define HDSPE_ENUM_PUT_REG(prop, regname, model, field, lock_req, excl_req) \ HDSPE_REG_PUTTER(prop, regname, model, field) \ static int snd_hdspe_put_##prop(struct snd_kcontrol *kcontrol, \ struct snd_ctl_elem_value *ucontrol) \ { \ return hdspe_control_put( \ kcontrol, ucontrol->value.enumerated.item[0], \ NULL, hdspe_put_##prop, lock_req, excl_req, #prop); \ } /** * Same, but using a generic int (*getter)(struct hdspe* hdspe) and * void (*putter)(struct hdspe* hdspe, int val). */ #define HDSPE_ENUM_PUT(prop, getter, putter, lock_req, excl_req) \ static int snd_hdspe_put_##prop(struct snd_kcontrol *kcontrol, \ struct snd_ctl_elem_value *ucontrol) \ { \ return hdspe_control_put( \ kcontrol, ucontrol->value.enumerated.item[0], \ getter, putter, lock_req, excl_req, #prop); \ } /** * HDSPE_RW_ENUM_REG_METHODS - generates a snd_kcontrol_new .get and .put * method for read-write control elements one-to-one corresponding to a bit * field in the control or settings register. */ #define HDSPE_RW_ENUM_REG_METHODS(prop, regname, card, field, excl_req) \ HDSPE_ENUM_GET_REG(prop, regname, card, field, true, false) \ HDSPE_ENUM_PUT_REG(prop, regname, card, field, true, excl_req) /** * Same, using a generic getter and putter. */ #define HDSPE_RW_ENUM_METHODS(prop, getter, putter, excl_req) \ HDSPE_ENUM_GET(prop, getter, true) \ HDSPE_ENUM_PUT(prop, getter, putter, true, excl_req) /** * HDSPE_RO_ENUM_REG_METHODS - generates a snd_kcontrol_new .get method * for read-only control elements one-to-one corresponding to a bit * field in a status register. */ #define HDSPE_RO_ENUM_REG_METHODS(prop, regname, card, field) \ HDSPE_ENUM_GET_REG(prop, regname, card, field, false, true) /** * Same, using a generic getter. */ #define HDSPE_RO_ENUM_METHODS(prop, getter) \ HDSPE_ENUM_GET(prop, getter, false) /** * Same, for single channel integer properties instead of enum. */ #define HDSPE_INT1_INFO(prop, minval, maxval, stepval) \ static int snd_hdspe_info_##prop(struct snd_kcontrol *kcontrol, \ struct snd_ctl_elem_info *uinfo) \ { \ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; \ uinfo->count = 1; \ uinfo->value.integer.min = minval; \ uinfo->value.integer.max = maxval; \ uinfo->value.integer.step = stepval; \ return 0; \ } #define HDSPE_INT1_GET(prop, getter, lock_req) \ static int snd_hdspe_get_##prop(struct snd_kcontrol *kcontrol, \ struct snd_ctl_elem_value *ucontrol) \ { \ struct hdspe *hdspe = snd_kcontrol_chip(kcontrol); \ ucontrol->value.integer.value[0] = getter(hdspe); \ return 0; \ } #define HDSPE_INT1_PUT(prop, getter, putter, lock_req, excl_req) \ static int snd_hdspe_put_##prop(struct snd_kcontrol *kcontrol, \ struct snd_ctl_elem_value *ucontrol) \ { \ return hdspe_control_put( \ kcontrol, ucontrol->value.integer.value[0], \ getter, putter, lock_req, excl_req, #prop); \ } #define HDSPE_RO_INT1_METHODS(prop, min, max, step, getter) \ HDSPE_INT1_INFO(prop, min, max, step) \ HDSPE_INT1_GET(prop, getter, false) #define HDSPE_RW_INT1_METHODS(prop, min, max, step, getter, putter, excl_req) \ HDSPE_INT1_INFO(prop, min, max, step) \ HDSPE_INT1_GET(prop, getter, true) \ HDSPE_INT1_PUT(prop, getter, putter, true, excl_req) /** * HDSPE_RW_INT1_HDSPE_METHODS: create methods for reading/writing * the integer struct hdspe member . */ #define HDSPE_RW_INT1_HDSPE_METHODS(prop, min, max, step) \ HDSPE_GETTER(prop) \ HDSPE_PUTTER(prop) \ HDSPE_RW_INT1_METHODS(prop, min, max, step, \ hdspe_get_##prop, hdspe_put_##prop, false) #define HDSPE_RO_INT1_HDSPE_METHODS(prop, min, max, step) \ HDSPE_GETTER(prop) \ HDSPE_RO_INT1_METHODS(prop, min, max, step, hdspe_get_##prop) #define HDSPE_RO_ENUM_HDSPE_METHODS(prop) \ HDSPE_GETTER(prop) \ HDSPE_RO_ENUM_METHODS(prop, hdspe_get_##prop) /** * HDSPE_ADD_CONTROL_ID: generates code for adding a control element, * storing the created snd_ctl_elem_id* in hdspe->cid., for * sending notifications later on. * @nctldecl: struct snd_kcontol_new initializer (e.g. HDSPE_RO_KCTL(...)) * @prop: name of the property, at the same time name of a member * of hdspe->cid. */ #define HDSPE_ADD_CONTROL_ID(nctldecl, prop) \ { \ const struct snd_kcontrol_new nctl = nctldecl; \ int err = hdspe_add_control_id(hdspe, &nctl, &hdspe->cid.prop); \ if (err < 0) \ return err; \ } #define HDSPE_ADD_RO_CONTROL_ID(iface, name, prop) \ HDSPE_ADD_CONTROL_ID(HDSPE_RO_KCTL(iface, name, prop), prop) #define HDSPE_ADD_RV_CONTROL_ID(iface, name, prop) \ HDSPE_ADD_CONTROL_ID(HDSPE_RV_KCTL(iface, name, prop), prop) #define HDSPE_ADD_RO_BOOL_CONTROL_ID(iface, name, prop) \ HDSPE_ADD_CONTROL_ID(HDSPE_RO_BOOL_KCTL(iface, name, prop), prop) #define HDSPE_ADD_RV_BOOL_CONTROL_ID(iface, name, prop) \ HDSPE_ADD_CONTROL_ID(HDSPE_RV_BOOL_KCTL(iface, name, prop), prop) #define HDSPE_ADD_RW_CONTROL_ID(iface, name, prop) \ HDSPE_ADD_CONTROL_ID(HDSPE_RW_KCTL(iface, name, prop), prop) #define HDSPE_ADD_RW_BOOL_CONTROL_ID(iface, name, prop) \ HDSPE_ADD_CONTROL_ID(HDSPE_RW_BOOL_KCTL(iface, name, prop), prop) #define HDSPE_ADD_RWV_CONTROL_ID(iface, name, prop) \ HDSPE_ADD_CONTROL_ID(HDSPE_RWV_KCTL(iface, name, prop), prop) #endif /* _HDSPE_CONTROL_H_ */ snd-hdspe-1.0.2/sound/pci/hdsp/hdspe/hdspe_core.c000066400000000000000000000413011506037051300216210ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later /* * ALSA driver for RME HDSPe MADI/AES/RayDAT/AIO/AIO Pro audio interface(s) * * Copyright (c) 2003 Winfried Ritsch (IEM) * code based on hdsp.c Paul Davis * Marcus Andersson * Thomas Charbonnel * Modified 2006-06-01 for AES support by Remy Bruno * * * Modified 2009-04-13 for proper metering by Florian Faber * * * Modified 2009-04-14 for native float support by Florian Faber * * * Modified 2009-04-26 fixed bug in rms metering by Florian Faber * * * Modified 2009-04-30 added hw serial number support by Florian Faber * * Modified 2011-01-14 added S/PDIF input on RayDATs by Adrian Knoth * * Modified 2011-01-25 variable period sizes on RayDAT/AIO by Adrian Knoth * * Modified 2019-05-23 fix AIO single speed ADAT capture and playback * by Philippe.Bekaert@uhasselt.be * * Modified 2021-07 ... 2021-12 AIO Pro support, fixes, register * documentation, clean up, refactoring, updated user space API, * renamed hdspe, updated control elements, TCO LTC output, double/quad * speed AIO / AIO Pro fixes, ... * by Philippe.Bekaert@uhasselt.be */ #include "hdspe.h" #include "hdspe_core.h" #include #include #include #include #include #include static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;/* Enable this card */ module_param_array(index, int, NULL, 0444); MODULE_PARM_DESC(index, "Index value for RME HDSPE interface."); module_param_array(id, charp, NULL, 0444); MODULE_PARM_DESC(id, "ID string for RME HDSPE interface."); module_param_array(enable, bool, NULL, 0444); MODULE_PARM_DESC(enable, "Enable/disable specific HDSPE soundcards."); MODULE_AUTHOR ( "Winfried Ritsch , " "Paul Davis , " "Marcus Andersson, Thomas Charbonnel , " "Remy Bruno , " "Florian Faber , " "Adrian Knoth , " "Philippe Bekaert " ); MODULE_DESCRIPTION("RME HDSPe"); MODULE_LICENSE("GPL"); // This driver can obsolete old snd-hdspm driver. MODULE_ALIAS("snd-hdspm"); /* RME PCI vendor ID as it is reported by the RME AIO PRO card */ #ifndef PCI_VENDOR_ID_RME #define PCI_VENDOR_ID_RME 0x1d18 #endif /*PCI_VENDOR_ID_RME*/ static const struct pci_device_id snd_hdspe_ids[] = { {.vendor = PCI_VENDOR_ID_XILINX, .device = PCI_DEVICE_ID_XILINX_HAMMERFALL_DSP_MADI, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, .class = 0, .class_mask = 0, .driver_data = 0}, {.vendor = PCI_VENDOR_ID_RME, .device = PCI_DEVICE_ID_XILINX_HAMMERFALL_DSP_MADI, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, .class = 0, .class_mask = 0, .driver_data = 0}, {0,} }; MODULE_DEVICE_TABLE(pci, snd_hdspe_ids); /* interrupt handler */ static irqreturn_t snd_hdspe_interrupt(int irq, void *dev_id) { struct hdspe *hdspe = (struct hdspe *) dev_id; int i, audio, midi, schedule = 0; hdspe->reg.status0 = hdspe_read_status0_nocache(hdspe); audio = hdspe->reg.status0.common.IRQ; midi = hdspe->reg.status0.raw & hdspe->midiIRQPendingMask; #ifdef TIME_INTERRUPT_INTERVAL u64 now = ktime_get_raw_fast_ns(); dev_dbg(hdspe->card->dev, "snd_hdspe_interrupt %llu us LAT=%d BUF_ID=%u BUF_PTR=%05u %s%s%s%s%s\n", (now - hdspe->last_interrupt_time) / 1000, hdspe->reg.control.common.LAT, hdspe->reg.status0.common.BUF_ID, le16_to_cpu(hdspe->reg.status0.common.BUF_PTR)<<6, audio ? "AUDIO " : "", hdspe->midiPorts>0 && (hdspe->reg.status0.raw & hdspe->midi[0].irq) ? "MIDI1 " : "", hdspe->midiPorts>1 && (hdspe->reg.status0.raw & hdspe->midi[1].irq) ? "MIDI2 " : "", hdspe->midiPorts>2 && (hdspe->reg.status0.raw & hdspe->midi[2].irq) ? "MIDI3 " : "", hdspe->midiPorts>3 && (hdspe->reg.status0.raw & hdspe->midi[3].irq) ? "MIDI4 " : "" ); hdspe->last_interrupt_time = now; #endif /*TIME_INTERRUPT_INTERVAL*/ if (!audio && !midi) return IRQ_NONE; if (audio) { hdspe_write(hdspe, HDSPE_interruptConfirmation, 0); hdspe->irq_count++; hdspe_update_frame_count(hdspe); if (hdspe->tco) { /* LTC In update must happen before user * space is notified of a new period */ hdspe_tco_period_elapsed(hdspe); } if (hdspe->capture_substream) snd_pcm_period_elapsed(hdspe->capture_substream); if (hdspe->playback_substream) snd_pcm_period_elapsed(hdspe->playback_substream); /* status polling at user controlled rate */ if (hdspe->status_polling > 0 && jiffies >= hdspe->last_status_jiffies + HZ/hdspe->status_polling) { hdspe->last_status_jiffies = jiffies; schedule_work(&hdspe->status_work); } } if (midi) { schedule = 0; for (i = 0; i < hdspe->midiPorts; i++) { if ((hdspe_read(hdspe, hdspe->midi[i].statusIn) & 0xff) && (hdspe->reg.status0.raw & hdspe->midi[i].irq)) { /* we disable interrupts for this input until * processing is done */ hdspe->reg.control.raw &= ~hdspe->midi[i].ie; hdspe->midi[i].pending = 1; schedule = 1; } } if (schedule) { hdspe_write_control(hdspe); queue_work(system_highpri_wq, &hdspe->midi_work); } } return IRQ_HANDLED; } /* Start audio and TCO MTC interrupts. Other MIDI interrupts * are enabled when the MIDI devices are created. */ static void hdspe_start_interrupts(struct hdspe* hdspe) { if (hdspe->tco) { /* TCO MTC port is always the last one */ struct hdspe_midi *m = &hdspe->midi[hdspe->midiPorts-1]; dev_dbg(hdspe->card->dev, "%s: enabling TCO MTC input port %d '%s'.\n", __func__, m->id, m->portname); hdspe->reg.control.raw |= m->ie; } hdspe->reg.control.common.START = hdspe->reg.control.common.IE_AUDIO = true; hdspe_write_control(hdspe); } static void hdspe_stop_interrupts(struct hdspe* hdspe) { /* stop the audio, and cancel all interrupts */ hdspe->reg.control.common.START = hdspe->reg.control.common.IE_AUDIO = false; hdspe->reg.control.raw &= ~hdspe->midiInterruptEnableMask; hdspe_write_control(hdspe); } /* Create ALSA devices, after hardware initialization */ static int snd_hdspe_create_alsa_devices(struct snd_card *card, struct hdspe *hdspe) { int err, i; dev_dbg(card->dev, "Create ALSA PCM devices ...\n"); err = snd_hdspe_create_pcm(card, hdspe); if (err < 0) return err; dev_dbg(card->dev, "Create ALSA MIDI devices ...\n"); for (i = 0; i < hdspe->midiPorts; i++) { err = snd_hdspe_create_midi(card, hdspe, i); if (err < 0) return err; } dev_dbg(card->dev, "Create ALSA hwdep ...\n"); err = snd_hdspe_create_hwdep(card, hdspe); if (err < 0) return err; dev_dbg(card->dev, "Create ALSA controls ...\n"); err = snd_hdspe_create_controls(card, hdspe); if (err < 0) return err; dev_dbg(card->dev, "Init proc interface...\n"); snd_hdspe_proc_init(hdspe); dev_dbg(card->dev, "Initializing complete?\n"); err = snd_card_register(card); if (err < 0) { dev_err(card->dev, "error registering card.\n"); return err; } dev_dbg(card->dev, "... yes now\n"); return 0; } /* Initialize struct hdspe fields beyond PCI info, hardware vars, firmware * revision and build, serial no, io_type, mixer and TCO. */ static int hdspe_init(struct hdspe* hdspe) { hdspe->pcm = NULL; hdspe->hwdep = NULL; hdspe->capture_substream = hdspe->playback_substream = NULL; hdspe->capture_buffer = hdspe->playback_buffer = NULL; hdspe->capture_pid = hdspe->playback_pid = -1; hdspe->running = false; hdspe->irq_count = 0; // Initialize hardware registers and their cache, card_name, methods, // and tables. hdspe->reg.control.raw = hdspe->reg.settings.raw = hdspe->reg.pll_freq = hdspe->reg.status0.raw = 0; hdspe->reg.control.common.LAT = 6; hdspe->reg.control.common.freq = HDSPE_FREQ_44_1KHZ; hdspe->reg.control.common.LineOut = true; hdspe_write_control(hdspe); switch (hdspe->io_type) { case HDSPE_MADI : case HDSPE_MADIFACE: hdspe_init_madi(hdspe); break; case HDSPE_AES : hdspe_init_aes(hdspe); break; case HDSPE_RAYDAT : case HDSPE_AIO : case HDSPE_AIO_PRO : hdspe_init_raio(hdspe); break; default : snd_BUG(); } hdspe_read_status0_nocache(hdspe); // init reg.status0 hdspe_write_internal_pitch(hdspe, 1000000); // init reg.pll_freq // Set the channel map according the initial speed mode */ hdspe_set_channel_map(hdspe, hdspe_speed_mode(hdspe)); return 0; } static void hdspe_terminate(struct hdspe* hdspe) { switch (hdspe->io_type) { case HDSPE_MADI : case HDSPE_MADIFACE: hdspe_terminate_madi(hdspe); break; case HDSPE_AES : hdspe_terminate_aes(hdspe); break; case HDSPE_RAYDAT : case HDSPE_AIO : case HDSPE_AIO_PRO : hdspe_terminate_raio(hdspe); break; default : snd_BUG(); } } /* get card serial number - for older cards */ static uint32_t snd_hdspe_get_serial_rev1(struct hdspe* hdspe) { uint32_t serial = 0; if (hdspe->io_type == HDSPE_MADIFACE) return 0; serial = (hdspe_read(hdspe, HDSPE_midiStatusIn0)>>8) & 0xFFFFFF; /* id contains either a user-provided value or the default * NULL. If it's the default, we're safe to * fill card->id with the serial number. * * If the serial number is 0xFFFFFF, then we're dealing with * an old PCI revision that comes without a sane number. In * this case, we don't set card->id to avoid collisions * when running with multiple cards. */ if (id[hdspe->dev] || serial == 0xFFFFFF) { serial = 0; } return serial; } /* get card serial number - for newer cards */ static uint32_t snd_hdspe_get_serial_rev2(struct hdspe* hdspe) { uint32_t serial = 0; // TODO: test endianness issues /* get the serial number from the RD_BARCODE{0,1} registers */ int i; union { __le32 dw[2]; char c[8]; } barcode; barcode.dw[0] = hdspe_read(hdspe, HDSPE_RD_BARCODE0); barcode.dw[1] = hdspe_read(hdspe, HDSPE_RD_BARCODE1); for (i = 0; i < 8; i++) { int c = barcode.c[i]; if (c >= '0' && c <= '9') serial = serial * 10 + (c - '0'); } return serial; } /* Get card model. TODO: check against Mac and windows driver */ static enum hdspe_io_type hdspe_get_io_type(int pci_vendor_id, int firmware_rev) { switch (firmware_rev) { case HDSPE_RAYDAT_REV: return HDSPE_RAYDAT; case HDSPE_AIO_REV: return (pci_vendor_id == PCI_VENDOR_ID_RME) ? HDSPE_AIO_PRO : HDSPE_AIO; case HDSPE_MADIFACE_REV: return HDSPE_MADIFACE; default: if ((firmware_rev == 0xf0) || ((firmware_rev >= 0xe6) && (firmware_rev <= 0xea))) { return HDSPE_AES; } else if ((firmware_rev == 0xd2) || ((firmware_rev >= 0xc8) && (firmware_rev <= 0xcf))) { return HDSPE_MADI; } } return HDSPE_IO_TYPE_INVALID; } static int snd_hdspe_create(struct hdspe *hdspe) { struct snd_card *card = hdspe->card; struct pci_dev *pci = hdspe->pci; int err; unsigned long io_extent; hdspe->irq = -1; hdspe->port = 0; hdspe->iobase = NULL; spin_lock_init(&hdspe->lock); INIT_WORK(&hdspe->midi_work, hdspe_midi_work); INIT_WORK(&hdspe->status_work, hdspe_status_work); pci_read_config_word(hdspe->pci, PCI_CLASS_REVISION, &hdspe->firmware_rev); hdspe->vendor_id = pci->vendor; dev_dbg(card->dev, "PCI vendor %04x, device %04x, class revision %x\n", pci->vendor, pci->device, hdspe->firmware_rev); strcpy(card->mixername, "RME HDSPe"); strcpy(card->driver, "HDSPe"); /* Determine card model */ hdspe->io_type = hdspe_get_io_type(hdspe->vendor_id, hdspe->firmware_rev); if (hdspe->io_type == HDSPE_IO_TYPE_INVALID) { dev_err(card->dev, "unknown firmware revision %d (0x%x)\n", hdspe->firmware_rev, hdspe->firmware_rev); return -ENODEV; } /* PCI */ err = pci_enable_device(pci); if (err < 0) return err; #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) err = dma_set_mask(&pci->dev, DMA_BIT_MASK(32)); #else err = pci_set_dma_mask(pci, DMA_BIT_MASK(32)); #endif if (!err) { #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) err = dma_set_coherent_mask(&pci->dev, DMA_BIT_MASK(32)); #else err = pci_set_consistent_dma_mask(pci, DMA_BIT_MASK(32)); #endif } if (err != 0) { dev_err(card->dev, "No suitable DMA addressing support.\n"); return -ENODEV; } pci_set_master(hdspe->pci); /* TODO: mac driver sets PCI latency timer to 255 ??? */ err = pci_request_regions(pci, "hdspe"); if (err < 0) return err; hdspe->port = pci_resource_start(pci, 0); io_extent = pci_resource_len(pci, 0); dev_dbg(card->dev, "grabbed memory region 0x%lx-0x%lx\n", hdspe->port, hdspe->port + io_extent - 1); hdspe->iobase = ioremap(hdspe->port, io_extent); if (!hdspe->iobase) { dev_err(card->dev, "unable to remap region 0x%lx-0x%lx\n", hdspe->port, hdspe->port + io_extent - 1); return -EBUSY; } dev_dbg(card->dev, "remapped region (0x%lx) 0x%lx-0x%lx\n", (unsigned long)hdspe->iobase, hdspe->port, hdspe->port + io_extent - 1); if (request_irq(pci->irq, snd_hdspe_interrupt, IRQF_SHARED, KBUILD_MODNAME, hdspe)) { dev_err(card->dev, "unable to use IRQ %d\n", pci->irq); return -EBUSY; } dev_dbg(card->dev, "use IRQ %d\n", pci->irq); hdspe->irq = pci->irq; card->sync_irq = hdspe->irq; /* Firmware build */ hdspe->fw_build = le32_to_cpu(hdspe_read(hdspe, HDSPE_RD_FLASH)) >> 12; dev_dbg(card->dev, "firmware build %d\n", hdspe->fw_build); /* Serial number */ if (pci->vendor == PCI_VENDOR_ID_RME || hdspe->fw_build >= 200) hdspe->serial = snd_hdspe_get_serial_rev2(hdspe); else hdspe->serial = snd_hdspe_get_serial_rev1(hdspe); dev_dbg(card->dev, "serial nr %08d\n", hdspe->serial); /* Card ID */ if (hdspe->serial != 0) { /* don't set ID if no serial (old PCI card) */ snprintf(card->id, sizeof(card->id), "HDSPe%08d", hdspe->serial); snd_card_set_id(card, card->id); } else { dev_warn(card->dev, "Card ID not set: no serial number.\n"); } /* Mixer */ err = hdspe_init_mixer(hdspe); if (err < 0) return err; /* TCO */ err = hdspe_init_tco(hdspe); if (err < 0) return err; /* Methods, tables, registers */ err = hdspe_init(hdspe); if (err < 0) return err; /* Create ALSA devices */ err = snd_hdspe_create_alsa_devices(card, hdspe); if (err < 0) return err; if (hdspe->io_type != HDSPE_MADIFACE && hdspe->serial != 0) { snprintf(card->shortname, sizeof(card->shortname), "%s_%08d", hdspe->card_name, hdspe->serial); snprintf(card->longname, sizeof(card->longname), "%s S/N %08d at 0x%lx irq %d", hdspe->card_name, hdspe->serial, hdspe->port, hdspe->irq); } else { // TODO: MADIFACE really has no serial nr? snprintf(card->shortname, sizeof(card->shortname), "%s", hdspe->card_name); snprintf(card->longname, sizeof(card->longname), "%s at 0x%lx irq %d", hdspe->card_name, hdspe->port, hdspe->irq); } return 0; } static int snd_hdspe_free(struct hdspe * hdspe) { if (hdspe->port) { hdspe_stop_interrupts(hdspe); cancel_work_sync(&hdspe->midi_work); cancel_work_sync(&hdspe->status_work); hdspe_terminate(hdspe); hdspe_terminate_tco(hdspe); hdspe_terminate_mixer(hdspe); } if (hdspe->irq >= 0) free_irq(hdspe->irq, (void *) hdspe); if (hdspe->iobase) iounmap(hdspe->iobase); if (hdspe->port) pci_release_regions(hdspe->pci); if (pci_is_enabled(hdspe->pci)) pci_disable_device(hdspe->pci); return 0; } static void snd_hdspe_card_free(struct snd_card *card) { struct hdspe *hdspe = card->private_data; if (hdspe) snd_hdspe_free(hdspe); } static int snd_hdspe_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; struct hdspe *hdspe; struct snd_card *card; int err; if (dev >= SNDRV_CARDS) return -ENODEV; if (!enable[dev]) { dev++; return -ENOENT; } err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, sizeof(*hdspe), &card); if (err < 0) return err; card->private_free = snd_hdspe_card_free; hdspe = card->private_data; hdspe->card = card; hdspe->dev = dev; hdspe->pci = pci; err = snd_hdspe_create(hdspe); if (err < 0) goto free_card; err = snd_card_register(card); if (err < 0) goto free_card; pci_set_drvdata(pci, card); dev++; hdspe_start_interrupts(hdspe); return 0; free_card: snd_card_free(card); return err; } static void snd_hdspe_remove(struct pci_dev *pci) { snd_card_free(pci_get_drvdata(pci)); } static struct pci_driver hdspe_driver = { .name = KBUILD_MODNAME, .id_table = snd_hdspe_ids, .probe = snd_hdspe_probe, .remove = snd_hdspe_remove, }; module_pci_driver(hdspe_driver); snd-hdspe-1.0.2/sound/pci/hdsp/hdspe/hdspe_core.h000066400000000000000000001340331506037051300216330ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note /** * @file hdspe_core.h * @brief RME HDSPe driver internal header. * * 20210728 ... 1207 - Philippe.Bekaert@uhasselt.be * * Based on earlier work of the other MODULE_AUTHORs, * information kindly made available by RME (www.rme-audio.com), * and hardware kindly made available by Amptec Belgium (www.amptec.be). */ /* TODO: undefine in production version */ #define DEBUG #define CONFIG_SND_DEBUG //#define TIME_INTERRUPT_INTERVAL #define DAW_MODE //#define PASSTHROUGH_MODE #ifndef __SOUND_HDSPE_CORE_H #define __SOUND_HDSPE_CORE_H #include #include #include #include #include // #define HDSPE_HDSP_REV 60 // HDSPe PCIe/ExpressCard #define HDSPE_MADI_REV 210 // TODO: use #define HDSPE_RAYDAT_REV 211 #define HDSPE_AIO_REV 212 #define HDSPE_MADIFACE_REV 213 #define HDSPE_AES_REV 240 // TODO: use /* --- Write registers. --- These are defined as byte-offsets from the iobase value. */ #define HDSPE_WR_SETTINGS 0 /* RayDAT, AIO, AIO Pro settings */ #define HDSPE_outputBufferAddress 32 #define HDSPE_inputBufferAddress 36 #define HDSPE_WR_FLASH (12*4) /* get serial nr on older firmware */ #define HDSPE_WR_CONTROL 64 /* main control register */ #define HDSPE_interruptConfirmation 96 #define HDSPE_WR_TCO 128 /* Time Code Option control */ #define HDSPE_WR_PLL_FREQ 256 /* for setting arbitrary clock values (DDS feature) */ #define HDSPE_midiDataOut0 352 #define HDSPE_midiDataOut1 356 #define HDSPE_midiDataOut2 368 #define HDSPE_eeprom_wr 384 /* for AES, not used in mac driver */ #define HDSPE_eeprom_rd (97*4) /* Unused */ /* DMA enable for 64 channels, only Bit 0 is relevant */ #define HDSPE_outputEnableBase 512 /* 512-767 input DMA */ #define HDSPE_inputEnableBase 768 /* 768-1023 output DMA */ // TODO: LOOPBACK #define MADI_RECORD_OUTPUT (384*4) /* (384+i)*4 = channel i loopback */ /* 16 page addresses for each of the 64 channels DMA buffer in and out (each 64k=16*4k) Buffer must be 4k aligned (which is default i386 ????) */ #define HDSPE_pageAddressBufferOut 8192 #define HDSPE_pageAddressBufferIn (HDSPE_pageAddressBufferOut+64*16*4) /* Hardware mixer: for each hardware output, there is a hardware input * fader and a software playback fader. Regardless of the actual number of * physical inputs and outputs of a card, the mixer always accomodates * 64 hardware outputs, 64 hardware inputs and 64 software playbacks. * So its size is 64*64*2 values. Each value is an uint16_t stored * into a regular uint32_t i/o mapped register. 0 means mute. 32768 is * unit gain. * Hardware input index i fader for output index o * is at byte address 4*(mixerBase + 128*o + i). * Software playback index p fader for output index o * is at byte address 4*(mixerBase + 128*o + 64 + p). */ #define HDSPE_MADI_mixerBase 32768 /* 32768-65535 for 2x64x64 Fader */ #define HDSPE_MATRIX_MIXER_SIZE 8192 /* = 2*64*64 * 4 Byte => 32kB */ /* --- Read registers. --- These are defined as byte-offsets from the iobase value */ #define HDSPE_RD_STATUS0 0 /* all cards, different interpretation */ #define HDSPE_RD_STATUS1 64 /* RayDAT, AIO, AIO Pro */ #define HDSPE_RD_STATUS2 192 /* all cards, different interpretation */ #define HDSPE_RD_FBITS 128 /* all cards except MADI */ #define HDSPE_RD_TCO 256 /* Time Code Option status */ #define HDSPE_RD_BARCODE0 (104*4) /* serial number part 1, rev2 cards */ #define HDSPE_RD_BARCODE1 (105*4) /* serial number part 2, rev2 cards */ #define HDSPE_RD_FLASH (112*4) /* contains firmware build */ #define HDSPE_RD_PLL_FREQ 512 /* PLL frequency (DDS feature) */ #define HDSPE_midiDataIn0 360 #define HDSPE_midiDataIn1 364 #define HDSPE_midiDataIn2 372 #define HDSPE_midiDataIn3 376 /* status is data bytes in MIDI-FIFO (0-128) */ #define HDSPE_midiStatusOut0 384 #define HDSPE_midiStatusOut1 388 #define HDSPE_midiStatusOut2 400 #define HDSPE_midiStatusIn0 392 #define HDSPE_midiStatusIn1 396 #define HDSPE_midiStatusIn2 404 #define HDSPE_midiStatusIn3 408 /* Peak and RMS level meters are regular i/o-mapped registers, but offset considerably from the rest. the peak registers are reset when read; the least-significant 4 bits are full-scale counters; the actual peak value is in the most-significant 24 bits. */ #define HDSPE_MADI_INPUT_PEAK 4096 #define HDSPE_MADI_PLAYBACK_PEAK 4352 #define HDSPE_MADI_OUTPUT_PEAK 4608 #define HDSPE_MADI_INPUT_RMS_L 6144 #define HDSPE_MADI_PLAYBACK_RMS_L 6400 #define HDSPE_MADI_OUTPUT_RMS_L 6656 #define HDSPE_MADI_INPUT_RMS_H 7168 #define HDSPE_MADI_PLAYBACK_RMS_H 7424 #define HDSPE_MADI_OUTPUT_RMS_H 7680 /* Register definitions. * All registers are 32-bit little endian. */ #pragma scalar_storage_order little-endian /** * WR_CONTROL register (byte offset 64) * * Pos Mask MADI AES RayDAT/AIO/AIO Pro * * 00 1 START START START * 01 2 LAT_0 LAT_0 LAT_0 * 02 4 LAT_1 LAT_1 LAT_1 * 03 8 LAT_2 LAT_2 LAT_2 * 04 10 Master Master * 05 20 IE_AUDIO IE_AUDIO IE_AUDIO * 06 40 freq0 freq0 freq0 * 07 80 freq1 freq1 freq1 * 08 100 freq2 freq2 freq2 * 09 200 PRO * 10 400 tx_64ch EMP * 11 800 AutoInp Dolby * 12 1000 * 13 2000 SyncRef2 * 14 4000 inp_0 * 15 8000 inp_1 * 16 10000 SyncRef0 SyncRef0 * 17 20000 SyncRef1 SyncRef1 * 18 40000 SMUX SMUX * 19 80000 CLR_TMS CLR_TMS * 20 100000 WCK48 WCK48 * 21 200000 IEN2 IEN2 IEN2 (RayDAT TCO) * 22 400000 IEN0 IEN0 IEN0 * 23 800000 IEN1 IEN1 IEN1 (AIO/Pro: TCO) * 24 1000000 LineOut LineOut LineOut * 25 2000000 HDSPe_float_format SyncRef3 HDSPe_float_format * 26 4000000 IEN3 (TCO) DS_DoubleWire * 27 8000000 QS_DoubleWire * 28 10000000 QS_QuadWire * 29 20000000 * 30 40000000 AES_float_format * 31 80000000 freq3 freq3 freq3 * * LAT_{0,1,2}: 3-bit value defining buffer size (and latency): 0=64 samples, * 1=128, ..., 6=4096, 7=8192 on MADI/AES, 32 on RayDAT/AIO/AIO Pro. * */ /* MIDI interrupt enable bitmasks */ #define HDSPE_Midi0InterruptEnable 0x00400000 /* MIDI 0 interrupt enable */ #define HDSPE_Midi1InterruptEnable 0x00800000 /* MIDI 1 interrupt enable */ #define HDSPE_Midi2InterruptEnable 0x00200000 /* MIDI 2 interrupt enable */ #define HDSPE_Midi3InterruptEnable 0x04000000 /* MIDI 3 interrupt enable */ /* Common to all cards */ struct hdspe_control_reg_common { __le32 START : 1, // start engine LAT : 3, // buffer size is 2^n where n is defined by LAT _04 : 1, IE_AUDIO: 1, // what do you think? freq : 2, // internal clock: 1=32KHz, 2=44.1KKhz, 3=48KHz ds : 1, // double speed mode _09 : 1, _10 : 1, _11 : 1, _12 : 1, _13 : 1, _14 : 1, _15 : 1, _16 : 1, _17 : 1, _18 : 1, _19 : 1, _20 : 1, IEN2 : 1, // MIDI 2 interrupt enable IEN0 : 1, // MIDI 0 interrupt enable IEN1 : 1, // MIDI 1 interrupt enable LineOut : 1, // Enable/disable analog output _25 : 1, _26 : 1, _27 : 1, _28 : 1, _29 : 1, _30 : 1, qs : 1; // quad speed mode }; /* MADI */ struct hdspe_control_reg_madi { __le32 START : 1, LAT : 3, Master : 1, // Clock mode: 0=AutoSync, 1=Master IE_AUDIO: 1, freq : 2, ds : 1, _09 : 1, tx_64ch : 1, // Output 64channel MODE=1, 56channelMODE=0 AutoInp : 1, // Auto Input (takeover) == Safe Mode, 0=off, 1=on opt_out : 1, // unused _13 : 1, inp_0 : 1, // Input select 0=optical, 1=coaxial inp_1 : 1, // Unused. Must be 0. SyncRef : 2, // Preferred AutoSync reference: // 0=WCK, 1=MADI, 2=TCO, 3=SyncIn SMUX : 1, // Frame??? CLR_TMS : 1, // Clear AES/SPDIF channel status and track marker bits WCK48 : 1, // Single speed word clock output IEN2 : 1, IEN0 : 1, IEN1 : 1, LineOut : 1, FloatFmt: 1, // 32-bit LE floating point sample format IEN3 : 1, // MIDI 3 interrupt enable (TCO) _27 : 1, _28 : 1, _29 : 1, _30 : 1, qs : 1; }; /* AES */ struct hdspe_control_reg_aes { __le32 START : 1, LAT : 3, Master : 1, // Clock mode: 0=AutoSync, 1=Master IE_AUDIO: 1, freq : 2, ds : 1, PRO : 1, // Professional EMP : 1, // Emphasis Dolby : 1, // Dolby = "NonAudio" _12 : 1, SyncRef2: 1, // Preferred AutoSync ref bit 2. Values: _14 : 1, // 0=WCLK, 1=AES1, 2=AES2, 3=AES3, 4=AES4, _15 : 1, // 5=AES5, 6=AES6, 7=AES7, 8=AES8, 9=TCO, 10=SyncIn SyncRef0: 1, // Preferred AutoSync ref bit 0 SyncRef1: 1, // Preferred AutoSync ref bit 1 SMUX : 1, CLR_TMS : 1, // Clear AES/SPDIF channel status and track marker bits WCK48 : 1, // Single speed word clock output IEN2 : 1, IEN0 : 1, IEN1 : 1, LineOut : 1, SyncRef3: 1, // Preferred AutoSync ref bit 3 ds_mode : 1, // DoubleSpeed mode: 0=single wire, 1=double wire qs_mode : 2, // QuadSpeed mode: 0=single, 1=double, 2=quad wire _29 : 1, FloatFmt: 1, // 32-bit LE floating point sample format qs : 1; }; /* RayDAT / AIO / AIO Pro */ struct hdspe_control_reg_raio { __le32 START : 1, LAT : 3, _4 : 1, IE_AUDIO: 1, freq : 2, ds : 1, _09 : 1, _10 : 1, _11 : 1, _12 : 1, _13 : 1, _14 : 1, _15 : 1, _16 : 1, _17 : 1, _18 : 1, _19 : 1, _20 : 1, IEN2 : 1, IEN0 : 1, IEN1 : 1, LineOut : 1, FloatFmt: 1, // 32-bit LE floating point sample format _26 : 1, _27 : 1, _28 : 1, _29 : 1, _30 : 1, qs : 1; }; union hdspe_control_reg { __le32 raw; // register value struct hdspe_control_reg_common common; // common fields struct hdspe_control_reg_madi madi; // MADI fields struct hdspe_control_reg_aes aes; // AES fields struct hdspe_control_reg_raio raio; // RayDAT/AIO/AIO Pro fields }; /** * WR_SETTINGS register bits (byte offset 0) - RayDAT / AIO / AIO Pro only. */ struct hdspe_settings_reg_raio { __le32 Master : 1, // Clock Master (1) or AutoSync (0) SyncRef : 4, // Preferred AutoSync reference: // 0=WCLK, 1=AES, 2=SPDIF, 3=ADAT/ADAT1 // 4=ADAT2, 5=ADAT3, 6=ADAT4, 9=TCO, 10=SyncIn Wck48 : 1, // Single speed word clock output = Single Speed in win DS_DoubleWire : 1, // win , mac unused QS_DoubleWire : 1, // win , mac unused QS_QuadWire : 1, // win , mac unused Madi_Smux : 1, // win , mac unused Madi_64_Channels : 1, // win , mac unused Madi_AutoInput : 1, // win , mac unused Input : 2, // SPDIF in: 0=Optical, 1=Coaxial, 2=Internal Spdif_Opt : 1, // SPDIF Optical out (ADAT4 optical out on RayDAT) Pro : 1, // SPDIF Professional format out clr_tms : 1, // Clear AES/SPDIF channel status and track marker bits AEB1 : 1, // ADAT1 internal (use with TEB or AEB extension boards) AEB2 : 1, // RayDAT ADAT2 internal, AIO: S/PDIF emphasis?? LineOut : 1, // AIO Pro only (AD/DA/PH power on/off?) AD_GAIN : 2, // Input Level 0-2 (AIO) / 0-3 (AIO Pro) DA_GAIN : 2, // Output Level 0-2 (AIO) / 0-3 (AIO Pro) PH_GAIN : 2, // Phones Level 0-2 (AIO) / 0-1 (AIO Pro) Sym6db : 1, // Analog output: 0=RCA, 1=XLR _27 : 1, _28 : 1, _29 : 1, _30 : 1, _21 : 1; }; union hdspe_settings_reg { __le32 raw; // register value struct hdspe_settings_reg_raio raio; // RayDAT / AIO / AIO Pro bits }; /** * Clock logic: * * If the card is running in master clock mode or no valid AutoSync * reference is present, the internal clock is used ("internal frequency"). * If in AutoSync mode, and a valid AutoSync reference is present, the card * synchronises to that AutoSync reference ("external frequency"). * * In all cases, the RD_PLL_FREQ register indicates at what exact frequency * the card is running, relative to a fixed oscillator frequency that depends * on the type of card - see hdspe_read_system_sample_rate() - and up to * double/quad speed rate multiplication as set in the WR_CONTROL register. * * The frequency class of the internal clock is in the WR_CONTROL register * (see above). When reduced to single speed mode, it shall always correspond * to the PLL frequency rounded to the nearest of 32KHz, 44.1KHz, 48KHz. * If in Master clock mode, the internal clock frequency is fine tuned through * the PLL frequency control register (WR_PLL_FREQ), see * hdspe_write_system_sample_rate(). * * MADI cards provide frequency and lock and sync status of the MADI input * and the external frequency (up to single/double/quad speed rate setting). * The MADI input lock/sync/frequency status is in the RD_STATUS0 register. The * external frequency and selected AutoSync reference is in RD_STATUS2. * lock/sync status of tco and sync in are in the RD_STATUS register, * lock/sync status of the word clock is in the RD_STATUS2 register. * * Other cards provide frequency and lock/sync status of the word clock, tco, * sync in, and all digital audio inputs individually, in different * registers depending on the type of card. * * The frequency class values reported in the status registers are always * 4-bit values, with the following interpretation: * 1=32KHz, 2=44.1KHz, 3=48KHz, * 4=64KHz, 5=88.2KHz, 6=96KHz, * 7=128KHz, 8=176.4KHz, 9=192KHz * other values indicate no lock. * * All cards report whether there is a valid external clock reference or not, * and if so, which reference is used for synchronisation. * The selected AutoSync reference is in different places and has different * semantics for MADI, AES or RayDAT/AIO/AIO Pro. For MADI, it is a 3-bit * value in the RD_STATUS2 register. For other cards it is a 4-bit value. * For AES, it is sync_ref{3,2,1,0} in the RD_STATUS0 register. * For RayDAT/AIO/AIO Pro it is in the RD_STATUS1 register. * * So, MADI cards readily report their external frequency, if in sync. * For other cards, we need to read the selected AutoSync reference. The * external frequency is the frequency of thet AutoSync reference. * * The external frequency thus found needs conversion of the single/double/ * quad speed mode as set in the WR_CONTROL register. */ /** * RD_STATUS0 register (byte offset 0) bits: * * Pos Mask MADI AES RayDAT/AIO/AIO Pro * * 00 1 IRQ IRQ IRQ * 01 2 rx_64ch tco_freq0 * 02 4 AB_int tco_freq1 * 03 8 madi_lock tco_freq2 * 04 10 tco_freq3 * 05 20 madi_tco_lock mIRQ2_AES * FFC0 hw buffer pointer hw buffer pointer hw buffer pointer * 16 10000 madi_sync_in_lock sync_ref0 * 17 20000 madi_sync_in_sync sync_ref1 * 18 40000 madi_sync sync_ref2 * 19 80000 sync_ref3 * 20 100000 wclk_sync * 21 200000 mIRQ3 wclk_lock * 22 400000 F_0 wclk_freq0 * 23 800000 F_1 wclk_freq1 * 24 1000000 F_2 wclk_freq2 * 25 2000000 F_3 wclk_freq3 * 26 4000000 BUF_ID BUF_ID BUF_ID * 27 8000000 tco_detect tco_detect * 28 10000000 tco_sync tco_sync * 29 20000000 mIRQ2 aes_tco_lock mIRQ2 (RayDAT TCO) * 30 40000000 mIRQ0 mIRQ0 mIRQ0 * 31 80000000 mIRQ1 mIRQ1 mIRQ1 (AIO/Pro: TCO) */ /* MIDI interrupt pending */ #define HDSPE_midi0IRQPending 0x40000000 #define HDSPE_midi1IRQPending 0x80000000 #define HDSPE_midi2IRQPending 0x20000000 #define HDSPE_midi2IRQPendingAES 0x00000020 /* AES with TCO */ #define HDSPE_midi3IRQPending 0x00200000 /* MADI with TCO */ /* common */ struct hdspe_status0_reg_common { __le32 IRQ : 1, // Audio interrupt pending _01 : 1, _02 : 1, _03 : 1, _04 : 1, _05 : 1, BUF_PTR : 10, // Most significant bits of the hardware buffer // pointer. Since 64-byte accurate, the least // significant 6 bits are 0. Little endian! _16 : 1, _17 : 1, _18 : 1, _19 : 1, _20 : 1, _21 : 1, _22 : 1, _23 : 1, _24 : 1, _25 : 1, BUF_ID : 1, // (Double) buffer ID, toggles with interrupt _27 : 1, _28 : 1, _29 : 1, mIRQ0 : 1, // MIDI 0 interrupt pending mIRQ1 : 1; // MIDI 1 interrupt pending }; /* MADI */ struct hdspe_status0_reg_madi { __le32 IRQ : 1, rx_64ch : 1, // Input 64chan. MODE=1, 56chn MODE=0 AB_int : 1, // Input channel: 0=optical, 1=coaxial madi_lock : 1, // MADI input lock status _04 : 1, tco_lock : 1, // TCO lock status BUF_PTR : 10, sync_in_lock : 1, // Sync In lock status sync_in_sync : 1, // Sync In sync status madi_sync : 1, // MADI input sync status _19 : 1, _20 : 1, mIRQ3 : 1, // MIDI 3 interrupt pending madi_freq : 4, // MADI input frequency class BUF_ID : 1, tco_detect : 1, // TCO present tco_sync : 1, // TCO is in sync mIRQ2 : 1, // MIDI 2 interrupt pending mIRQ0 : 1, mIRQ1 : 1; }; /* AES */ struct hdspe_status0_reg_aes { __le32 IRQ : 1, tco_freq : 4, // TCO frequency class mIRQ2 : 1, // MIDI 2 interrupt pending BUF_PTR : 10, sync_ref : 4, // active AutoSync reference: 0=WCLK, 1=AES1, // ... 8=AES8, 9=TCO, 10=SyncIn, other=None wc_sync : 1, // word clock sync status wc_lock : 1, // word clock lock status wc_freq : 4, // word clock frequency class BUF_ID : 1, tco_detect : 1, // TCO present tco_sync : 1, // TCO sync status tco_lock : 1, // TCO lock status mIRQ0 : 1, mIRQ1 : 1; }; /* RayDAT / AIO / AIO Pro */ struct hdspe_status0_reg_raio { __le32 IRQ : 1, _01 : 1, _02 : 1, _03 : 1, _04 : 1, _05 : 1, BUF_PTR : 10, _16 : 1, _17 : 1, _18 : 1, _19 : 1, _20 : 1, _21 : 1, _22 : 1, _23 : 1, _24 : 1, _25 : 1, BUF_ID : 1, _27 : 1, _28 : 1, mIRQ2 : 1, // MIDI 2 interrupt pending (RayDAT TCO MIDI) mIRQ0 : 1, mIRQ1 : 1; }; union hdspe_status0_reg { __le32 raw; // register value struct hdspe_status0_reg_common common; // common bits struct hdspe_status0_reg_madi madi; // MADI bits struct hdspe_status0_reg_aes aes; // AES bits struct hdspe_status0_reg_raio raio; // RayDAT/AIO/AIO Pro bits }; /** * RD_STATUS2 register (byte offset 192) * * Pos Mask MADI AES RayDAT/AIO/AIO Pro * * 00 1 spdif_lock7 * 01 2 spdif_lock6 * 02 4 spdif_lock5 * 03 8 wc_lock spdif_lock4 * 04 10 wc_sync spdif_lock3 * 05 20 inp_freq0 spdif_lock2 * 06 40 inp_freq1 spdif_lock1 s2_tco_detect * 07 80 inp_freq2 spdif_lock0 s2_AEBO_D * 08 100 SelSyncRef0 spdif_sync7 s2_AEBI_D * 09 200 SelSyncRef1 spdif_sync6 * 10 400 SelSyncRef2 spdif_sync5 s2_sync_in_lock * 11 800 inp_freq3 spdif_sync4 s2_sync_in_sync * 12 1000 spdif_sync3 s2_sync_in_freq0 * 13 2000 spdif_sync2 s2_sync_in_freq1 * 14 4000 spdif_sync1 s2_sync_in_freq2 * 15 8000 spdif_sync0 s2_sync_in_freq3 * 16 10000 aes_mode0 * 17 20000 aes_mode1 * 18 40000 aes_mode2 * 19 80000 aes_mode3 * 20 100000 sync_in_lock * 21 200000 sync_in_sync * 22 400000 sync_in_freq0 * 23 800000 sync_in_freq1 * 24 1000000 sync_in_freq2 * 25 2000000 sync_in_freq3 */ /* MADI */ struct hdspe_status2_reg_madi { __le32 _00 : 1, _01 : 1, _02 : 1, wc_lock : 1, // Word clock lock status wc_sync : 1, // Word clock sync status inp_freq0 : 1, // AutoSync frequency class (4 bits, scattered) inp_freq1 : 1, // " inp_freq2 : 1, // " sync_ref : 3, // Active AutoSync ref: 0=WCLK, 1=MADI, 2=TCO, inp_freq3 : 1, // 3=Sync In, other=None _12 : 1, _13 : 1, _14 : 1, _15 : 1, __2 : 8, __3 : 8; }; /* AES */ struct hdspe_status2_reg_aes { __le32 lock : 8, // bit 0=AES8, 1=AES7 ... 7=AES1 lock status sync : 8, // bit 0=AES8, 1=AES7 ... 7=AES1 sync status aes_mode : 4, sync_in_lock : 1, // Sync In lock status sync_in_sync : 1, // Sync In sync status sync_in_freq : 4, // Sync In frequency class _26 : 1, _27 : 1, _28 : 1, _29 : 1, _30 : 1, _31 : 1; }; /* RayDAT / AIO / AIO Pro */ struct hdspe_status2_reg_raio { __le32 _00 : 1, _01 : 1, _02 : 1, _03 : 1, _04 : 1, _05 : 1, tco_detect : 1, // TCO detected AEBO_D : 1, // Output Audio Extension Board NOT present AEBI_D : 1, // Input Audio Extension Board NOT present _09 : 1, sync_in_lock : 1, // SyncIn lock status sync_in_sync : 1, // SyncIn sync status sync_in_freq : 4, // SyncIn frequency class __2 : 8, __3 : 8; }; union hdspe_status2_reg { __le32 raw; // register value struct hdspe_status2_reg_madi madi; // MADI fields struct hdspe_status2_reg_aes aes; // AES fields struct hdspe_status2_reg_raio raio; // RayDAT / AIO / AIO Pro fields }; /** * RD_STATUS1 register (byte offset 64) - RayDAT/AIO/AIO Pro only. */ struct hdspe_status1_reg_raio { __le32 lock : 8, // bit 0=AES, 1=SPDIF, 2..5=ADAT1..4 lock status sync : 8, // bit 0=AES, 1=SPDIF, 2..5=ADAT1..4 sync status wc_freq : 4, // word clock frequency class tco_freq : 4, // TCO frequency class wc_lock : 1, // word clock lock status wc_sync : 1, // word clock sync status tco_lock : 1, // TCO lock status tco_sync : 1, // TCO sync status sync_ref : 4; // Active AutoSync ref: 0=WCLK, 1=AES, 2=SPDIF // 3=ADAT1, 4=ADAT2, 5=ADAT3, 6=ADAT4, // 9=TCO, 10=SyncIn, 15=Internal }; union hdspe_status1_reg { __le32 raw; // register value struct hdspe_status1_reg_raio raio; // RayDAT / AIO / AIO Pro fields }; /** * RD_FBITS register (byte offset 192) contains audio input clock rate values * for AES and RayDAT/AIO/AIO Pro digital audio inputs. * * Bit mask AES RayDAT/AIO/AIO Pro * * 0000000f AES1 AES * 000000f0 AES2 SPDIF * 00000f00 AES3 ADAT (AIO/AIO Pro), ADAT1 (RayDAT) * 0000f000 AES4 ADAT2 (RayDAT) * 000f0000 AES5 ADAT3 (RayDAT) * 00f00000 AES6 ADAT4 (RayDAT) * 0f000000 AES7 * f0000000 AES8 * * Frequency values: 1=32KHz, 2=44.1KHz, ... 9=192KHz, other=NoLock */ /* end of register definitions */ #pragma scalar_storage_order default /** * Compose HSDPE_SYNC_STATUS from lock, sync and present bitfields in * the various status registers. */ #define HDSPE_MAKE_SYNC_STATUS(lock, sync, present) \ (!(present) ? HDSPE_SYNC_STATUS_NOT_AVAILABLE \ : !(lock) ? HDSPE_SYNC_STATUS_NO_LOCK \ : !(sync) ? HDSPE_SYNC_STATUS_LOCK \ : HDSPE_SYNC_STATUS_SYNC) /** * Get frequency class from RD_FBITS register */ #define HDSPE_FBITS_FREQ(reg, i) \ (((reg) >> ((i)*4)) & 0xF) /* --------------------------------------------------------- */ /* max. 4 MIDI ports per card */ #define HDSPE_MAX_MIDI 4 struct hdspe_midi { struct timer_list timer; spinlock_t lock; struct hdspe *hdspe; const char* portname; int id; int dataIn; int statusIn; int dataOut; int statusOut; int ie; int irq; struct snd_rawmidi *rmidi; struct snd_rawmidi_substream *input; struct snd_rawmidi_substream *output; int pending; /* interrupt is pending */ int istimer; /* timer in use */ }; //#define DEBUG_LTC //#define DEBUG_MTC struct hdspe_tco { spinlock_t lock; /* cached control registers */ u32 reg[4]; enum hdspe_tco_source input; enum hdspe_ltc_frame_rate ltc_fps; enum hdspe_bool ltc_drop; enum hdspe_tco_sample_rate sample_rate; enum hdspe_pull pull; enum hdspe_wck_conversion wck_conversion; enum hdspe_bool term; enum hdspe_speed wck_out_speed; /* LTC out control */ u32 ltc_out; /* requested start LTC for output */ u64 ltc_out_frame_count; /* start LTC output at this frame count */ bool ltc_set; /* time code set - need reset at next period */ bool ltc_run; /* time code output is running */ bool ltc_flywheel; /* loop back time code output to input */ /* Current LTC in */ bool ltc_changed; /* set when new LTC has been received */ u32 ltc_in; /* current LTC: last parsed LTC + 1 frame */ u64 ltc_time; /* frame_count at start of current period */ u64 ltc_in_frame_count; /* frame count at start of current LTC */ /* for status polling */ struct hdspe_tco_status last_status; /* for measuring the actual LTC In fps and pull factor */ #define LTC_CACHE_SIZE 60 u64 prev_ltc_time; /* nanosecond timestamp of previous MTC irq */ u64 ltc_duration_sum; /* sum of observed LTC frame durations */ u32 ltc_duration[LTC_CACHE_SIZE]; /* observed LTC frame durations */ u32 ltc_count; /* number of received LTC frames */ u32 ltc_in_pullfac; /* actual LTC in pull factor - estimated */ u32 last_ltc_in_pullfac; /* for change notification */ #ifdef DEBUG_MTC u32 mtc; /* current MIDI time code */ #endif /*DEBUG_MTC*/ u8 fw_version; /* TCO firmware version */ }; /** * Card-dependent methods. Initialized by hdspe_init_[madi|aes|raio]. */ struct hdspe_methods { void (*get_card_info)(struct hdspe* hdspe, struct hdspe_card_info* inf); void (*read_status)(struct hdspe* hdspe, struct hdspe_status* status); void (*set_float_format)(struct hdspe* hdspe, bool val); bool (*get_float_format)(struct hdspe* hdspe); void (*read_proc)(struct snd_info_entry*, struct snd_info_buffer*); enum hdspe_clock_source (*get_autosync_ref)(struct hdspe*); enum hdspe_clock_mode (*get_clock_mode)(struct hdspe*); void (*set_clock_mode)(struct hdspe*, enum hdspe_clock_mode); enum hdspe_clock_source (*get_pref_sync_ref)(struct hdspe*); void (*set_pref_sync_ref)(struct hdspe*, enum hdspe_clock_source); bool (*check_status_change)(struct hdspe*, struct hdspe_status* old_status, struct hdspe_status* new_status); }; /** * Card dependant tables. Initialized by hdspe_init_[madi|aes|raio]. */ struct hdspe_tables { /* See hdspe_init_autosync_tables() */ int autosync_count; const char* autosync_texts[HDSPE_CLOCK_SOURCE_COUNT]; enum hdspe_clock_source autosync_idx2ref[HDSPE_CLOCK_SOURCE_COUNT]; int autosync_ref2idx[HDSPE_CLOCK_SOURCE_COUNT]; /* Initialized by hdspe__init() and assigned to * hdspe::port_names_in, hdspe::port_names_out, hdspe::channel_map_in, * hdspe_channel_map_out, hdspe::max_channels_in, * hdspe::max_channels_out by hdspe_set_channel_map(). */ const char * const *port_names_in_ss; const char * const *port_names_in_ds; const char * const *port_names_in_qs; const char * const *port_names_out_ss; const char * const *port_names_out_ds; const char * const *port_names_out_qs; const signed char *channel_map_in_ss, *channel_map_in_ds, *channel_map_in_qs; const signed char *channel_map_out_ss, *channel_map_out_ds, *channel_map_out_qs; unsigned char ss_in_channels; unsigned char ds_in_channels; unsigned char qs_in_channels; unsigned char ss_out_channels; unsigned char ds_out_channels; unsigned char qs_out_channels; const char * const *clock_source_names; }; /* status element ids for status change notification */ struct hdspe_ctl_ids { // TODO: there's probably a better way to query whether // we're running or changing buffer size, without control elements. struct snd_ctl_elem_id* running; struct snd_ctl_elem_id* buffer_size; struct snd_ctl_elem_id* status_polling; struct snd_ctl_elem_id* internal_freq; struct snd_ctl_elem_id* raw_sample_rate; struct snd_ctl_elem_id* dds; struct snd_ctl_elem_id* autosync_ref; struct snd_ctl_elem_id* autosync_status; struct snd_ctl_elem_id* autosync_freq; /* MADI */ struct snd_ctl_elem_id* external_freq; struct snd_ctl_elem_id* madi_input_source; struct snd_ctl_elem_id* madi_rx_64ch; /* TCO */ struct snd_ctl_elem_id* ltc_in; struct snd_ctl_elem_id* ltc_valid; struct snd_ctl_elem_id* ltc_in_fps; struct snd_ctl_elem_id* ltc_in_drop; struct snd_ctl_elem_id* ltc_in_pullfac; struct snd_ctl_elem_id* video; struct snd_ctl_elem_id* wck_valid; struct snd_ctl_elem_id* wck_speed; struct snd_ctl_elem_id* tco_lock; struct snd_ctl_elem_id* ltc_run; struct snd_ctl_elem_id* ltc_jam_sync; struct snd_ctl_elem_id* video_in_fps; /* struct snd_ctl_elem_id* wck_out_rate; */ }; struct hdspe { struct pci_dev *pci; /* pci info */ int vendor_id; /* PCI vendor ID: Xilinx or RME */ int dev; /* hardware vars... */ int irq; unsigned long port; void __iomem *iobase; u16 firmware_rev; /* determines io_type (card model) */ u16 reserved; u32 fw_build; /* firmware build */ u32 serial; /* serial nr */ enum hdspe_io_type io_type; /* MADI, AES, RAYDAT, AIO or AIO_PRO */ char *card_name; /* for procinfo */ struct hdspe_methods m; /* methods for the card model */ struct hdspe_tables t; /* tables for the card model */ /* ALSA devices */ struct snd_card *card; /* one card */ struct snd_pcm *pcm; /* has one pcm */ struct snd_hwdep *hwdep; /* and a hwdep for additional ioctl */ /* Only one playback and/or capture stream */ struct snd_pcm_substream *capture_substream; struct snd_pcm_substream *playback_substream; /* MIDI */ struct hdspe_midi midi[HDSPE_MAX_MIDI]; struct work_struct midi_work; __le32 midiInterruptEnableMask; __le32 midiIRQPendingMask; int midiPorts; /* number of MIDI ports */ /* Check for status changes, status_polling times per second, if >0. * Status polling is disabled if 0. * Initially, it is 0 and needs to be enabled by the client. * hdspe_status_work() resets it to 0 when detecting a change, * notifying the client with a "Status Polling" * control notification event and notifications for all * changed status control elements. * Status polling is also automatically disabled by the driver, * with "Status Polling" notification, after 2 seconds without * changes. The client must re-enable by setting "Status Polling" * to a non-zero value to re-enable it. */ int status_polling; struct work_struct status_work; unsigned long last_status_jiffies; unsigned long last_status_change_jiffies; struct hdspe_status last_status; struct hdspe_ctl_ids cid; /* control ids to be notified */ /* Mixer vars */ /* full mixer accessible over mixer ioctl or hwdep-device */ struct hdspe_mixer *mixer; struct hdspe_peak_rms peak_rms; /* fast alsa mixer */ struct snd_kcontrol *playback_mixer_ctls[HDSPE_MAX_CHANNELS]; /* but input to much, so not used */ struct snd_kcontrol *input_mixer_ctls[HDSPE_MAX_CHANNELS]; /* Optional Time Code Option module handle (NULL if absent) */ struct hdspe_tco *tco; #ifdef DEBUG_LTC struct timer_list tco_timer; #endif /*DEBUG_LTC*/ /* Channel map and port names - set by hdspe_set_channel_map() */ unsigned char max_channels_in; unsigned char max_channels_out; const signed char *channel_map_in; const signed char *channel_map_out; const char * const *port_names_in; const char * const *port_names_out; unsigned char *playback_buffer; /* suitably aligned address */ unsigned char *capture_buffer; /* suitably aligned address */ pid_t capture_pid; /* process id which uses capture */ pid_t playback_pid; /* process id which uses capture */ int running; /* running status */ spinlock_t lock; int irq_count; /* for debug */ #ifdef TIME_INTERRUPT_INTERVAL u64 last_interrupt_time; #endif /*TIME_INTERRUPT_INTERVAL*/ /* Register cache */ struct reg { union hdspe_control_reg control; union hdspe_settings_reg settings; __le32 pll_freq; union hdspe_status0_reg status0; /* read at every interrupt */ } reg; u64 frame_count; /* current period frame counter */ u32 hw_pointer_wrap_count; /* hw pointer wrapped this many times */ u32 last_hw_pointer; /* previous period hw pointer */ u32 hw_buffer_size; /* sample buffer size, in nr of samples */ u32 period_size; /* current period size, in nr of samples */ }; /** * Write/read to/from HDSPE with Adresses in Bytes * not words but only 32Bit writes are allowed. */ static inline __attribute__((always_inline)) void hdspe_write(struct hdspe * hdspe, u32 reg, __le32 val) { writel(val, hdspe->iobase + reg); } static inline __attribute__((always_inline)) __le32 hdspe_read(struct hdspe * hdspe, u32 reg) { return readl(hdspe->iobase + reg); #ifdef FROM_WIN_DRIVER if (!deviceExtension->bShutdown) { value = READ_REGISTER_ULONG(deviceExtension->MemBase+offset); if (value == 0xFFFFFFFF && !(offset >= 256 && offset < 320)) { // surprise removal value = 0; } #endif } static inline __attribute__((always_inline)) void hdspe_write_control(struct hdspe* hdspe) { hdspe_write(hdspe, HDSPE_WR_CONTROL, hdspe->reg.control.raw); } static inline __attribute__((always_inline)) void hdspe_write_settings(struct hdspe* hdspe) { hdspe_write(hdspe, HDSPE_WR_SETTINGS, hdspe->reg.settings.raw); } static inline __attribute__((always_inline)) void hdspe_write_pll_freq(struct hdspe* hdspe) { hdspe_write(hdspe, HDSPE_WR_PLL_FREQ, hdspe->reg.pll_freq); } // status0 register is read at every interrupt if audio is started and // audio interrupt enabled. We make hdspe_read_status() return the // value of the status register at the time of the interrupt in that // case, thus reducing the number of times the hardware is read to a // minimum as well as reducing the need for locking. static inline __attribute__((always_inline)) union hdspe_status0_reg hdspe_read_status0_nocache(struct hdspe* hdspe) { union hdspe_status0_reg reg; reg.raw = hdspe_read(hdspe, HDSPE_RD_STATUS0); return reg; } static inline __attribute__((always_inline)) bool hdspe_is_running(struct hdspe* hdspe) { return hdspe->reg.control.common.START && hdspe->reg.control.common.IE_AUDIO; } static inline __attribute__((always_inline)) union hdspe_status0_reg hdspe_read_status0(struct hdspe* hdspe) { return hdspe_is_running(hdspe) ? hdspe->reg.status0 : hdspe_read_status0_nocache(hdspe); } static inline __attribute__((always_inline)) union hdspe_status1_reg hdspe_read_status1(struct hdspe* hdspe) { union hdspe_status1_reg reg; reg.raw = hdspe_read(hdspe, HDSPE_RD_STATUS1); return reg; } static inline __attribute__((always_inline)) union hdspe_status2_reg hdspe_read_status2(struct hdspe* hdspe) { union hdspe_status2_reg reg; reg.raw = hdspe_read(hdspe, HDSPE_RD_STATUS2); return reg; } static inline __attribute__((always_inline)) u32 hdspe_read_fbits(struct hdspe* hdspe) { return le32_to_cpu(hdspe_read(hdspe, HDSPE_RD_FBITS)); } static inline __attribute__((always_inline)) u32 hdspe_read_pll_freq(struct hdspe* hdspe) { return le32_to_cpu(hdspe_read(hdspe, HDSPE_RD_PLL_FREQ)); } /** * hdspe_pcm.c */ extern int snd_hdspe_create_pcm(struct snd_card *card, struct hdspe *hdspe); /* Current period size in samples. */ extern u32 hdspe_period_size(struct hdspe *hdspe); /* Get current hardware frame counter. Wraps every 16K frames or sooner * for MADI and AES cards. */ extern snd_pcm_uframes_t hdspe_hw_pointer(struct hdspe *hdspe); /* Called right from the interrupt handler in order to update * hdspe->frame_count. * In absence of xruns, the frame counter increments by * hdspe_period_size() frames each period. This routine will correctly * determine the frame counter even in the presence of xruns or late * interrupt handling, as long as the hardware pointer did not wrap more * than once since the previous invocation. */ extern void hdspe_update_frame_count(struct hdspe* hdspe); /** * hdspe_midi.c */ extern void hdspe_init_midi(struct hdspe* hdspe, int count, struct hdspe_midi *list); extern void hdspe_terminate_midi(struct hdspe* hdspe); extern int snd_hdspe_create_midi(struct snd_card *card, struct hdspe *hdspe, int id); extern void hdspe_midi_work(struct work_struct *work); /** * hdspe_hwdep.c */ extern int snd_hdspe_create_hwdep(struct snd_card *card, struct hdspe *hdspe); extern void hdspe_get_card_info(struct hdspe* hdspe, struct hdspe_card_info *s); /** * hdspe_proc.c */ extern void snd_hdspe_proc_init(struct hdspe *hdspe); /* Read hdspe_status from hardware and prints properties common to all * HDSPe cards, to be done before printing card-specific state. */ extern void hdspe_proc_read_common(struct snd_info_buffer *buffer, struct hdspe* hdspe, struct hdspe_status* s); /* Proc read common stuff to be done after printing card-specific state */ void hdspe_proc_read_common2(struct snd_info_buffer *buffer, struct hdspe* hdspe, struct hdspe_status* s); /* Prints the fields of the FBITS register */ extern void hdspe_iprint_fbits(struct snd_info_buffer *buffer, const char* name, u32 fbits); /* Prints the bits that are on in a register. */ extern void hdspe_iprintf_reg(struct snd_info_buffer *buffer, const char* name, __le32 reg, const char* const *bitNames); #ifdef CONFIG_SND_DEBUG #define IPRINTREG(buffer, name, reg, bitNames) \ hdspe_iprintf_reg(buffer, name, reg, bitNames); #else /* not CONFIG_SND_DEBUG */ #define IPRINTREG(buffer, name, reg, bitNames) \ hdspe_iprintf_reg(buffer, name, reg, 0); #endif /* CONFIG_SND_DEBUG */ /** * hdspe_control.c */ extern void hdspe_init_autosync_tables(struct hdspe* hdspe, int nr_autosync_opts, enum hdspe_clock_source* autosync_opts); extern int snd_hdspe_create_controls(struct snd_card *card, struct hdspe *hdspe); extern int hdspe_add_controls(struct hdspe *hdspe, int count, const struct snd_kcontrol_new *list); extern struct snd_kcontrol* hdspe_add_control(struct hdspe* hdspe, const struct snd_kcontrol_new* newctl); /* Same, filling in control element snd_ctl_elem_id* in ctl_id */ extern int hdspe_add_control_id(struct hdspe* hdspe, const struct snd_kcontrol_new* nctl, struct snd_ctl_elem_id** ctl_id); extern void hdspe_status_work(struct work_struct* work); #define HDSPE_CTL_NOTIFY(prop) \ snd_ctl_notify(hdspe->card, SNDRV_CTL_EVENT_MASK_VALUE, \ hdspe->cid.prop); /** * hdspe_mixer.c */ extern int hdspe_init_mixer(struct hdspe* hdspe); extern void hdspe_terminate_mixer(struct hdspe* hdspe); extern int hdspe_create_mixer_controls(struct hdspe* hdspe); #ifdef OLDSTUFF extern int hdspe_update_simple_mixer_controls(struct hdspe * hdspe); #endif /*OLDSTUFF*/ extern void hdspe_mixer_read_proc(struct snd_info_entry *entry, struct snd_info_buffer *buffer); extern void hdspe_mixer_update_channel_map(struct hdspe* hdspe); /** * hdspe_tco.c */ extern int hdspe_init_tco(struct hdspe* hdspe); extern void hdspe_terminate_tco(struct hdspe* hdspe); extern int hdspe_create_tco_controls(struct hdspe* hdspe); extern void hdspe_tco_read_status(struct hdspe* hdspe, struct hdspe_tco_status* status); extern void snd_hdspe_proc_read_tco(struct snd_info_entry *entry, struct snd_info_buffer *buffer); /* Called from the MIDI input handler, whenever an MTC message comes in */ extern void hdspe_tco_mtc(struct hdspe* hdspe, const u8* data, int count); /* Scheduled from the audio interrupt handler */ extern void hdspe_tco_period_elapsed(struct hdspe* hdspe); /* TCO module status polling */ extern bool hdspe_tco_notify_status_change(struct hdspe* hdspe); /* Set "app" sample rate on TCO module, when sound card sample rate changes. */ extern void hdspe_tco_set_app_sample_rate(struct hdspe* hdspe); /** * hdspe_common.c */ /* Get name of the clock source */ extern const char* const hdspe_clock_source_name(struct hdspe* hdspe, int i); /* Get name of the frequency class */ extern const char* const hdspe_freq_name(enum hdspe_freq f); /* Get frequency class frame rate */ extern u32 hdspe_freq_sample_rate(enum hdspe_freq f); /* Get the frequency class best representing the given rate */ enum hdspe_freq hdspe_sample_rate_freq(u32 rate); /* Sets the channel map and port names according the indicated speed mode. */ extern void hdspe_set_channel_map(struct hdspe* hdspe, enum hdspe_speed speed); /* Get the current internal frequency class (single speed * frequency and speed mode control register bits encoded into * a single frequency class value. */ extern enum hdspe_freq hdspe_internal_freq(struct hdspe* hdspe); /* Write the internal frequency (single speed frequency and speed * mode control register bits). No checks, no channel map update. * Returns 1 if changed and 0 if not. */ extern int hdspe_write_internal_freq(struct hdspe* hdspe, enum hdspe_freq f); /* Get the current speed mode */ extern enum hdspe_speed hdspe_speed_mode(struct hdspe* hdspe); /* Get the current speed factor: 1, 2 or 4 */ extern int hdspe_speed_factor(struct hdspe* hdspe); /* Convert frequency class to speed mode */ extern enum hdspe_freq hdspe_speed_adapt(enum hdspe_freq f, enum hdspe_speed speed_mode); /* Get WR_PLL_FREQ (DDS) register value valid range (controls internal sampe * rate in the range 27000 ... 207000/4 Hz) */ extern void hdspe_dds_range(struct hdspe* hdspe, u32* ddsmin, u32* ddsmax); /* Get DDS register value */ extern u32 hdspe_get_dds(struct hdspe*); /* Check and write DDS register value. Return -EINVAL if dds is out * of range, 0 if no change, 1 if change. */ extern int hdspe_write_dds(struct hdspe*, u32 dds); /*Writes the desired internal clock pitch to the WR_PLL_FREQ. * Pitch is in parts per milion relative to the current control registers * single speed frequency setting. If ppm is 1000000, the cards internal * clock will run at exactly the internal frequency set in the control * register. If pitch < 1000000, the card will run slower. If > 1000000 it * will run faster. Returns 1 if changed and 0 if not. */ extern int hdspe_write_internal_pitch(struct hdspe* hdspe, int ppm); /* Return the cached value of the internal pitch. */ extern u32 hdspe_internal_pitch(struct hdspe* hdspe); /* Reads effective system pitch from the RD_PLL_FREQ register, converting * to parts per milion relative to the controls registers single speed * frequency setting. */ extern u32 hdspe_read_system_pitch(struct hdspe* hdspe); /* Read effective system sample rate from hardware. This may differ * from 32KHz, 44.1KHz, etc... because of DDS (non-neutral system pitch). */ extern u32 hdspe_read_system_sample_rate(struct hdspe* hdspe); /* Read current system sample rate from hardware - read_status() helper. */ extern void hdspe_read_sample_rate_status(struct hdspe* hdspe, struct hdspe_status* status); /* Set arbitray sample rate in the cards range, writing the control * register single speed frequency closest to the desired rate, * the speed mode corresponding with the desired rate, and the pll_freq * register. Forbids speed mode change if there are processes capturing or * playing back. Sets the channel map according to the desired speed mode * if allowed and rate differs from current. * Returns: * -EBUSY : forbidden speed mode change. * 0 : desired rate is same as current. * 1 : new rate set. */ extern int hdspe_set_sample_rate(struct hdspe * hdspe, u32 desired_rate); /* Check if same process is writing and reading. */ static inline __attribute__((always_inline)) int snd_hdspe_use_is_exclusive(struct hdspe *hdspe) { unsigned long flags; int ret = 1; spin_lock_irqsave(&hdspe->lock, flags); if ((hdspe->playback_pid != hdspe->capture_pid) && (hdspe->playback_pid >= 0) && (hdspe->capture_pid >= 0)) { ret = 0; } spin_unlock_irqrestore(&hdspe->lock, flags); return ret; } /* read_status() helper. */ static inline __attribute__((always_inline)) void hdspe_set_sync_source(struct hdspe_status* status, int i, enum hdspe_freq freq, bool lock, bool sync, bool present) { status->freq[i] = freq; status->sync[i] = HDSPE_MAKE_SYNC_STATUS(lock, sync, present); } /** * hdspe_madi.c */ extern int hdspe_init_madi(struct hdspe* hdspe); extern void hdspe_terminate_madi(struct hdspe* hdspe); extern enum hdspe_freq hdspe_madi_get_external_freq(struct hdspe* hdspe); /** * hdspe_aes.c */ extern int hdspe_init_aes(struct hdspe* hdspe); extern void hdspe_terminate_aes(struct hdspe* hdspe); /** * hdspe_raio.c */ extern int hdspe_init_raio(struct hdspe* hdspe); extern void hdspe_terminate_raio(struct hdspe* hdspe); #endif /* __SOUND_HDSPE_CORE_H */ snd-hdspe-1.0.2/sound/pci/hdsp/hdspe/hdspe_hwdep.c000066400000000000000000000171101506037051300220010ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later /** * hdspe_hwdep.c * @brief RME HDSPe driver HWDEP interface. * * 20210728 - Philippe.Bekaert@uhasselt.be * 20210810,12 - PhB : new card info ioctl. * 20211125 - PhB : IOCTL_GET_CONFIG reimplemented in terms of hdspe_status. * * Refactored work of the other MODULE_AUTHORs. */ #include "hdspe.h" #include "hdspe_core.h" #include #ifdef OLDSTUFF /* AutoSync external sync source frequency class. Returns 0 if * no valid external reference. */ static enum hdspe_freq hdspe_autosync_freq(struct hdspe *hdspe) { struct hdspe_status status; hdspe->m.read_status(hdspe, &status); return status.external_freq; } #endif /*OLDSTUFF*/ static int snd_hdspe_hwdep_dummy_op(struct snd_hwdep *hw, struct file *file) { /* we have nothing to initialize but the call is required */ return 0; } static inline int copy_u32_le(void __user *dest, void __iomem *src) { u32 val = readl(src); return copy_to_user(dest, &val, 4); } void hdspe_get_card_info(struct hdspe* hdspe, struct hdspe_card_info *s) { s->version = HDSPE_VERSION; s->card_type = hdspe->io_type; s->serial = hdspe->serial; s->fw_rev = hdspe->firmware_rev; s->fw_build = hdspe->fw_build; s->irq = hdspe->irq; s->port = hdspe->port; s->vendor_id = hdspe->vendor_id; s->expansion = 0; if (hdspe->tco) s->expansion |= HDSPE_EXPANSION_TCO; } static int snd_hdspe_hwdep_ioctl(struct snd_hwdep *hw, struct file *file, unsigned int cmd, unsigned long arg) { void __user *argp = (void __user *)arg; struct hdspe *hdspe = hw->private_data; struct hdspe_mixer_ioctl mixer; struct hdspe_config info; struct hdspe_version hdspe_version; struct hdspe_peak_rms *levels; #ifdef OLDSTUFF struct hdspe_ltc ltc; #endif /*OLDSTUFF*/ struct hdspe_status status; struct hdspe_card_info card_info; struct hdspe_tco_status tco_status; long unsigned int s; int i = 0; switch (cmd) { case SNDRV_HDSPE_IOCTL_GET_CARD_INFO: hdspe->m.get_card_info(hdspe, &card_info); if (copy_to_user(argp, &card_info, sizeof(struct hdspe_card_info))) return -EFAULT; break; case SNDRV_HDSPE_IOCTL_GET_STATUS: hdspe->m.read_status(hdspe, &status); if (copy_to_user(argp, &status, sizeof(struct hdspe_status))) return -EFAULT; break; case SNDRV_HDSPE_IOCTL_GET_LTC: if (!hdspe->tco) { dev_dbg(hdspe->card->dev, "%s: %d: EINVAL\n", __func__, __LINE__); return -EINVAL; } else { hdspe_tco_read_status(hdspe, &tco_status); } if (copy_to_user(argp, &tco_status, sizeof(struct hdspe_tco_status))) return -EFAULT; break; case SNDRV_HDSPE_IOCTL_GET_PEAK_RMS: levels = &hdspe->peak_rms; for (i = 0; i < HDSPE_MAX_CHANNELS; i++) { levels->input_peaks[i] = readl(hdspe->iobase + HDSPE_MADI_INPUT_PEAK + i*4); levels->playback_peaks[i] = readl(hdspe->iobase + HDSPE_MADI_PLAYBACK_PEAK + i*4); levels->output_peaks[i] = readl(hdspe->iobase + HDSPE_MADI_OUTPUT_PEAK + i*4); levels->input_rms[i] = ((uint64_t) readl(hdspe->iobase + HDSPE_MADI_INPUT_RMS_H + i*4) << 32) | (uint64_t) readl(hdspe->iobase + HDSPE_MADI_INPUT_RMS_L + i*4); levels->playback_rms[i] = ((uint64_t)readl(hdspe->iobase + HDSPE_MADI_PLAYBACK_RMS_H+i*4) << 32) | (uint64_t)readl(hdspe->iobase + HDSPE_MADI_PLAYBACK_RMS_L + i*4); levels->output_rms[i] = ((uint64_t)readl(hdspe->iobase + HDSPE_MADI_OUTPUT_RMS_H + i*4) << 32) | (uint64_t)readl(hdspe->iobase + HDSPE_MADI_OUTPUT_RMS_L + i*4); } levels->speed = hdspe_speed_mode(hdspe); levels->status2 = hdspe_read_status2(hdspe).raw; s = copy_to_user(argp, levels, sizeof(*levels)); if (0 != s) { /* dev_err(hdspe->card->dev, "copy_to_user(.., .., %lu): %lu [Levels]\n", sizeof(struct hdspe_peak_rms), s); */ return -EFAULT; } break; #ifdef OLDSTUFF case SNDRV_HDSPE_IOCTL_GET_LTC: ltc.ltc = hdspe_read(hdspe, HDSPE_RD_TCO); i = hdspe_read(hdspe, HDSPE_RD_TCO + 4); if (i & HDSPE_TCO1_LTC_Input_valid) { switch (i & (HDSPE_TCO1_LTC_Format_LSB | HDSPE_TCO1_LTC_Format_MSB)) { case 0: ltc.format = fps_24; break; case HDSPE_TCO1_LTC_Format_LSB: ltc.format = fps_25; break; case HDSPE_TCO1_LTC_Format_MSB: ltc.format = fps_2997; break; default: ltc.format = fps_30; break; } if (i & HDSPE_TCO1_set_drop_frame_flag) { ltc.frame = drop_frame; } else { ltc.frame = full_frame; } } else { ltc.format = format_invalid; ltc.frame = frame_invalid; } if (i & HDSPE_TCO1_Video_Input_Format_NTSC) { ltc.input_format = ntsc; } else if (i & HDSPE_TCO1_Video_Input_Format_PAL) { ltc.input_format = pal; } else { ltc.input_format = no_video; } s = copy_to_user(argp, <c, sizeof(ltc)); if (0 != s) { /* dev_err(hdspe->card->dev, "copy_to_user(.., .., %lu): %lu [LTC]\n", sizeof(struct hdspe_ltc), s); */ return -EFAULT; } #endif /*OLDSTUFF*/ dev_warn(hdspe->card->dev, "IOCTL_GET_LTC: TBD.\n"); break; case SNDRV_HDSPE_IOCTL_GET_CONFIG: memset(&info, 0, sizeof(info)); spin_lock_irq(&hdspe->lock); hdspe->m.read_status(hdspe, &status); info.pref_sync_ref = status.preferred_ref; info.wordclock_sync_check = status.sync[HDSPE_CLOCK_SOURCE_WORD]; snd_BUG_ON(status.sample_rate_denominator == 0); info.system_sample_rate = div_u64(status.sample_rate_numerator, status.sample_rate_denominator); info.autosync_sample_rate = hdspe_freq_sample_rate(status.external_freq); info.system_clock_mode = status.clock_mode; info.clock_source = status.internal_freq; info.autosync_ref = status.autosync_ref; info.line_out = hdspe->reg.control.common.LineOut; info.passthru = 0; #ifdef OLDSTUFF info.pref_sync_ref = hdspe->m.get_pref_sync_ref(hdspe); info.wordclock_sync_check = hdspe->m.get_sync_status( hdspe, HDSPE_CLOCK_SOURCE_WORD); info.system_sample_rate = hdspe_read_system_sample_rate(hdspe); info.autosync_sample_rate = hdspe_freq_sample_rate( hdspe_autosync_freq(hdspe)); info.system_clock_mode = hdspe->m.get_clock_mode(hdspe); info.clock_source = hdspe_internal_freq(hdspe); info.autosync_ref = hdspe->m.get_autosync_ref(hdspe); info.line_out = hdspe->reg.control.common.LineOut; info.passthru = 0; #endif /*OLDSTUFF*/ spin_unlock_irq(&hdspe->lock); if (copy_to_user(argp, &info, sizeof(info))) return -EFAULT; break; case SNDRV_HDSPE_IOCTL_GET_VERSION: memset(&hdspe_version, 0, sizeof(hdspe_version)); hdspe_version.card_type = hdspe->io_type; strscpy(hdspe_version.cardname, hdspe->card_name, sizeof(hdspe_version.cardname)); hdspe_version.serial = hdspe->serial; hdspe_version.firmware_rev = hdspe->firmware_rev; hdspe_version.addons = 0; if (hdspe->tco) hdspe_version.addons |= HDSPE_ADDON_TCO; if (copy_to_user(argp, &hdspe_version, sizeof(hdspe_version))) return -EFAULT; break; case SNDRV_HDSPE_IOCTL_GET_MIXER: if (copy_from_user(&mixer, argp, sizeof(mixer))) return -EFAULT; if (copy_to_user((void __user *)mixer.mixer, hdspe->mixer, sizeof(*mixer.mixer))) return -EFAULT; break; default: dev_dbg(hdspe->card->dev, "%s: %d: cmd=%u EINVAL\n", __func__, __LINE__, cmd); return -EINVAL; } return 0; } int snd_hdspe_create_hwdep(struct snd_card *card, struct hdspe *hdspe) { struct snd_hwdep *hw; int err; err = snd_hwdep_new(card, "HDSPE hwdep", 0, &hw); if (err < 0) return err; hdspe->hwdep = hw; hw->private_data = hdspe; strcpy(hw->name, "HDSPE hwdep interface"); hw->ops.open = snd_hdspe_hwdep_dummy_op; hw->ops.ioctl = snd_hdspe_hwdep_ioctl; hw->ops.ioctl_compat = snd_hdspe_hwdep_ioctl; hw->ops.release = snd_hdspe_hwdep_dummy_op; return 0; } snd-hdspe-1.0.2/sound/pci/hdsp/hdspe/hdspe_ltc_math.c000066400000000000000000000241501506037051300224670ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later /** * hdspe_ltc_math.c * @brief RME HDSPe 32-bit LTC code optimised math for TCO module. * * 20210930,1001,08 - Philippe.Bekaert@uhasselt.be */ #include "hdspe_ltc_math.h" int hdspe_ltc_fpd(int fps, int df) { return df ? 24*107892 : 24*60*60*fps; } void hdspe_ltc32_parse(u32 ltc, int *h, int *m, int *s, int *f) { *h = ((ltc >> 28) & 0x03)*10 + ((ltc >> 24) & 0x0f); *m = ((ltc >> 20) & 0x07)*10 + ((ltc >> 16) & 0x0f); *s = ((ltc >> 12) & 0x07)*10 + ((ltc >> 8) & 0x0f); *f = ((ltc >> 4) & 0x03)*10 + ((ltc >> 0) & 0x0f); } u32 hdspe_ltc32_compose(int h, int m, int s, int f) { int H = h/10; int M = m/10; int S = s/10; int F = f/10; h -= H*10; m -= M*10; s -= S*10; f -= F*10; return ((H&0x03)<<28)|((h&0x0f)<<24)|((M&0x07)<<20)|((m&0x0f)<<16)| ((S&0x07)<<12)|((s&0x0f)<< 8)|((F&0x03)<< 4)|((f&0x0f) ); } int hdspe_ltc32_cmp(u32 ltc1, u32 ltc2) { return (int)(ltc1 & 0x3f7f7f3f) - (int)(ltc2 & 0x3f7f7f3f); } /** * hdspe_ltc32_to_frames_df: Convert LTC to frames since midnight, for * drop-frame 32-bit LTC. * @ltc: 32-bit ltc code to convert to frames since midnight. */ static unsigned int hdspe_ltc32_to_frames_df(u32 ltc) { return ((ltc >> 28) & 0x3) * 1078920 + ((ltc >> 24) & 0xf) * 107892 + ((ltc >> 20) & 0x7) * 17982 + ((ltc >> 16) & 0xf) * 1798 + ((ltc >> 12) & 0x7) * 300 + ((ltc >> 8) & 0xf) * 30 + ((ltc >> 4) & 0x3) * 10 + ((ltc ) & 0xf); } /** * hdspe_ltc32_to_frames_ndf: Convert LTC to frames since midnight, * for non-drop-frame 32-bit LTC. * @ltc: 32-bit LTC code to convert. * @fps: LTC frames per second. */ static unsigned int hdspe_ltc32_to_frames_ndf(u32 ltc, int fps) { return (((ltc >> 28) & 0x3) * 36000 + ((ltc >> 24) & 0xf) * 3600 + ((ltc >> 20) & 0x7) * 600 + ((ltc >> 16) & 0xf) * 60 + ((ltc >> 12) & 0x7) * 10 + ((ltc >> 8) & 0xf)) * fps + ((ltc >> 4) & 0x3) * 10 + ((ltc ) & 0xf); } unsigned int hdspe_ltc32_to_frames(u32 ltc, int fps, int df) { return df ? hdspe_ltc32_to_frames_df(ltc) : hdspe_ltc32_to_frames_ndf(ltc, fps); } u32 hdspe_ltc32_from_frames(int frames, int fps, int df) { int H, h, M, m, S, s, F, f; int fpd = hdspe_ltc_fpd(fps, df); f = frames % fpd; if (f < 0) f += fpd; if (!df) { s = f / fps; f -= s * fps; F = f / 10; f -= F * 10; m = s / 60; s -= m * 60; S = s / 10; s -= S * 10; h = m / 60; m -= h * 60; M = m / 10; m -= M * 10; H = h / 10; h -= H * 10; } else { H = f / 1078920; f -= H * 1078920; h = f / 107892; f -= h * 107892; M = f / 17982; f -= M * 17982; if (f < 1800) { m = 0; S = f / 300; f -= S * 300; s = f / 30; f -= s * 30; F = f / 10; f -= F * 10; } else { f -= 1800; m = f / 1798; f -= m * 1798 - 2; m ++; S = f / 300; f -= S * 300; s = f / 30; f -= s * 30; F = f / 10; f -= F * 10; } } return ((H&3)<<28)|(h<<24)|(M<<20)|(m<<16)|(S<<12)|(s<<8)|(F<<4)|f; } u32 hdspe_ltc32_decr(u32 tci, int fps, int df) { u32 tco = 0; int f = tci & 0xf; f --; if (df && f < 2 && (tci & 0x00007ff0) == 0 && (tci & 0x000f0000) != 0) { f -= 2; } if (f < 0) { int F = (tci >> 4) & 0x3; F --; f = 9; if (F < 0) { int s = (tci >> 8) & 0xf; s --; F = (fps-1)/10; f = fps-1 - F*10; if (s < 0) { int S = (tci >> 12) & 0x7; S --; s = 9; if (S < 0) { int m = (tci >> 16) & 0xf; m --; S = 5; if (m < 0) { int M = (tci >> 20) & 0x7; M --; m = 9; if (M < 0) { int h = (tci >> 24) & 0xf; h --; M = 5; if (h < 0) { int H = (tci >> 28) & 0x3; H --; h = 9; if (H < 0) { H = 2; h = 3; } tco = H << 28; } else { tco = tci & 0x30000000; } tco |= h << 24; } else { tco = tci & 0x3f000000; } tco |= M << 20; } else { tco = tci & 0x3f700000; } tco |= m << 16; } else { tco = tci & 0x3f7f0000; } tco |= S << 12; } else { tco = tci & 0x3f7f7000; } tco |= s << 8; } else { tco = tci & 0x3f7f7f00; } tco |= F << 4; } else { tco = tci & 0x3f7f7f30; } return tco | f; } u32 hdspe_ltc32_incr(u32 tci, int fps, int df) { int tco = 0; int f = (tci >> 0) & 0xf; int F = (tci >> 4) & 0x3; f ++; if (f >= 10) { F ++; f = 0; } if (10*F + f >= fps) { int s = (tci >> 8) & 0xf; s ++; F = f = 0; if (s >= 10) { int S = (tci >> 12) & 0x7; S ++; s = 0; if (S >= 6) { int m = (tci >> 16) & 0xf; m ++; S = 0; if (m >= 10) { int M = (tci >> 20) & 0x7; M ++; m = 0; if (M >= 6) { int h = (tci >> 24) & 0xf; int H = (tci >> 28) & 0x3; h ++; M = 0; if (h >= 10) { H ++; h = 0; } if (10*H + h >= 24) { H = h = 0; } tco |= (H << 28)|(h << 24); } else { tco = tci & 0x3f000000; } tco |= (M << 20); } else { if (df) f = 2; tco = tci & 0x3f700000; } tco |= (m << 16); } else { tco = tci & 0x3f7f0000; } tco |= (S << 12); } else { tco = tci & 0x3f7f7000; } tco |= (s << 8); } else { tco = tci & 0x3f7f7f00; } tco |= (F << 4)|(f << 0); return tco; } int hdspe_ltc32_running(u32 ltc1, u32 ltc2, int fps, int df) { if (((ltc1+1) & 0x3f7f7f3f) == (ltc2 & 0x3f7f7f3f) || hdspe_ltc32_cmp(hdspe_ltc32_incr(ltc1, fps, df), ltc2) == 0) return +1; if (hdspe_ltc32_cmp(hdspe_ltc32_incr(ltc2, fps, df), ltc1) == 0) return -1; return 0; } u32 hdspe_ltc32_add_frames(int n, u32 ltc, int fps, int df) { int frames = hdspe_ltc32_to_frames(ltc, fps, df); return hdspe_ltc32_from_frames(frames+n, fps, df); } unsigned int hdspe_ltc32_diff_frames(u32 ltc1, u32 ltc2, int fps, int df) { int frames1 = hdspe_ltc32_to_frames(ltc1, fps, df); int frames2 = hdspe_ltc32_to_frames(ltc2, fps, df); int diff = frames1 - frames2; int fpd = hdspe_ltc_fpd(fps, df); diff = diff % fpd; return diff < 0 ? diff + fpd : diff; } #ifdef UNIT_TESTING ///////////////////////////////////////////////////////////////////////////// // Unit testing. #include #include #include #include int test_compose_parse(int h, int m, int s, int f, int fps, int df) { int h1, m1, s1, f1; u32 ltc = hdspe_ltc32_compose(h, m, s, f); hdspe_ltc32_parse(ltc, &h1, &m1, &s1, &f1); return ltc_cmp(h, m, s, f, h1, m1, s1, f1) == 0; } int test_to_from_frames_32(int h, int m, int s, int f, int fps, int df) { u32 ltc = hdspe_ltc32_compose(h, m, s, f); int frames = hdspe_ltc32_to_frames(ltc, fps, df); u32 ltc1 = hdspe_ltc32_from_frames(frames, fps, df); return hdspe_ltc32_cmp(ltc, ltc1) == 0; } int test_incr_decr_32(int h, int m, int s, int f, int fps, int df) { u32 ltc = hdspe_ltc32_compose(h, m, s, f); int frames = hdspe_ltc32_to_frames(ltc, fps, df); u32 ltc1 = hdspe_ltc32_incr(ltc, fps, df); int frames1 = hdspe_ltc32_to_frames(ltc1, fps, df); if (frames1 != frames+1) { fprintf(stderr, "%08x %d %sfps (frames=%d) +1 = %08x (frames %d)\n", ltc, fps, df ? "d" : "", frames, ltc1, frames1); } u32 ltc2 = hdspe_ltc32_add_frames(1, ltc, fps, df); if (hdspe_ltc32_cmp(ltc1, ltc2) != 0) { fprintf(stderr, "hdspe_ltc32_add_frames +1: %08x != %08x\n", ltc2, ltc1); return 0; } ltc2 = hdspe_ltc32_add_frames(-1, ltc2, fps, df); if (hdspe_ltc32_cmp(ltc, ltc2) != 0) { fprintf(stderr, "hdspe_ltc32_add_frames -1: %08x - 1 = %08x != %08x\n", ltc1, ltc2, ltc); return 0; } ltc2 = hdspe_ltc32_decr(ltc1, fps, df); if (hdspe_ltc32_cmp(ltc, ltc2) != 0) { fprintf(stderr, "hdspe_ltc32_decr: %08x -> %08x != %08x\n", ltc1, ltc2, ltc); return 0; } return 1; } int test_add_diff_single_32(int n, int h, int m, int s, int f, int fps, int df) { u32 ltc = hdspe_ltc32_compose(h, m, s, f); u32 ltc1 = hdspe_ltc32_add_frames(n, ltc, fps, df); int diff = hdspe_ltc32_diff_frames(ltc1, ltc, fps, df); int fpd = hdspe_ltc_fpd(fps, df); int expectdiff = n >= 0 ? n : n + fpd; if (diff != expectdiff) { fprintf(stderr, "hdspe_ltc32_add_frames: %08x + %d = %08x - . = %d != %d.\n", ltc, n, ltc1, diff, n); return 0; } int running = hdspe_ltc32_running(ltc, ltc1, fps, df); int expect = (n == 1)||(n == -fpd+1) ? 1 : (n == -1)||(n == fpd-1) ? -1 : 0; if (running != expect) { fprintf(stderr, "hdspe_ltc32_running: n=%d, %08x -> %08x = %d != %d.\n", n, ltc, ltc1, running, expect); return 0; } return 1; } int test_add_diff_32(int h, int m, int s, int f, int fps, int df) { int n[] = { 0, 1, fps, fps*60, fps*3600, lrand48() % hdspe_ltc_fpd(fps, df) }; for (int i=0; i /** * hdspe_ltc_fpd: Frames per day. * @fps: LTC frames per second (24,25,30). * @df: 30DF drop frame LTC format (TRUE or FALSE). * Returns number of frames in a full day. */ extern int hdspe_ltc_fpd(int fps, int df); /** * hdspe_ltc32_parse: Parse 32-bit LTC code into hours, minutes, seconds and * frames. * @ltc: LTC code to parse. * @h: hours. * @m: minutes. * @s seconds. * @f: frames. */ extern void hdspe_ltc32_parse(u32 ltc, int *h, int *m, int *s, int *f); /** * hdspe_ltc32_compose: Convert hours, minutes, seconds and frames quadruple * into a 32-bit LTC code. * @h: hours 0 .. 23 * @m: minutes 0 .. 59 * @s seconds 0 .. 59 * @f: frames 0 .. @fps-1 * Returns 32-bit LTC code. */ extern u32 hdspe_ltc32_compose(int h, int m, int s, int f); /** * hdspe_ltc32_cmp: Compare two 32-bit LTC codes. * @ltc1, @ltc2: two LTC codes to compare. * Returns >0 if @ltc1 > @ltc2, <0 if @ltc1 < @ltc2 and 0 if equal. */ extern int hdspe_ltc32_cmp(u32 ltc1, u32 ltc2); /** * hdspe_ltc32_to_frames: Convert 32-bit LTC code to frames since midnight. * @ltc: LTC code to convert * @fps: LTC frames per second (24,25,30). * @df: 30DF drop frame LTC format (TRUE or FALSE). * Returns number of frames since midnight. */ extern unsigned int hdspe_ltc32_to_frames(u32 ltc, int fps, int df); /** * hdspe_ltc32_from_frames: Convert frame count since midnight to * 32-bit LTC code. * @frames: number of frames since midnight. If negative or larger than * fpd(), it is brought into 0 ... fpd()-1 range. * @fps: LTC frames per second (24,25,30). * @df: 30DF drop frame LTC format (TRUE or FALSE). * Returns 32-bit LTC code. */ extern u32 hdspe_ltc32_from_frames(int frames, int fps, int df); /** * hdspe_ltc32_decr: Decrement 32-bit LTC by one frame. * @ltc: LTC code to decrement. Assumed to be in 0 ... fpd()-1 range. * @fps: LTC frames per second (24,25,30). * @df: 30DF drop frame LTC format (TRUE or FALSE). * Returns 32-bit LTC code, one frame less than @ltc. Returns 23:59:59:@fps-1 * if @ltc is 00:00:00:00. */ extern u32 hdspe_ltc32_decr(u32 ltc, int fps, int df); /** * hdspe_ltc32_incr: Increment 32-bit LTC by one frame. * @ltc: LTC code to increment. Assumed to be in 0 ... fpd()-1 range. * @fps: LTC frames per second (24,25,30). * @df: 30DF drop frame LTC format (TRUE or FALSE). * Returns 32-bit LTC code, one frame ahead of @ltc. Returns 00:00:00:00 * if @ltc is 23:59:59:@fps-1. */ extern u32 hdspe_ltc32_incr(u32 ltc, int fps, int df); /** * hdspe_ltc32_running: LTC running direction. * @ltc1, @ltc2 : 32-bit LTC codes to compare. * @fps: LTC frames per second (24,25,30). * @df: 30DF drop frame LTC format (TRUE or FALSE). * Returns +1 if ltc2 is one frame ahead of ltc1, -1 if ltc2 is * one frame before ltc1, and 0 in other cases. Use this to determine LTC * running direction (1 = forward, -1 = backward, 0 = stationary or jumping). */ extern int hdspe_ltc32_running(u32 ltc1, u32 ltc2, int fps, int df); /** * hdspe_ltc32_add_frames: add n frames to 32-bit LTC code. * @n: number of frames to add (if positive) or subtract (if negative). * @ltc: 32-bit LTC code to add frames to. * @fps: LTC frames per second (24,25,30). * @df: 30DF drop frame LTC format (TRUE or FALSE). * Returns @ltc + @n frames. LTC code wraps from 23:59:59:@fps-1 to * 00:00:00:00 and back. */ extern u32 hdspe_ltc32_add_frames(int n, u32 ltc, int fps, int df); /** * hdspe_ltc32_diff_frames: return LTC code difference in frames. * @ltc1, @ltc2: LTC codes to subtract. * @fps: LTC frames per second (24,25,30). * @df: 30DF drop frame LTC format (TRUE or FALSE). * Returns @ltc1 - @ltc2 in frames. Return value is always in the range * 0 ... fpd()-1. */ extern unsigned int hdspe_ltc32_diff_frames(u32 ltc1, u32 ltc2, int fps, int df); #endif /* HDSPE_LTC_MATH_H */ snd-hdspe-1.0.2/sound/pci/hdsp/hdspe/hdspe_madi.c000066400000000000000000000442151506037051300216120ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later /** * hdspe_madi.c * @brief RME HDSPe MADI driver methods. * * 20210728,1207 - Philippe.Bekaert@uhasselt.be * * Based on earlier work of the other MODULE_AUTHORS, * information kindly made available by RME (www.rme-audio.com), * and hardware kindly made available by Amptec Belgium (www.amptec.be). */ #include "hdspe.h" #include "hdspe_core.h" /* Map MADI WR_CONTROL / RD_STATUS2 sync ref 3-bit code to hdspe_autosync_ref * enum */ static enum hdspe_clock_source madi_autosync_ref[8] = { HDSPE_CLOCK_SOURCE_WORD, HDSPE_CLOCK_SOURCE_MADI, HDSPE_CLOCK_SOURCE_TCO, HDSPE_CLOCK_SOURCE_SYNC_IN, HDSPE_CLOCK_SOURCE_INTERN, // unused codes HDSPE_CLOCK_SOURCE_INTERN, HDSPE_CLOCK_SOURCE_INTERN, HDSPE_CLOCK_SOURCE_INTERN // master clock mode }; const char* const hdspe_madi_clock_source_names[HDSPE_CLOCK_SOURCE_COUNT] = { HDSPE_CLOCK_SOURCE_NAME(HDSPE_MADI, 0), HDSPE_CLOCK_SOURCE_NAME(HDSPE_MADI, 1), HDSPE_CLOCK_SOURCE_NAME(HDSPE_MADI, 2), HDSPE_CLOCK_SOURCE_NAME(HDSPE_MADI, 3), HDSPE_CLOCK_SOURCE_NAME(HDSPE_MADI, 4), HDSPE_CLOCK_SOURCE_NAME(HDSPE_MADI, 5), HDSPE_CLOCK_SOURCE_NAME(HDSPE_MADI, 6), HDSPE_CLOCK_SOURCE_NAME(HDSPE_MADI, 7), HDSPE_CLOCK_SOURCE_NAME(HDSPE_MADI, 8), HDSPE_CLOCK_SOURCE_NAME(HDSPE_MADI, 9), HDSPE_CLOCK_SOURCE_NAME(HDSPE_MADI, 10), HDSPE_CLOCK_SOURCE_NAME(HDSPE_MADI, 11), HDSPE_CLOCK_SOURCE_NAME(HDSPE_MADI, 12), HDSPE_CLOCK_SOURCE_NAME(HDSPE_MADI, 13), HDSPE_CLOCK_SOURCE_NAME(HDSPE_MADI, 14), HDSPE_CLOCK_SOURCE_NAME(HDSPE_MADI, 15), }; /* Number of channels for different Speed Modes */ #define MADI_SS_CHANNELS 64 #define MADI_DS_CHANNELS 32 #define MADI_QS_CHANNELS 16 /* port names */ static const char * const texts_ports_madi[] = { "MADI.1", "MADI.2", "MADI.3", "MADI.4", "MADI.5", "MADI.6", "MADI.7", "MADI.8", "MADI.9", "MADI.10", "MADI.11", "MADI.12", "MADI.13", "MADI.14", "MADI.15", "MADI.16", "MADI.17", "MADI.18", "MADI.19", "MADI.20", "MADI.21", "MADI.22", "MADI.23", "MADI.24", "MADI.25", "MADI.26", "MADI.27", "MADI.28", "MADI.29", "MADI.30", "MADI.31", "MADI.32", "MADI.33", "MADI.34", "MADI.35", "MADI.36", "MADI.37", "MADI.38", "MADI.39", "MADI.40", "MADI.41", "MADI.42", "MADI.43", "MADI.44", "MADI.45", "MADI.46", "MADI.47", "MADI.48", "MADI.49", "MADI.50", "MADI.51", "MADI.52", "MADI.53", "MADI.54", "MADI.55", "MADI.56", "MADI.57", "MADI.58", "MADI.59", "MADI.60", "MADI.61", "MADI.62", "MADI.63", "MADI.64", }; /* These tables map the ALSA channels 1..N to the channels that we need to use in order to find the relevant channel buffer. RME refers to this kind of mapping as between "the ADAT channel and the DMA channel." We index it using the logical audio channel, and the value is the DMA channel (i.e. channel buffer number) where the data for that channel can be read/written from/to. */ static const char channel_map_unity_ss[HDSPE_MAX_CHANNELS] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63 }; #ifdef CONFIG_SND_DEBUG /* WR_CONTROL register bit names for MADI card */ static const char* const madi_control_bitNames[32] = { "START", "LAT_0", "LAT_1", "LAT_2", "Master", "IE_AUDIO", "freq0", "freq1", "freq2", "?09", "tx_64ch", "AutoInp", "opt_out", "SyncRef2", "inp_0", "inp_1", "SyncRef0", "SyncRef1", "SMUX", "CLR_TMS", "WCK48", "IEN2", "IEN0", "IEN1", "LineOut", "HDSPe_float_format", "IEN3", "?27", "?28", "?29", "?30", "freq3" }; /* RD_STATUS2 register bit names for MADI */ static const char* const madi_status2_bitNames[32] = { "?00", "?01", "?02", "wc_lock", "wc_sync", "inp_freq0", "inp_freq1", "inp_freq2", "SelSyncRef0", "SelSyncRef1", "SelSyncRef2", "inp_freq3", "?12", "?13", "?14", "?15", "?16", "?17", "?18", "?19", "?20", "?21", "?22", "?23", "?24", "?25", "?26", "?27", "?28", "?29", "?30", "?31" }; #endif /*CONFIG_SND_DEBUG*/ static void hdspe_madi_read_status(struct hdspe* hdspe, struct hdspe_status* status) { int i, inp_freq; struct hdspe_control_reg_madi control = hdspe->reg.control.madi; struct hdspe_status0_reg_madi status0 = hdspe_read_status0(hdspe).madi; struct hdspe_status2_reg_madi status2 = hdspe_read_status2(hdspe).madi; status->version = HDSPE_VERSION; hdspe_read_sample_rate_status(hdspe, status); status->clock_mode = control.Master; status->internal_freq = hdspe_internal_freq(hdspe); status->speed_mode = hdspe_speed_mode(hdspe); status->preferred_ref = madi_autosync_ref[control.SyncRef]; status->autosync_ref = madi_autosync_ref[status2.sync_ref]; for (i = 0; i < HDSPE_CLOCK_SOURCE_COUNT; i++) { hdspe_set_sync_source(status, i, 0, false, false, false); } hdspe_set_sync_source(status, HDSPE_CLOCK_SOURCE_WORD, 0, /* MADI doesn't report word clock frequency class */ status2.wc_lock, status2.wc_sync, true); hdspe_set_sync_source(status, HDSPE_CLOCK_SOURCE_MADI, status0.madi_freq, status0.madi_lock, status0.madi_sync, true); hdspe_set_sync_source(status, HDSPE_CLOCK_SOURCE_TCO, 0, /* MADI doesn't report TCO frequency class */ status0.tco_lock, status0.tco_sync, status0.tco_detect); hdspe_set_sync_source(status, HDSPE_CLOCK_SOURCE_SYNC_IN, 0, /* MADI doesn't report SyncIn frequency class */ status0.sync_in_lock, status0.sync_in_sync, true); /* MADI reports only the frequency class of the active AutoSync ref. */ inp_freq = (status2.inp_freq3 << 3) | (status2.inp_freq2 << 2) | (status2.inp_freq1 << 1) | (status2.inp_freq0 << 0); status->external_freq = hdspe_speed_adapt(inp_freq, status->speed_mode); status->wck48 = control.WCK48; status->clr_tms = control.CLR_TMS; // MADI specific settings status->madi.input_select = control.inp_0; status->madi.auto_select = control.AutoInp; status->madi.tx_64ch = control.tx_64ch; status->madi.smux = control.SMUX; // MADI specific status status->madi.input_source = status0.AB_int; status->madi.rx_64ch = status0.rx_64ch; } /* set 32-bit float sample format if val is true, s32le format if false */ static void hdspe_madi_set_float_format(struct hdspe* hdspe, bool val) { hdspe->reg.control.madi.FloatFmt = val; hdspe_write_control(hdspe); } static bool hdspe_madi_get_float_format(struct hdspe* hdspe) { return hdspe->reg.control.madi.FloatFmt; } static enum hdspe_clock_mode hdspe_madi_get_clock_mode(struct hdspe *hdspe) { return hdspe->reg.control.madi.Master; } static void hdspe_madi_set_clock_mode(struct hdspe* hdspe, enum hdspe_clock_mode master) { hdspe->reg.control.madi.Master = master; hdspe_write_control(hdspe); } static enum hdspe_clock_source hdspe_madi_get_preferred_sync_ref( struct hdspe* hdspe) { return madi_autosync_ref[hdspe->reg.control.madi.SyncRef]; } static void hdspe_madi_set_preferred_sync_ref(struct hdspe* hdspe, enum hdspe_clock_source ref) { int madi_syncref_value[HDSPE_CLOCK_SOURCE_COUNT] = { 0, // WCLK 1, // MADI 0, 0, 0, 0, 0, 0, 0, // invalid, map to WCLK (default) 2, // TCO 3, // SyncIn 0, 0, 0, 0, 0 // invalid, map to WCLK (default) }; hdspe->reg.control.madi.SyncRef = madi_syncref_value[ref]; hdspe_write_control(hdspe); } static enum hdspe_clock_source hdspe_madi_get_autosync_ref(struct hdspe* hdspe) { return madi_autosync_ref[hdspe_read_status2(hdspe).madi.sync_ref]; } enum hdspe_freq hdspe_madi_get_external_freq(struct hdspe* hdspe) { struct hdspe_status2_reg_madi status2 = hdspe_read_status2(hdspe).madi; enum hdspe_freq inp_freq = (status2.inp_freq3 << 3) | (status2.inp_freq2 << 2) | (status2.inp_freq1 << 1) | (status2.inp_freq0 << 0); return hdspe_speed_adapt(inp_freq, hdspe_speed_mode(hdspe)); } static bool hdspe_madi_check_status_change(struct hdspe* hdspe, struct hdspe_status* o, struct hdspe_status* n) { bool changed = false; if (n->external_freq != o->external_freq && hdspe->cid.external_freq) { dev_dbg(hdspe->card->dev, "external freq changed %d -> %d.\n", o->external_freq, n->external_freq); HDSPE_CTL_NOTIFY(external_freq); changed = true; } if (n->madi.input_source != o->madi.input_source) { dev_dbg(hdspe->card->dev, "input source changed %d -> %d\n", o->madi.input_source, n->madi.input_source); HDSPE_CTL_NOTIFY(madi_input_source); changed = true; } if (n->madi.rx_64ch != o->madi.rx_64ch) { dev_dbg(hdspe->card->dev, "rx_64ch changed %d -> %d\n", o->madi.rx_64ch, n->madi.rx_64ch); HDSPE_CTL_NOTIFY(madi_rx_64ch); changed = true; } return changed; } static void hdspe_madi_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) { struct hdspe *hdspe = entry->private_data; struct hdspe_status s; hdspe_proc_read_common(buffer, hdspe, &s); snd_iprintf(buffer, "Preferred input\t\t: %d %s\n", s.madi.input_select, HDSPE_MADI_INPUT_NAME(s.madi.input_select)); snd_iprintf(buffer, "Auto input\t\t: %d %s\n", s.madi.auto_select, HDSPE_BOOL_NAME(s.madi.auto_select)); snd_iprintf(buffer, "Current input\t\t: %d %s\n", s.madi.input_source, HDSPE_MADI_INPUT_NAME(s.madi.input_source)); snd_iprintf(buffer, "Tx 64Ch\t\t\t: %d %s\n", s.madi.tx_64ch, HDSPE_BOOL_NAME(s.madi.tx_64ch)); snd_iprintf(buffer, "Rx 64Ch\t\t\t: %d %s\n", s.madi.rx_64ch, HDSPE_BOOL_NAME(s.madi.rx_64ch)); snd_iprintf(buffer, "S/Mux\t\t\t: %d %s\n", s.madi.smux, HDSPE_BOOL_NAME(s.madi.smux)); snd_iprintf(buffer, "\n"); IPRINTREG(buffer, "CONTROL", hdspe->reg.control.raw, madi_control_bitNames); { union hdspe_status2_reg status2 = hdspe_read_status2(hdspe); u32 fbits = hdspe_read_fbits(hdspe); IPRINTREG(buffer, "STATUS2", status2.raw, madi_status2_bitNames); hdspe_iprint_fbits(buffer, "FBITS", fbits); } hdspe_proc_read_common2(buffer, hdspe, &s); #ifdef OLDSTUFF unsigned int status, status2; int prefsync; char *autosync_ref; char *system_clock_mode; int x, x2; status = hdspe_read(hdspe, HDSPE_RD_STATUS0); status2 = hdspe_read(hdspe, HDSPE_RD_STATUS2); snd_iprintf(buffer, "%s (Card #%d) Rev.%x Status2first3bits: %x\n", hdspe->card_name, hdspe->card->number + 1, hdspe->firmware_rev, (status2 & HDSPE_version0) | (status2 & HDSPE_version1) | (status2 & HDSPE_version2)); snd_iprintf(buffer, "HW Serial: 0x%06x%06x\n", (hdspe_read(hdspe, HDSPE_midiStatusIn1)>>8) & 0xFFFFFF, hdspe->serial); snd_iprintf(buffer, "IRQ: %d Registers bus: 0x%lx VM: 0x%lx\n", hdspe->irq, hdspe->port, (unsigned long)hdspe->iobase); snd_iprintf(buffer, "--- System ---\n"); snd_iprintf(buffer, "IRQ Pending: Audio=%d, MIDI0=%d, MIDI1=%d, IRQcount=%d\n", status & HDSPE_audioIRQPending, (status & HDSPE_midi0IRQPending) ? 1 : 0, (status & HDSPE_midi1IRQPending) ? 1 : 0, hdspe->irq_count); snd_iprintf(buffer, "HW pointer: id = %d, rawptr = %d (%d->%d) " "estimated= %ld (bytes)\n", ((status & HDSPE_BufferID) ? 1 : 0), (status & HDSPE_BufferPositionMask), (status & HDSPE_BufferPositionMask) % (2 * (int)hdspe->period_bytes), ((status & HDSPE_BufferPositionMask) - 64) % (2 * (int)hdspe->period_bytes), (long) hdspe_hw_pointer(hdspe) * 4); snd_iprintf(buffer, "MIDI FIFO: Out1=0x%x, Out2=0x%x, In1=0x%x, In2=0x%x \n", hdspe_read(hdspe, HDSPE_midiStatusOut0) & 0xFF, hdspe_read(hdspe, HDSPE_midiStatusOut1) & 0xFF, hdspe_read(hdspe, HDSPE_midiStatusIn0) & 0xFF, hdspe_read(hdspe, HDSPE_midiStatusIn1) & 0xFF); snd_iprintf(buffer, "MIDIoverMADI FIFO: In=0x%x, Out=0x%x \n", hdspe_read(hdspe, HDSPE_midiStatusIn2) & 0xFF, hdspe_read(hdspe, HDSPE_midiStatusOut2) & 0xFF); snd_iprintf(buffer, "Register: ctrl1=0x%x, status0=0x%x, " "status2=0x%x\n", hdspe->reg.control.raw, status, status2); snd_iprintf(buffer, "--- Settings ---\n"); x = hdspe_period_size(hdspe); snd_iprintf(buffer, "Size (Latency): %d samples (2 periods of %lu bytes)\n", x, (unsigned long) hdspe->period_bytes); snd_iprintf(buffer, "Line out: %s\n", (hdspe->reg.control.raw & HDSPE_LineOut) ? "on " : "off"); snd_iprintf(buffer, "ClearTrackMarker = %s, Transmit in %s Channel Mode, " "Auto Input %s\n", (hdspe->reg.control.raw & HDSPE_clr_tms) ? "on" : "off", (hdspe->reg.control.raw & HDSPE_TX_64ch) ? "64" : "56", (hdspe->reg.control.raw & HDSPE_AutoInp) ? "on" : "off"); if (!(hdspe->reg.control.raw & HDSPE_ClockModeMaster)) system_clock_mode = "AutoSync"; else system_clock_mode = "Master"; snd_iprintf(buffer, "AutoSync Reference: %s\n", system_clock_mode); prefsync = hdspe_madi_get_preferred_sync_ref(hdspe); snd_iprintf(buffer, "Preferred Sync Reference: %s\n", HDSPE_CLOCK_SOURCE_NAME(HDSPE_MADI, prefsync)); snd_iprintf(buffer, "System Clock Frequency: %d\n", hdspe->system_sample_rate); snd_iprintf(buffer, "--- Status:\n"); x = status & HDSPE_madiSync; x2 = status2 & HDSPE_wcSync; snd_iprintf(buffer, "Inputs MADI=%s, WordClock=%s\n", (status & HDSPE_madiLock) ? (x ? "Sync" : "Lock") : "NoLock", (status2 & HDSPE_wcLock) ? (x2 ? "Sync" : "Lock") : "NoLock"); switch (hdspe_madi_get_autosync_ref(hdspe)) { case HDSPE_CLOCK_SOURCE_SYNC_IN: autosync_ref = "Sync In"; break; case HDSPE_CLOCK_SOURCE_TCO: autosync_ref = "TCO"; break; case HDSPE_CLOCK_SOURCE_WORD: autosync_ref = "Word Clock"; break; case HDSPE_CLOCK_SOURCE_MADI: autosync_ref = "MADI Sync"; break; case HDSPE_CLOCK_SOURCE_INTERN: autosync_ref = "Input not valid"; break; default: autosync_ref = "---"; break; } snd_iprintf(buffer, "AutoSync: Reference= %s, Freq=%d (MADI = %d, Word = %d)\n", autosync_ref, hdspe_freq_sample_rate(hdspe_autosync_freq(hdspe)), HDSPE_MADI_freq(status), HDSPE_MADI_inp_freq(status2)); snd_iprintf(buffer, "Input: %s, Mode=%s\n", (status & HDSPE_AB_int) ? "Coax" : "Optical", (status & HDSPE_RX_64ch) ? "64 channels" : "56 channels"); snd_iprintf(buffer, "\n"); #endif /*OLDSTUFF*/ } static const struct hdspe_methods hdspe_madi_methods = { .get_card_info = hdspe_get_card_info, .read_status = hdspe_madi_read_status, .get_float_format = hdspe_madi_get_float_format, .set_float_format = hdspe_madi_set_float_format, .read_proc = hdspe_madi_proc_read, .get_autosync_ref = hdspe_madi_get_autosync_ref, .get_clock_mode = hdspe_madi_get_clock_mode, .set_clock_mode = hdspe_madi_set_clock_mode, .get_pref_sync_ref = hdspe_madi_get_preferred_sync_ref, .set_pref_sync_ref = hdspe_madi_set_preferred_sync_ref, .check_status_change = hdspe_madi_check_status_change }; static const struct hdspe_tables hdspe_madi_tables = { .ss_in_channels = MADI_SS_CHANNELS, .ss_out_channels = MADI_SS_CHANNELS, .ds_in_channels = MADI_DS_CHANNELS, .ds_out_channels = MADI_DS_CHANNELS, .qs_in_channels = MADI_QS_CHANNELS, .qs_out_channels = MADI_QS_CHANNELS, .channel_map_in_ss = channel_map_unity_ss, .channel_map_out_ss = channel_map_unity_ss, .channel_map_in_ds = channel_map_unity_ss, .channel_map_out_ds = channel_map_unity_ss, .channel_map_in_qs = channel_map_unity_ss, .channel_map_out_qs = channel_map_unity_ss, .port_names_in_ss = texts_ports_madi, .port_names_out_ss = texts_ports_madi, .port_names_in_ds = texts_ports_madi, .port_names_out_ds = texts_ports_madi, .port_names_in_qs = texts_ports_madi, .port_names_out_qs = texts_ports_madi, .clock_source_names = hdspe_madi_clock_source_names }; static struct hdspe_midi hdspe_madi_midi_ports[4] = { {.portname = "MIDIoverMADI 1", .dataIn = HDSPE_midiDataIn0, .statusIn = HDSPE_midiStatusIn0, .dataOut = HDSPE_midiDataOut0, .statusOut = HDSPE_midiStatusOut0, .ie = HDSPE_Midi0InterruptEnable, .irq = HDSPE_midi0IRQPending, }, {.portname = "MIDIoverMADI 2", .dataIn = HDSPE_midiDataIn1, .statusIn = HDSPE_midiStatusIn1, .dataOut = HDSPE_midiDataOut1, .statusOut = HDSPE_midiStatusOut1, .ie = HDSPE_Midi1InterruptEnable, .irq = HDSPE_midi1IRQPending, }, {.portname = "MIDIoverMADI 3", .dataIn = HDSPE_midiDataIn2, .statusIn = HDSPE_midiStatusIn2, .dataOut = HDSPE_midiDataOut2, .statusOut = HDSPE_midiStatusOut2, .ie = HDSPE_Midi2InterruptEnable, .irq = HDSPE_midi2IRQPending, }, {.portname = "MTC", .dataIn = HDSPE_midiDataIn3, .statusIn = HDSPE_midiStatusIn3, .dataOut = -1, .statusOut = -1, .ie = HDSPE_Midi3InterruptEnable, .irq = HDSPE_midi3IRQPending, } }; static struct hdspe_midi hdspe_madiface_midi_ports[1] = { {.portname = "MIDIoverMADI", .dataIn = HDSPE_midiDataIn2, .statusIn = HDSPE_midiStatusIn2, .dataOut = HDSPE_midiDataOut2, .statusOut = HDSPE_midiStatusOut2, .ie = HDSPE_Midi2InterruptEnable, .irq = HDSPE_midi2IRQPending, } }; int hdspe_init_madi(struct hdspe* hdspe) { int midi_count = 0; struct hdspe_midi *midi_ports = NULL; hdspe->reg.control.madi.Master = true; hdspe->reg.control.madi.tx_64ch = true; hdspe->reg.control.madi.inp_0 = HDSPE_MADI_INPUT_COAXIAL; // TODO: shouldn't we prefer Auto Input? #ifdef FROM_WIN_DRIVER deviceExtension->Register |= LineOut; if (deviceExtension->Channels) deviceExtension->Register |= tx_64ch; else deviceExtension->Register &= ~tx_64ch; if (deviceExtension->AutoInput) deviceExtension->Register |= AutoInp; else deviceExtension->Register &= ~AutoInp; if (deviceExtension->Frame) deviceExtension->Register &= ~SMUX; else deviceExtension->Register |= SMUX; if (deviceExtension->InputSource) // || deviceExtension->Revision == MADI_EXPRESSCARD_REV) deviceExtension->Register |= inp_0; else deviceExtension->Register &= ~inp_0; if (deviceExtension->SingleSpeed) deviceExtension->Register |= MADI_WCK48; else deviceExtension->Register &= ~MADI_WCK48; deviceExtension->Register &= ~disable_MM; #endif hdspe_write_control(hdspe); hdspe->m = hdspe_madi_methods; switch (hdspe->io_type) { case HDSPE_MADI: hdspe->card_name = "RME MADI"; midi_count = 3 + (hdspe->tco ? 1 : 0); midi_ports = hdspe_madi_midi_ports; break; case HDSPE_MADIFACE: hdspe->card_name = "RME MADIface"; midi_count = 1; midi_ports = hdspe_madiface_midi_ports; break; default: snd_BUG(); return -ENODEV; } hdspe_init_midi(hdspe, midi_count, midi_ports); hdspe->t = hdspe_madi_tables; hdspe_init_autosync_tables( hdspe, ARRAY_SIZE(madi_autosync_ref), madi_autosync_ref); return 0; } void hdspe_terminate_madi(struct hdspe* hdspe) { /* nothing to do */ } snd-hdspe-1.0.2/sound/pci/hdsp/hdspe/hdspe_midi.c000066400000000000000000000221521506037051300216160ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later /** * hdspe_midi.c * @brief RME HDSPe MIDI driver. * * 20210728 - Philippe.Bekaert@uhasselt.be * * Refactored work of the other MODULE_AUTHORs. */ #include "hdspe.h" #include "hdspe_core.h" #include #include static inline bool hdspe_midi_is_readwrite(struct hdspe_midi *m) { return m->dataOut > 0; } static inline unsigned char snd_hdspe_midi_read_byte (struct hdspe *hdspe, int id) { /* the hardware already does the relevant bit-mask with 0xff */ return hdspe_read(hdspe, hdspe->midi[id].dataIn); } static inline void snd_hdspe_midi_write_byte (struct hdspe *hdspe, int id, int val) { /* the hardware already does the relevant bit-mask with 0xff */ return hdspe_write(hdspe, hdspe->midi[id].dataOut, val); } static inline int snd_hdspe_midi_input_available (struct hdspe *hdspe, int id) { return hdspe_read(hdspe, hdspe->midi[id].statusIn) & 0xFF; } static inline int snd_hdspe_midi_output_possible (struct hdspe *hdspe, int id) { int fifo_bytes_used; fifo_bytes_used = hdspe_read(hdspe, hdspe->midi[id].statusOut) & 0xFF; if (fifo_bytes_used < 128) return 128 - fifo_bytes_used; else return 0; } static void snd_hdspe_flush_midi_input(struct hdspe *hdspe, int id) { while (snd_hdspe_midi_input_available (hdspe, id)) snd_hdspe_midi_read_byte (hdspe, id); } static int snd_hdspe_midi_output_write (struct hdspe_midi *hmidi) { unsigned long flags; int n_pending; int to_write; int i; unsigned char buf[128]; /* Output is not interrupt driven */ spin_lock_irqsave (&hmidi->lock, flags); if (hmidi->output && !snd_rawmidi_transmit_empty (hmidi->output)) { n_pending = snd_hdspe_midi_output_possible (hmidi->hdspe, hmidi->id); if (n_pending > 0) { if (n_pending > (int)sizeof (buf)) n_pending = sizeof (buf); to_write = snd_rawmidi_transmit (hmidi->output, buf, n_pending); if (to_write > 0) { for (i = 0; i < to_write; ++i) snd_hdspe_midi_write_byte (hmidi->hdspe, hmidi->id, buf[i]); } } } spin_unlock_irqrestore (&hmidi->lock, flags); return 0; } static int snd_hdspe_midi_input_read (struct hdspe_midi *hmidi) { unsigned char buf[128]; /* this buffer is designed to match the MIDI * input FIFO size */ unsigned long flags; int n_pending; int i; spin_lock_irqsave (&hmidi->lock, flags); n_pending = snd_hdspe_midi_input_available (hmidi->hdspe, hmidi->id); while (n_pending > 0) { for (i = 0; i < n_pending && i < sizeof(buf); i++) buf[i] = snd_hdspe_midi_read_byte (hmidi->hdspe, hmidi->id); /* all hdspe MIDI ports are read-write, except for the TCO MTC * port. MTC messages are either 2 or 10 bytes, so always fit * in the buffer. */ if (i>0 && !hdspe_midi_is_readwrite(hmidi)) hdspe_tco_mtc(hmidi->hdspe, buf, i); if (hmidi->input) snd_rawmidi_receive (hmidi->input, buf, i); n_pending -= i; } hmidi->pending = 0; spin_unlock_irqrestore(&hmidi->lock, flags); /* re-enable MIDI interrupt (was disabled in snd_hdspe_interrupt()) */ spin_lock_irqsave(&hmidi->hdspe->lock, flags); hmidi->hdspe->reg.control.raw |= hmidi->ie; hdspe_write_control(hmidi->hdspe); spin_unlock_irqrestore(&hmidi->hdspe->lock, flags); return snd_hdspe_midi_output_write (hmidi); } static void snd_hdspe_midi_input_trigger(struct snd_rawmidi_substream *substream, int up) { struct hdspe_midi *hmidi = substream->rmidi->private_data; struct hdspe *hdspe = hmidi->hdspe; bool changed = false; unsigned long flags; if (!hdspe_midi_is_readwrite(hmidi)) { /* MTC port is always up. */ return; } spin_lock_irqsave (&hdspe->lock, flags); if (up) { if (!(hdspe->reg.control.raw & hmidi->ie)) { snd_hdspe_flush_midi_input (hdspe, hmidi->id); hdspe->reg.control.raw |= hmidi->ie; hdspe_write_control(hdspe); changed = true; } } else { if ((hdspe->reg.control.raw & hmidi->ie) != 0) { hdspe->reg.control.raw &= ~hmidi->ie; hdspe_write_control(hdspe); changed = true; } } spin_unlock_irqrestore (&hdspe->lock, flags); return; if (changed) dev_dbg(hdspe->card->dev, "%s: MIDI port %d input %s. IE=0x%08x.\n", __func__, hmidi->id, up ? "UP" : "DOWN", hmidi->ie); } static void snd_hdspe_midi_output_timer(struct timer_list *t) { #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 16, 0) struct hdspe_midi *hmidi = timer_container_of(hmidi, t, timer); #else struct hdspe_midi *hmidi = from_timer(hmidi, t, timer); #endif unsigned long flags; snd_hdspe_midi_output_write(hmidi); spin_lock_irqsave (&hmidi->lock, flags); /* this does not bump hmidi->istimer, because the kernel automatically removed the timer when it expired, and we are now adding it back, thus leaving istimer wherever it was set before. */ if (hmidi->istimer) mod_timer(&hmidi->timer, 1 + jiffies); spin_unlock_irqrestore (&hmidi->lock, flags); } static void snd_hdspe_midi_output_trigger(struct snd_rawmidi_substream *substream, int up) { struct hdspe_midi *hmidi; unsigned long flags; hmidi = substream->rmidi->private_data; spin_lock_irqsave (&hmidi->lock, flags); if (up) { if (!hmidi->istimer) { timer_setup(&hmidi->timer, snd_hdspe_midi_output_timer, 0); mod_timer(&hmidi->timer, 1 + jiffies); hmidi->istimer++; } } else { if (hmidi->istimer && --hmidi->istimer <= 0) #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 2, 0) timer_delete (&hmidi->timer); #else del_timer (&hmidi->timer); #endif } spin_unlock_irqrestore (&hmidi->lock, flags); if (up) snd_hdspe_midi_output_write(hmidi); dev_dbg(hmidi->hdspe->card->dev, "%s: MIDI port %d output %s.\n", __func__, hmidi->id, up ? "UP" : "DOWN"); } static int snd_hdspe_midi_input_open(struct snd_rawmidi_substream *substream) { struct hdspe_midi *hmidi; hmidi = substream->rmidi->private_data; spin_lock_irq (&hmidi->lock); snd_hdspe_flush_midi_input (hmidi->hdspe, hmidi->id); hmidi->input = substream; spin_unlock_irq (&hmidi->lock); return 0; } static int snd_hdspe_midi_output_open(struct snd_rawmidi_substream *substream) { struct hdspe_midi *hmidi; hmidi = substream->rmidi->private_data; spin_lock_irq (&hmidi->lock); hmidi->output = substream; spin_unlock_irq (&hmidi->lock); return 0; } static int snd_hdspe_midi_input_close(struct snd_rawmidi_substream *substream) { struct hdspe_midi *hmidi; snd_hdspe_midi_input_trigger (substream, 0); hmidi = substream->rmidi->private_data; spin_lock_irq (&hmidi->lock); hmidi->input = NULL; spin_unlock_irq (&hmidi->lock); return 0; } static int snd_hdspe_midi_output_close(struct snd_rawmidi_substream *substream) { struct hdspe_midi *hmidi; snd_hdspe_midi_output_trigger (substream, 0); hmidi = substream->rmidi->private_data; spin_lock_irq (&hmidi->lock); hmidi->output = NULL; spin_unlock_irq (&hmidi->lock); return 0; } static const struct snd_rawmidi_ops snd_hdspe_midi_output = { .open = snd_hdspe_midi_output_open, .close = snd_hdspe_midi_output_close, .trigger = snd_hdspe_midi_output_trigger, }; static const struct snd_rawmidi_ops snd_hdspe_midi_input = { .open = snd_hdspe_midi_input_open, .close = snd_hdspe_midi_input_close, .trigger = snd_hdspe_midi_input_trigger, }; void hdspe_init_midi(struct hdspe* hdspe, int count, struct hdspe_midi *list) { int i; hdspe->midiPorts = count; hdspe->midiInterruptEnableMask = 0; hdspe->midiIRQPendingMask = 0; for (i = 0; i < count; i ++) { hdspe->midi[i] = list[i]; hdspe->midi[i].hdspe = hdspe; hdspe->midi[i].id = i; hdspe->midiInterruptEnableMask |= hdspe->midi[i].ie; hdspe->midiIRQPendingMask |= hdspe->midi[i].irq; } } int snd_hdspe_create_midi(struct snd_card* card, struct hdspe *hdspe, int id) { struct hdspe_midi* m = &hdspe->midi[id]; bool rw = hdspe_midi_is_readwrite(m); int err; char buf[64]; spin_lock_init (&m->lock); snprintf(buf, sizeof(buf), "%s %s", card->shortname, m->portname); err = snd_rawmidi_new(card, buf, id, 1, 1, &m->rmidi); if (err < 0) return err; m->rmidi->private_data = &hdspe->midi[id]; if (rw) { // read-write port snprintf(m->rmidi->name, sizeof(m->rmidi->name), "%s MIDI %d", card->id, id+1); snd_rawmidi_set_ops(m->rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_hdspe_midi_output); snd_rawmidi_set_ops(m->rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_hdspe_midi_input); m->rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT | SNDRV_RAWMIDI_INFO_DUPLEX; } else { // read-only port (MTC from TCO module) snprintf(m->rmidi->name, sizeof(m->rmidi->name), "%s MTC", card->id); snd_rawmidi_set_ops(m->rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_hdspe_midi_input); m->rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT; } dev_dbg(hdspe->card->dev, "%s: %s rawmidi port %d %s created.\n", __func__, rw ? "read-write" : "read-only", id, m->rmidi->name); snd_hdspe_flush_midi_input(hdspe, id); return 0; } void hdspe_midi_work(struct work_struct *work) { struct hdspe *hdspe = container_of(work, struct hdspe, midi_work); int i; for (i = 0; i < hdspe->midiPorts; i++) { if (hdspe->midi[i].pending) snd_hdspe_midi_input_read(&hdspe->midi[i]); } } snd-hdspe-1.0.2/sound/pci/hdsp/hdspe/hdspe_mixer.c000066400000000000000000000256461506037051300220330ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later /** * hdspe-mixer.c * @brief RME HDSPe hardware mixer status and control interface. * * 20210728,1206,07 - Philippe.Bekaert@uhasselt.be * * Based on earlier work of the other MODULE_AUTHORS, * information kindly made available by RME (www.rme-audio.com), * and hardware kindly made available by Amptec Belgium (www.amptec.be). */ #include "hdspe.h" #include "hdspe_core.h" #include "hdspe_control.h" #include /* for each output channel (chan) I have an Input (in) and Playback (pb) Fader mixer is write only on hardware so we have to cache him for read each fader is a u32, but uses only the first 16 bit */ static inline u16 hdspe_read_in_gain(struct hdspe * hdspe, unsigned int chan, unsigned int in) { if (chan >= HDSPE_MIXER_CHANNELS || in >= HDSPE_MIXER_CHANNELS) return 0; return hdspe->mixer->ch[chan].in[in]; } static inline u16 hdspe_read_pb_gain(struct hdspe * hdspe, unsigned int chan, unsigned int pb) { if (chan >= HDSPE_MIXER_CHANNELS || pb >= HDSPE_MIXER_CHANNELS) return 0; return hdspe->mixer->ch[chan].pb[pb]; } static int hdspe_write_in_gain(struct hdspe *hdspe, unsigned int chan, unsigned int in, u16 data) { if (chan >= HDSPE_MIXER_CHANNELS || in >= HDSPE_MIXER_CHANNELS) return -1; hdspe_write(hdspe, HDSPE_MADI_mixerBase + ((in + 128 * chan) * sizeof(u32)), cpu_to_le32(hdspe->mixer->ch[chan].in[in] = data & 0xFFFF)); return 0; } static int hdspe_write_pb_gain(struct hdspe *hdspe, unsigned int chan, unsigned int pb, u16 data) { if (chan >= HDSPE_MIXER_CHANNELS || pb >= HDSPE_MIXER_CHANNELS) return -1; hdspe_write(hdspe, HDSPE_MADI_mixerBase + ((64 + pb + 128 * chan) * sizeof(u32)), (hdspe->mixer->ch[chan].pb[pb] = data & 0xFFFF)); return 0; } void hdspe_mixer_read_proc(struct snd_info_entry *entry, struct snd_info_buffer *buffer) { struct hdspe *hdspe = entry->private_data; int i, j; snd_iprintf(buffer, "Capture Volume:\n"); snd_iprintf(buffer, " "); for (j = 0; j < HDSPE_MIXER_CHANNELS; j++) snd_iprintf(buffer, " %02u ", j); snd_iprintf(buffer, "\n"); for (i = 0; i < HDSPE_MIXER_CHANNELS; i++) { snd_iprintf(buffer, "%02d: ", i); for (j = 0; j < HDSPE_MIXER_CHANNELS; j++) snd_iprintf(buffer, "%5u ", hdspe_read_in_gain(hdspe, i, j)); snd_iprintf(buffer, "\n"); } snd_iprintf(buffer, "\nPlayback Volume:\n"); snd_iprintf(buffer, " "); for (j = 0; j < HDSPE_MIXER_CHANNELS; j++) snd_iprintf(buffer, " %02u ", j); snd_iprintf(buffer, "\n"); for (i = 0; i < HDSPE_MIXER_CHANNELS; i++) { snd_iprintf(buffer, "%02d: ", i); for (j = 0; j < HDSPE_MIXER_CHANNELS; j++) snd_iprintf(buffer, "%5u ", hdspe_read_pb_gain(hdspe, i, j)); snd_iprintf(buffer, "\n"); } } void hdspe_mixer_update_channel_map(struct hdspe* hdspe) { int i, j; bool used[HDSPE_MAX_CHANNELS]; dev_dbg(hdspe->card->dev, "%s:\n", __func__); /* mute all unused playback channels */ for (i = 0; i < HDSPE_MIXER_CHANNELS; i ++) { used[i] = false; } for (i = 0; i < HDSPE_MIXER_CHANNELS; i ++) { int c = hdspe->channel_map_out[i]; used[c] = true; } for (i = 0; i < HDSPE_MIXER_CHANNELS; i ++) { if (!used[i]) { for (j = 0; j < HDSPE_MIXER_CHANNELS; j ++) { hdspe_write_in_gain(hdspe, i, j, 0); hdspe_write_pb_gain(hdspe, i, j, 0); } } else { #ifdef DAW_MODE hdspe_write_pb_gain(hdspe, i, i, HDSPE_UNITY_GAIN); #endif /*DAW_MODE*/ } } /* mute all unused capture channels */ for (i = 0; i < HDSPE_MIXER_CHANNELS; i ++) { used[i] = false; } for (i = 0; i < HDSPE_MIXER_CHANNELS; i ++) { int c = hdspe->channel_map_in[i]; used[c] = true; } for (i = 0; i < HDSPE_MIXER_CHANNELS; i ++) { if (!used[i]) { for (j = 0; j < HDSPE_MIXER_CHANNELS; j ++) { hdspe_write_in_gain(hdspe, j, i, 0); } } else { #ifdef PASSTHROUGH_MODE hdspe_write_in_gain(hdspe, i, i, HDSPE_UNITY_GAIN); #endif /*PASSTHROUGH_MODE*/ } } } static void hdspe_clear_mixer(struct hdspe * hdspe, u16 sgain) { int i, j; unsigned int gain; if (sgain > HDSPE_UNITY_GAIN) gain = HDSPE_UNITY_GAIN; else if (sgain < 0) gain = 0; else gain = sgain; for (i = 0; i < HDSPE_MIXER_CHANNELS; i++) for (j = 0; j < HDSPE_MIXER_CHANNELS; j++) { hdspe_write_in_gain(hdspe, i, j, gain); hdspe_write_pb_gain(hdspe, i, j, gain); } } #define HDSPE_MIXER(xname, xindex) \ { .iface = SNDRV_CTL_ELEM_IFACE_HWDEP, \ .name = xname, \ .index = xindex, \ .device = 0, \ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \ SNDRV_CTL_ELEM_ACCESS_VOLATILE, \ .info = snd_hdspe_info_mixer, \ .get = snd_hdspe_get_mixer, \ .put = snd_hdspe_put_mixer \ } static int snd_hdspe_info_mixer(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; uinfo->count = 3; uinfo->value.integer.min = 0; uinfo->value.integer.max = 65535; uinfo->value.integer.step = 1; return 0; } static int snd_hdspe_get_mixer(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct hdspe *hdspe = snd_kcontrol_chip(kcontrol); int source; int destination; source = ucontrol->value.integer.value[0]; if (source < 0) source = 0; else if (source >= 2 * HDSPE_MAX_CHANNELS) source = 2 * HDSPE_MAX_CHANNELS - 1; destination = ucontrol->value.integer.value[1]; if (destination < 0) destination = 0; else if (destination >= HDSPE_MAX_CHANNELS) destination = HDSPE_MAX_CHANNELS - 1; spin_lock_irq(&hdspe->lock); if (source >= HDSPE_MAX_CHANNELS) ucontrol->value.integer.value[2] = hdspe_read_pb_gain(hdspe, destination, source - HDSPE_MAX_CHANNELS); else ucontrol->value.integer.value[2] = hdspe_read_in_gain(hdspe, destination, source); spin_unlock_irq(&hdspe->lock); return 0; } static int snd_hdspe_put_mixer(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct hdspe *hdspe = snd_kcontrol_chip(kcontrol); int change; int source; int destination; int gain; if (!snd_hdspe_use_is_exclusive(hdspe)) return -EBUSY; source = ucontrol->value.integer.value[0]; destination = ucontrol->value.integer.value[1]; if (source < 0 || source >= 2 * HDSPE_MAX_CHANNELS) return -1; if (destination < 0 || destination >= HDSPE_MAX_CHANNELS) return -1; gain = ucontrol->value.integer.value[2]; spin_lock_irq(&hdspe->lock); if (source >= HDSPE_MAX_CHANNELS) change = gain != hdspe_read_pb_gain(hdspe, destination, source - HDSPE_MAX_CHANNELS); else change = gain != hdspe_read_in_gain(hdspe, destination, source); if (change) { if (source >= HDSPE_MAX_CHANNELS) hdspe_write_pb_gain(hdspe, destination, source - HDSPE_MAX_CHANNELS, gain); else hdspe_write_in_gain(hdspe, destination, source, gain); } spin_unlock_irq(&hdspe->lock); return change; } /* The simple mixer control(s) provide gain control for the basic 1:1 mappings of playback streams to output streams. */ #define HDSPE_PLAYBACK_MIXER \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_WRITE | \ SNDRV_CTL_ELEM_ACCESS_VOLATILE, \ .info = snd_hdspe_info_playback_mixer, \ .get = snd_hdspe_get_playback_mixer, \ .put = snd_hdspe_put_playback_mixer \ } static int snd_hdspe_info_playback_mixer(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; uinfo->count = 1; uinfo->value.integer.min = 0; uinfo->value.integer.max = 64; uinfo->value.integer.step = 1; return 0; } static int snd_hdspe_get_playback_mixer(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct hdspe *hdspe = snd_kcontrol_chip(kcontrol); int channel; channel = ucontrol->id.index - 1; if (snd_BUG_ON(channel < 0 || channel >= HDSPE_MAX_CHANNELS)) return -EINVAL; spin_lock_irq(&hdspe->lock); ucontrol->value.integer.value[0] = (hdspe_read_pb_gain(hdspe, channel, channel)*64)/HDSPE_UNITY_GAIN; spin_unlock_irq(&hdspe->lock); return 0; } static int snd_hdspe_put_playback_mixer(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct hdspe *hdspe = snd_kcontrol_chip(kcontrol); int change; int channel; int gain; if (!snd_hdspe_use_is_exclusive(hdspe)) return -EBUSY; channel = ucontrol->id.index - 1; if (snd_BUG_ON(channel < 0 || channel >= HDSPE_MAX_CHANNELS)) return -EINVAL; gain = ucontrol->value.integer.value[0]*HDSPE_UNITY_GAIN/64; spin_lock_irq(&hdspe->lock); change = gain != hdspe_read_pb_gain(hdspe, channel, channel); if (change) hdspe_write_pb_gain(hdspe, channel, channel, gain); spin_unlock_irq(&hdspe->lock); return change; } static struct snd_kcontrol_new snd_hdspe_playback_mixer = HDSPE_PLAYBACK_MIXER; static int hdspe_update_simple_mixer_controls(struct hdspe * hdspe) { int i; dev_dbg(hdspe->card->dev, "Update mixer controls...\n"); // TODO: what about quad speed nr of channels?? for (i = hdspe->t.ds_out_channels; i < hdspe->t.ss_out_channels; ++i) { if (hdspe_speed_mode(hdspe) > HDSPE_SPEED_SINGLE) { hdspe->playback_mixer_ctls[i]->vd[0].access = SNDRV_CTL_ELEM_ACCESS_INACTIVE | SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE; } else { hdspe->playback_mixer_ctls[i]->vd[0].access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_VOLATILE; } snd_ctl_notify(hdspe->card, SNDRV_CTL_EVENT_MASK_VALUE | SNDRV_CTL_EVENT_MASK_INFO, &hdspe->playback_mixer_ctls[i]->id); } return 0; } static int hdspe_nr_out_channels(struct hdspe* hdspe) { switch (hdspe_speed_mode(hdspe)) { case HDSPE_SPEED_SINGLE: return hdspe->t.ss_out_channels; case HDSPE_SPEED_DOUBLE: return hdspe->t.ds_out_channels; case HDSPE_SPEED_QUAD : return hdspe->t.qs_out_channels; default: snd_BUG(); } return 0; } static const struct snd_kcontrol_new snd_hdspe_controls_mixer[] = { HDSPE_MIXER("Mixer", 0) }; int hdspe_create_mixer_controls(struct hdspe* hdspe) { unsigned int idx, limit; int err; struct snd_kcontrol *kctl; err = hdspe_add_controls(hdspe, ARRAY_SIZE(snd_hdspe_controls_mixer), snd_hdspe_controls_mixer); if (err < 0) return err; /* create simple 1:1 playback mixer controls */ snd_hdspe_playback_mixer.name = "Chn"; limit = hdspe_nr_out_channels(hdspe); for (idx = 0; idx < limit; ++idx) { snd_hdspe_playback_mixer.index = idx + 1; kctl = snd_ctl_new1(&snd_hdspe_playback_mixer, hdspe); err = snd_ctl_add(hdspe->card, kctl); if (err < 0) return err; hdspe->playback_mixer_ctls[idx] = kctl; } hdspe_update_simple_mixer_controls(hdspe); return 0; } int hdspe_init_mixer(struct hdspe* hdspe) { dev_dbg(hdspe->card->dev, "kmalloc Mixer memory of %zd Bytes\n", sizeof(*hdspe->mixer)); hdspe->mixer = kzalloc(sizeof(*hdspe->mixer), GFP_KERNEL); if (!hdspe->mixer) return -ENOMEM; hdspe_clear_mixer(hdspe, 0 * HDSPE_UNITY_GAIN); return 0; } void hdspe_terminate_mixer(struct hdspe* hdspe) { kfree(hdspe->mixer); } snd-hdspe-1.0.2/sound/pci/hdsp/hdspe/hdspe_pcm.c000066400000000000000000000644641506037051300214670ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later /** * hdspe_pcm.c * @brief RME HDSPe PCM interface. * * 20210728 ... 1208 - Philippe.Bekaert@uhasselt.be * * Refactored work of the other MODULE_AUTHORs. */ #include "hdspe.h" #include "hdspe_core.h" #include #include #include /* the size of a substream (1 mono data stream) */ #define HDSPE_CHANNEL_BUFFER_SAMPLES (16*1024) #define HDSPE_CHANNEL_BUFFER_BYTES (4*HDSPE_CHANNEL_BUFFER_SAMPLES) /* the size of the area we need to allocate for DMA transfers. the size is the same regardless of the number of channels, and also the latency to use. for one direction !!! */ #define HDSPE_DMA_AREA_BYTES (HDSPE_MAX_CHANNELS * HDSPE_CHANNEL_BUFFER_BYTES) #define HDSPE_DMA_AREA_KILOBYTES (HDSPE_DMA_AREA_BYTES/1024) /*------------------------------------------------------------ memory interface ------------------------------------------------------------*/ static int snd_hdspe_preallocate_memory(struct hdspe *hdspe) { struct snd_pcm *pcm; size_t wanted; pcm = hdspe->pcm; wanted = HDSPE_DMA_AREA_BYTES; snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG, &hdspe->pci->dev, wanted, wanted); dev_dbg(hdspe->card->dev, "Preallocated %zd Bytes for DMA.\n", wanted); return 0; } /* Inform the card what DMA addresses to use for the indicated channel. * Each channel got 16 4K pages allocated for DMA transfers. We map the * channels the same way for all speeds: DMA channel 0 at the start * of the buffer, DMA channel 1 next, a.s.o. Audio data for some logical * channels (e.g. ADAT) may appear in different DMA channels, depending * on speed mode. We catch that by setting the buffer offsets for each * logical channel appropriately, depending on current speed mode, in * snd_hdspe_channel_info(). */ static void hdspe_set_channel_dma_addr(struct hdspe *hdspe, struct snd_pcm_substream *substream, unsigned int reg, int channel) { int i; for (i = channel * 16; i < channel * 16 + 16; i++) { hdspe_write(hdspe, reg + 4 * i, snd_pcm_sgbuf_get_addr(substream, 4096 * i)); } } /* enable DMA for specific channels, now available for DSP-MADI */ static inline void snd_hdspe_enable_in(struct hdspe * hdspe, int i, int v) { hdspe_write(hdspe, HDSPE_inputEnableBase + (4 * i), v); } static inline void snd_hdspe_enable_out(struct hdspe * hdspe, int i, int v) { hdspe_write(hdspe, HDSPE_outputEnableBase + (4 * i), v); } /* ------------------------------------------------------- */ /** * Returns true if the card is a RayDAT / AIO / AIO Pro */ static inline bool hdspe_is_raydat_or_aio(struct hdspe *hdspe) { return ((HDSPE_AIO == hdspe->io_type) || (HDSPE_RAYDAT == hdspe->io_type) || (HDSPE_AIO_PRO == hdspe->io_type)); } /* return period size in samples per period */ u32 hdspe_period_size(struct hdspe *hdspe) { int n = hdspe->reg.control.common.LAT; /* Special case for new RME cards with 32 samples period size. * The three latency bits in the control register * (HDSP_LatencyMask) encode latency values of 64 samples as * 0, 128 samples as 1 ... 4096 samples as 6. For old cards, 7 * denotes 8192 samples, but on new cards like RayDAT or AIO, * it corresponds to 32 samples. */ if ((7 == n) && hdspe_is_raydat_or_aio(hdspe)) n = -1; return 64 << n; // 1 << (n + 6); } /* Sets hdspe->period_size and hdspe->hw_buffer_size according to the * current latency setting in the control register. */ static void hdspe_set_period_size(struct hdspe* hdspe) { hdspe->period_size = hdspe_period_size(hdspe); hdspe->hw_buffer_size = hdspe_is_raydat_or_aio(hdspe) ? ((1<<16)/4) : 2 * hdspe->period_size; } static int hdspe_set_interrupt_interval(struct hdspe *hdspe, unsigned int frames) { int n; spin_lock_irq(&hdspe->lock); if (32 == frames) { /* Special case for new RME cards like RayDAT/AIO which * support period sizes of 32 samples. Since latency is * encoded in the three bits of HDSP_LatencyMask, we can only * have values from 0 .. 7. While 0 still means 64 samples and * 6 represents 4096 samples on all cards, 7 represents 8192 * on older cards and 32 samples on new cards. * * In other words, period size in samples is calculated by * 2^(n+6) with n ranging from 0 .. 7. */ n = 7; } else { frames >>= 7; n = 0; while (frames) { n++; frames >>= 1; } } hdspe->reg.control.common.LAT = n; hdspe_write_control(hdspe); hdspe_set_period_size(hdspe); spin_unlock_irq(&hdspe->lock); snd_ctl_notify(hdspe->card, SNDRV_CTL_EVENT_MASK_VALUE, hdspe->cid.buffer_size); return 0; } /* Return hardware buffer pointer in samples (always 4 bytes) */ snd_pcm_uframes_t hdspe_hw_pointer(struct hdspe *hdspe) { /* (BUF_PTR << 6) bytes / 4 bytes per sample */ return ((le16_to_cpu(hdspe->reg.status0.common.BUF_PTR)) << 4) & (hdspe->hw_buffer_size - 1); } /* Called right from the interrupt handler in order to update the frame * counter. In absence of xruns, the frame counter increments by * hdspe_period_size() frames each period. This routine will correctly * determine the frame counter even in the presence of xruns or late * interrupt handling, as long as the hardware pointer did not wrap more * than once since the previous invocation. The hardware pointer wraps every * 16K frames, so about 3 times a second at 48 KHz sampling rate. */ void hdspe_update_frame_count(struct hdspe* hdspe) { u32 hw_pointer; hw_pointer = le16_to_cpu(hdspe->reg.status0.common.BUF_PTR) << 4; if (hw_pointer < hdspe->last_hw_pointer) hdspe->hw_pointer_wrap_count ++; hdspe->last_hw_pointer = hw_pointer; hdspe->frame_count = (u64)hdspe->hw_pointer_wrap_count * ((1<<16)/4) + (hw_pointer & ~(hdspe->period_size - 1)); #ifdef DEBUG_FRAME_COUNT { static u64 last_frame_count =0; static u64 last_hw_pointer =0; hw_pointer = hdspe_hw_pointer(hdspe); dev_dbg(hdspe->card->dev, "%s: hw_pointer=%u (delta %llu), frame_count=%llu (delta=%llu)\n", __func__, hw_pointer, hw_pointer > last_hw_pointer ? hw_pointer - last_hw_pointer : (hw_pointer + hdspe->hw_buffer_size) - last_hw_pointer, hdspe->frame_count, hdspe->frame_count - last_frame_count); last_frame_count = hdspe->frame_count; last_hw_pointer = hw_pointer; } #endif /*DEBUG_FRAME_COUNT*/ } static inline void hdspe_start_audio(struct hdspe * s) { return; /* we have audio interrupts enabled all the time */ if (s->tco) return; /* always running with TCO */ s->reg.control.common.START = s->reg.control.common.IE_AUDIO = true; hdspe_write_control(s); } static inline void hdspe_stop_audio(struct hdspe * s) { return; /* we leave audio interrupts enabled all the time */ if (s->tco) return; /* leave always running with TCO */ s->reg.control.common.START = s->reg.control.common.IE_AUDIO = false; hdspe_write_control(s); } /* should I silence all or only opened ones ? doit all for first even is 4MB*/ static void hdspe_silence_playback(struct hdspe *hdspe) { int i; int n = hdspe->period_size * 4; void *buf = hdspe->playback_buffer; if (!buf) return; for (i = 0; i < HDSPE_MAX_CHANNELS; i++) { memset(buf, 0, n); buf += HDSPE_CHANNEL_BUFFER_BYTES; } } static snd_pcm_uframes_t snd_hdspe_hw_pointer(struct snd_pcm_substream *substream) { struct hdspe *hdspe = snd_pcm_substream_chip(substream); return hdspe_hw_pointer(hdspe); } static int snd_hdspe_reset(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct hdspe *hdspe = snd_pcm_substream_chip(substream); struct snd_pcm_substream *other; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) other = hdspe->capture_substream; else other = hdspe->playback_substream; if (hdspe->running) runtime->status->hw_ptr = hdspe_hw_pointer(hdspe); else runtime->status->hw_ptr = 0; if (other) { struct snd_pcm_substream *s; struct snd_pcm_runtime *oruntime = other->runtime; snd_pcm_group_for_each_entry(s, substream) { if (s == other) { oruntime->status->hw_ptr = runtime->status->hw_ptr; break; } } } return 0; } static void snd_hdspe_set_float_format(struct hdspe* hdspe, bool val) { if (hdspe->m.get_float_format(hdspe) == val) return; dev_info(hdspe->card->dev, "Switching to native 32-bit %s format.\n", val ? "LE float" : "LE integer"); hdspe->m.set_float_format(hdspe, val); } static int snd_hdspe_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { struct hdspe *hdspe = snd_pcm_substream_chip(substream); int err; int i; pid_t this_pid; pid_t other_pid; spin_lock_irq(&hdspe->lock); if (substream->pstr->stream == SNDRV_PCM_STREAM_PLAYBACK) { this_pid = hdspe->playback_pid; other_pid = hdspe->capture_pid; } else { this_pid = hdspe->capture_pid; other_pid = hdspe->playback_pid; } if (other_pid > 0 && this_pid != other_pid) { /* The other stream is open, and not by the same task as this one. Make sure that the parameters that matter are the same. */ u32 sysrate = hdspe_read_system_sample_rate(hdspe); if (params_rate(params) != sysrate) { spin_unlock_irq(&hdspe->lock); dev_warn(hdspe->card->dev, "Requested sample rate %d does not match actual rate %d used by process %d.\n", params_rate(params), sysrate, other_pid); _snd_pcm_hw_param_setempty(params, SNDRV_PCM_HW_PARAM_RATE); return -EBUSY; } if (params_period_size(params) != hdspe->period_size) { spin_unlock_irq(&hdspe->lock); dev_warn(hdspe->card->dev, "Requested period size %d does not match actual latency used by process %d.\n", params_period_size(params), hdspe->period_size); _snd_pcm_hw_param_setempty(params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE); return -EBUSY; } } /* We're fine. */ spin_unlock_irq(&hdspe->lock); /* how to make sure that the rate matches an externally-set one ? */ spin_lock_irq(&hdspe->lock); err = hdspe_set_sample_rate(hdspe, params_rate(params)); if (err < 0) { dev_info(hdspe->card->dev, "err on hdspe_set_rate: %d\n", err); spin_unlock_irq(&hdspe->lock); _snd_pcm_hw_param_setempty(params, SNDRV_PCM_HW_PARAM_RATE); return err; } spin_unlock_irq(&hdspe->lock); err = hdspe_set_interrupt_interval(hdspe, params_period_size(params)); if (err < 0) { dev_info(hdspe->card->dev, "err on hdspe_set_interrupt_interval: %d\n", err); _snd_pcm_hw_param_setempty(params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE); return err; } /* Memory allocation, takashi's method, dont know if we should * spinlock */ /* malloc all buffer even if not enabled to get sure */ /* Update for MADI rev 204: we need to allocate for all channels, * otherwise it doesn't work at 96kHz */ err = snd_pcm_lib_malloc_pages(substream, HDSPE_DMA_AREA_BYTES); if (err < 0) { dev_info(hdspe->card->dev, "err on snd_pcm_lib_malloc_pages: %d\n", err); return err; } if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { /* Enable only the required DMA channels. */ for (i = 0; i < params_channels(params); ++i) { int c = hdspe->channel_map_out[i]; if (c < 0) continue; /* just make sure */ hdspe_set_channel_dma_addr(hdspe, substream, HDSPE_pageAddressBufferOut, c); snd_hdspe_enable_out(hdspe, c, 1); } hdspe->playback_buffer = (unsigned char *) substream->runtime->dma_area; dev_dbg(hdspe->card->dev, "Allocated sample buffer for playback at %p\n", hdspe->playback_buffer); } else { for (i = 0; i < params_channels(params); ++i) { int c = hdspe->channel_map_in[i]; if (c < 0) continue; hdspe_set_channel_dma_addr(hdspe, substream, HDSPE_pageAddressBufferIn, c); snd_hdspe_enable_in(hdspe, c, 1); } hdspe->capture_buffer = (unsigned char *) substream->runtime->dma_area; dev_dbg(hdspe->card->dev, "Allocated sample buffer for capture at %p\n", hdspe->capture_buffer); } /* dev_dbg(hdspe->card->dev, "Allocated sample buffer for %s at 0x%08X\n", substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? "playback" : "capture", snd_pcm_sgbuf_get_addr(substream, 0)); */ /* dev_dbg(hdspe->card->dev, "set_hwparams: %s %d Hz, %d channels, bs = %d\n", substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? "playback" : "capture", params_rate(params), params_channels(params), params_buffer_size(params)); */ /* Switch to native float format if requested, s32le otherwise. */ snd_hdspe_set_float_format( hdspe, params_format(params) == SNDRV_PCM_FORMAT_FLOAT_LE); return 0; } static int snd_hdspe_hw_free(struct snd_pcm_substream *substream) { int i; struct hdspe *hdspe = snd_pcm_substream_chip(substream); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { /* Just disable all channels. The saving when disabling a */ /* smaller set is not worth the trouble. */ for (i = 0; i < HDSPE_MAX_CHANNELS; ++i) snd_hdspe_enable_out(hdspe, i, 0); hdspe->playback_buffer = NULL; } else { for (i = 0; i < HDSPE_MAX_CHANNELS; ++i) snd_hdspe_enable_in(hdspe, i, 0); hdspe->capture_buffer = NULL; } snd_pcm_lib_free_pages(substream); return 0; } static int snd_hdspe_channel_info(struct snd_pcm_substream *substream, struct snd_pcm_channel_info *info) { struct hdspe *hdspe = snd_pcm_substream_chip(substream); unsigned int channel = info->channel; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { if (snd_BUG_ON(channel >= hdspe->max_channels_out)) { dev_info(hdspe->card->dev, "snd_hdspe_channel_info: output channel out of range (%d)\n", channel); return -EINVAL; } channel = array_index_nospec(channel, hdspe->max_channels_out); if (hdspe->channel_map_out[channel] < 0) { dev_info(hdspe->card->dev, "snd_hdspe_channel_info: output channel %d mapped out\n", channel); return -EINVAL; } info->offset = hdspe->channel_map_out[channel] * HDSPE_CHANNEL_BUFFER_BYTES; } else { if (snd_BUG_ON(channel >= hdspe->max_channels_in)) { dev_info(hdspe->card->dev, "snd_hdspe_channel_info: input channel out of range (%d)\n", channel); return -EINVAL; } channel = array_index_nospec(channel, hdspe->max_channels_in); if (hdspe->channel_map_in[channel] < 0) { dev_info(hdspe->card->dev, "snd_hdspe_channel_info: input channel %d mapped out\n", channel); return -EINVAL; } info->offset = hdspe->channel_map_in[channel] * HDSPE_CHANNEL_BUFFER_BYTES; } info->first = 0; info->step = 32; return 0; } static int snd_hdspe_ioctl(struct snd_pcm_substream *substream, unsigned int cmd, void *arg) { switch (cmd) { case SNDRV_PCM_IOCTL1_RESET: return snd_hdspe_reset(substream); case SNDRV_PCM_IOCTL1_CHANNEL_INFO: { struct snd_pcm_channel_info *info = arg; return snd_hdspe_channel_info(substream, info); } default: break; } return snd_pcm_lib_ioctl(substream, cmd, arg); } static int snd_hdspe_trigger(struct snd_pcm_substream *substream, int cmd) { struct hdspe *hdspe = snd_pcm_substream_chip(substream); struct snd_pcm_substream *other; int running; spin_lock(&hdspe->lock); running = hdspe->running; switch (cmd) { case SNDRV_PCM_TRIGGER_START: running |= 1 << substream->stream; break; case SNDRV_PCM_TRIGGER_STOP: running &= ~(1 << substream->stream); break; default: snd_BUG(); spin_unlock(&hdspe->lock); return -EINVAL; } if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) other = hdspe->capture_substream; else other = hdspe->playback_substream; if (other) { struct snd_pcm_substream *s; snd_pcm_group_for_each_entry(s, substream) { if (s == other) { snd_pcm_trigger_done(s, substream); if (cmd == SNDRV_PCM_TRIGGER_START) running |= 1 << s->stream; else running &= ~(1 << s->stream); goto _ok; } } if (cmd == SNDRV_PCM_TRIGGER_START) { if (!(running & (1 << SNDRV_PCM_STREAM_PLAYBACK)) && substream->stream == SNDRV_PCM_STREAM_CAPTURE) hdspe_silence_playback(hdspe); } else { if (running && substream->stream == SNDRV_PCM_STREAM_PLAYBACK) hdspe_silence_playback(hdspe); } } else { if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) hdspe_silence_playback(hdspe); } _ok: snd_pcm_trigger_done(substream, substream); if (!hdspe->running && running) hdspe_start_audio(hdspe); else if (hdspe->running && !running) hdspe_stop_audio(hdspe); hdspe->running = running; spin_unlock(&hdspe->lock); snd_ctl_notify(hdspe->card, SNDRV_CTL_EVENT_MASK_VALUE, hdspe->cid.running); return 0; } static int snd_hdspe_prepare(struct snd_pcm_substream *substream) { return 0; } static const struct snd_pcm_hardware snd_hdspe_playback_subinfo = { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_NONINTERLEAVED | SNDRV_PCM_INFO_SYNC_START | SNDRV_PCM_INFO_DOUBLE), .formats = SNDRV_PCM_FMTBIT_S32_LE, // .formats = SNDRV_PCM_FMTBIT_FLOAT_LE, .rates = (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_64000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000 ), .rate_min = 32000, .rate_max = 192000, .channels_min = 1, .channels_max = HDSPE_MAX_CHANNELS, .buffer_bytes_max = HDSPE_CHANNEL_BUFFER_BYTES * HDSPE_MAX_CHANNELS, .period_bytes_min = (32 * 4), .period_bytes_max = (8192 * 4) * HDSPE_MAX_CHANNELS, .periods_min = 2, .periods_max = 512, .fifo_size = 0 }; static const struct snd_pcm_hardware snd_hdspe_capture_subinfo = { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_NONINTERLEAVED | SNDRV_PCM_INFO_SYNC_START), .formats = SNDRV_PCM_FMTBIT_S32_LE, // .formats = SNDRV_PCM_FMTBIT_FLOAT_LE, .rates = (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_64000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000), .rate_min = 32000, .rate_max = 192000, .channels_min = 1, .channels_max = HDSPE_MAX_CHANNELS, .buffer_bytes_max = HDSPE_CHANNEL_BUFFER_BYTES * HDSPE_MAX_CHANNELS, .period_bytes_min = (32 * 4), .period_bytes_max = (8192 * 4) * HDSPE_MAX_CHANNELS, .periods_min = 2, .periods_max = 512, .fifo_size = 0 }; static int snd_hdspe_hw_rule_in_channels_rate(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) { struct hdspe *hdspe = rule->private; struct snd_interval *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); struct snd_interval *r = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); if (r->min > 96000 && r->max <= 192000) { struct snd_interval t = { .min = hdspe->t.qs_in_channels, .max = hdspe->t.qs_in_channels, .integer = 1, }; return snd_interval_refine(c, &t); } else if (r->min > 48000 && r->max <= 96000) { struct snd_interval t = { .min = hdspe->t.ds_in_channels, .max = hdspe->t.ds_in_channels, .integer = 1, }; return snd_interval_refine(c, &t); } else if (r->max < 64000) { struct snd_interval t = { .min = hdspe->t.ss_in_channels, .max = hdspe->t.ss_in_channels, .integer = 1, }; return snd_interval_refine(c, &t); } return 0; } static int snd_hdspe_hw_rule_out_channels_rate(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule * rule) { struct hdspe *hdspe = rule->private; struct snd_interval *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); struct snd_interval *r = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); if (r->min > 96000 && r->max <= 192000) { struct snd_interval t = { .min = hdspe->t.qs_out_channels, .max = hdspe->t.qs_out_channels, .integer = 1, }; return snd_interval_refine(c, &t); } else if (r->min > 48000 && r->max <= 96000) { struct snd_interval t = { .min = hdspe->t.ds_out_channels, .max = hdspe->t.ds_out_channels, .integer = 1, }; return snd_interval_refine(c, &t); } else if (r->max < 64000) { struct snd_interval t = { .min = hdspe->t.ss_out_channels, .max = hdspe->t.ss_out_channels, .integer = 1, }; return snd_interval_refine(c, &t); } else { } return 0; } static int snd_hdspe_hw_rule_rate_in_channels(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule * rule) { struct hdspe *hdspe = rule->private; struct snd_interval *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); struct snd_interval *r = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); if (c->min >= hdspe->t.ss_in_channels) { struct snd_interval t = { .min = 32000, .max = 48000, .integer = 1, }; return snd_interval_refine(r, &t); } else if (c->max <= hdspe->t.qs_in_channels) { struct snd_interval t = { .min = 128000, .max = 192000, .integer = 1, }; return snd_interval_refine(r, &t); } else if (c->max <= hdspe->t.ds_in_channels) { struct snd_interval t = { .min = 64000, .max = 96000, .integer = 1, }; return snd_interval_refine(r, &t); } return 0; } static int snd_hdspe_hw_rule_rate_out_channels(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) { struct hdspe *hdspe = rule->private; struct snd_interval *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); struct snd_interval *r = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); if (c->min >= hdspe->t.ss_out_channels) { struct snd_interval t = { .min = 32000, .max = 48000, .integer = 1, }; return snd_interval_refine(r, &t); } else if (c->max <= hdspe->t.qs_out_channels) { struct snd_interval t = { .min = 128000, .max = 192000, .integer = 1, }; return snd_interval_refine(r, &t); } else if (c->max <= hdspe->t.ds_out_channels) { struct snd_interval t = { .min = 64000, .max = 96000, .integer = 1, }; return snd_interval_refine(r, &t); } return 0; } static int snd_hdspe_hw_rule_in_channels(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) { unsigned int list[3]; struct hdspe *hdspe = rule->private; struct snd_interval *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); list[0] = hdspe->t.qs_in_channels; list[1] = hdspe->t.ds_in_channels; list[2] = hdspe->t.ss_in_channels; return snd_interval_list(c, 3, list, 0); } static int snd_hdspe_hw_rule_out_channels(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) { unsigned int list[3]; struct hdspe *hdspe = rule->private; struct snd_interval *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); list[0] = hdspe->t.qs_out_channels; list[1] = hdspe->t.ds_out_channels; list[2] = hdspe->t.ss_out_channels; return snd_interval_list(c, 3, list, 0); } static const unsigned int hdspe_aes_sample_rates[] = { 32000, 44100, 48000, 64000, 88200, 96000, 128000, 176400, 192000 }; static const struct snd_pcm_hw_constraint_list hdspe_hw_constraints_aes_sample_rates = { .count = ARRAY_SIZE(hdspe_aes_sample_rates), .list = hdspe_aes_sample_rates, .mask = 0 }; static int snd_hdspe_open(struct snd_pcm_substream *substream) { struct hdspe *hdspe = snd_pcm_substream_chip(substream); struct snd_pcm_runtime *runtime = substream->runtime; bool playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); spin_lock_irq(&hdspe->lock); snd_pcm_set_sync(substream); runtime->hw = (playback) ? snd_hdspe_playback_subinfo : snd_hdspe_capture_subinfo; if (playback) { if (!hdspe->capture_substream) hdspe_stop_audio(hdspe); hdspe->playback_pid = current->pid; hdspe->playback_substream = substream; } else { if (!hdspe->playback_substream) hdspe_stop_audio(hdspe); hdspe->capture_pid = current->pid; hdspe->capture_substream = substream; } spin_unlock_irq(&hdspe->lock); snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); snd_pcm_hw_constraint_pow2(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE); switch (hdspe->io_type) { case HDSPE_AIO: case HDSPE_RAYDAT: case HDSPE_AIO_PRO: snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 32, 4096); /* RayDAT & AIO have a fixed buffer of 16384 samples per channel */ snd_pcm_hw_constraint_single(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 16384); break; default: snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 64, 8192); snd_pcm_hw_constraint_single(runtime, SNDRV_PCM_HW_PARAM_PERIODS, 2); break; } if (HDSPE_AES == hdspe->io_type) { runtime->hw.rates |= SNDRV_PCM_RATE_KNOT; snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &hdspe_hw_constraints_aes_sample_rates); } else { snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, (playback ? snd_hdspe_hw_rule_rate_out_channels : snd_hdspe_hw_rule_rate_in_channels), hdspe, SNDRV_PCM_HW_PARAM_CHANNELS, -1); } snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, (playback ? snd_hdspe_hw_rule_out_channels : snd_hdspe_hw_rule_in_channels), hdspe, SNDRV_PCM_HW_PARAM_CHANNELS, -1); snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, (playback ? snd_hdspe_hw_rule_out_channels_rate : snd_hdspe_hw_rule_in_channels_rate), hdspe, SNDRV_PCM_HW_PARAM_RATE, -1); return 0; } static int snd_hdspe_release(struct snd_pcm_substream *substream) { struct hdspe *hdspe = snd_pcm_substream_chip(substream); bool playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); spin_lock_irq(&hdspe->lock); if (playback) { hdspe->playback_pid = -1; hdspe->playback_substream = NULL; } else { hdspe->capture_pid = -1; hdspe->capture_substream = NULL; } spin_unlock_irq(&hdspe->lock); return 0; } static const struct snd_pcm_ops snd_hdspe_ops = { .open = snd_hdspe_open, .close = snd_hdspe_release, .ioctl = snd_hdspe_ioctl, .hw_params = snd_hdspe_hw_params, .hw_free = snd_hdspe_hw_free, .prepare = snd_hdspe_prepare, .trigger = snd_hdspe_trigger, .pointer = snd_hdspe_hw_pointer, /* TODO: .get_time_info = snd_hdspe_get_time_info */ }; int snd_hdspe_create_pcm(struct snd_card *card, struct hdspe *hdspe) { struct snd_pcm *pcm; int err; hdspe->playback_pid = -1; hdspe->capture_pid = -1; hdspe->capture_substream = NULL; hdspe->playback_substream = NULL; err = snd_pcm_new(card, hdspe->card_name, 0, 1, 1, &pcm); if (err < 0) return err; hdspe->pcm = pcm; pcm->private_data = hdspe; strcpy(pcm->name, hdspe->card_name); snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_hdspe_ops); snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_hdspe_ops); pcm->info_flags = SNDRV_PCM_INFO_JOINT_DUPLEX; err = snd_hdspe_preallocate_memory(hdspe); if (err < 0) return err; hdspe_set_period_size(hdspe); return 0; } snd-hdspe-1.0.2/sound/pci/hdsp/hdspe/hdspe_proc.c000066400000000000000000000142401506037051300216360ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later /** * hdspe_proc.c * @brief RME HDSPe driver proc interface. * * 20210728 - Philippe.Bekaert@uhasselt.be * * Based on earlier work of the other MODULE_AUTHORs, * information kindly made available by RME (www.rme-audio.com), * and hardware kindly made available by Amptec Belgium (www.amptec.be). */ #include "hdspe.h" #include "hdspe_core.h" /*------------------------------------------------------------ /proc interface ------------------------------------------------------------*/ static void hdspe_iprintf_reg_bits(struct snd_info_buffer *buffer, __le32 reg, const char* const *bitNames) { uint32_t r = le32_to_cpu(reg); int i, k; for (i=0, k=0; i<32; i++) { if (r & (1<0 ? "|" : "", bitNames[i]); k++; } } } void hdspe_iprintf_reg(struct snd_info_buffer *buffer, const char* name, __le32 reg, const char* const *bitNames) { snd_iprintf(buffer, "%-8s: 0x%08x \t", name, le32_to_cpu(reg)); if (bitNames) hdspe_iprintf_reg_bits(buffer, reg, bitNames); snd_iprintf(buffer, "\n"); } void hdspe_iprint_fbits(struct snd_info_buffer *buffer, const char* name, u32 fbits) { int i; snd_iprintf(buffer, "%s\t: 0x%08x \t", name, fbits); for (i=0; i<8; i++) snd_iprintf(buffer, "%d ", HDSPE_FBITS_FREQ(fbits, i)); snd_iprintf(buffer, "\n"); } void hdspe_proc_read_common(struct snd_info_buffer *buffer, struct hdspe* hdspe, struct hdspe_status* s) { int i; hdspe->m.read_status(hdspe, s); snd_iprintf(buffer, "RME HDSPe %s serial %08d firmware %d\n\n", HDSPE_IO_TYPE_NAME(hdspe->io_type), hdspe->serial, hdspe->fw_build); snd_iprintf(buffer, "Buffer size\t\t: %u\n", s->buffer_size); snd_iprintf(buffer, "System sample rate\t: %lu\n", (long unsigned)div_u64(s->sample_rate_numerator, (u64)s->sample_rate_denominator)); snd_iprintf(buffer, "Internal sample rate\t: %lu\n", (long unsigned)div_u64(s->sample_rate_numerator, (u64)s->internal_sample_rate_denominator)); snd_iprintf(buffer, "Speed mode\t\t: %d %s\n", s->speed_mode, HDSPE_SPEED_NAME(s->speed_mode)); snd_iprintf(buffer, "Clock mode\t\t: %d %s\n", s->clock_mode, HDSPE_CLOCK_MODE_NAME(s->clock_mode)); snd_iprintf(buffer, "Preferred clock source\t: %d %s\n", s->preferred_ref, hdspe_clock_source_name(hdspe, s->preferred_ref)); snd_iprintf(buffer, "AutoSync ref\t\t: %d %s\n", s->autosync_ref, hdspe_clock_source_name(hdspe, s->autosync_ref)); snd_iprintf(buffer, "Internal freq\t\t: %d %s\n", s->internal_freq, hdspe_freq_name(s->internal_freq)); snd_iprintf(buffer, "External freq\t\t: %d %s\n", s->external_freq, hdspe_freq_name(s->external_freq)); for (i=0; isync[i] != HDSPE_SYNC_STATUS_NOT_AVAILABLE) snd_iprintf(buffer, "Sync source %2d %-8s\t: freq %d %-9s, sync %d %-9s %s\n", i, hdspe_clock_source_name(hdspe, i), s->freq[i], hdspe_freq_name(s->freq[i]), s->sync[i], HDSPE_SYNC_STATUS_NAME(s->sync[i]), i == s->autosync_ref ? "*" : ""); } snd_iprintf(buffer, "Single speed WCLK output: %d %s\n", s->wck48, HDSPE_BOOL_NAME(s->wck48)); snd_iprintf(buffer, "Clear TMS : %d %s\n", s->clr_tms, HDSPE_BOOL_NAME(s->clr_tms)); snd_iprintf(buffer, "\n"); } void hdspe_proc_read_common2(struct snd_info_buffer *buffer, struct hdspe* hdspe, struct hdspe_status* s) { int i; union hdspe_status0_reg status0 = hdspe_read_status0(hdspe); snd_iprintf(buffer, "\n"); snd_iprintf(buffer, "BUF_PTR\t: %05d\nBUF_ID\t: %d\nID_PTR\t: %05d\n", le16_to_cpu(status0.common.BUF_PTR)<<6, status0.common.BUF_ID, status0.common.BUF_ID * s->buffer_size*4); snd_iprintf(buffer, "LAT\t: %d\n", hdspe->reg.control.common.LAT); snd_iprintf(buffer, "\n"); snd_iprintf(buffer, "Running \t: %d\n", hdspe->running); snd_iprintf(buffer, "Capture PID \t: %d\n", hdspe->capture_pid); snd_iprintf(buffer, "Playback PID\t: %d\n", hdspe->playback_pid); snd_iprintf(buffer, "\n"); snd_iprintf(buffer, "Capture channel mapping:\n"); for (i = 0 ; i < hdspe->max_channels_in; i ++) { snd_iprintf(buffer, "Logical %d DMA %d '%s'\n", i, hdspe->channel_map_in[i], hdspe->port_names_in[i]); } snd_iprintf(buffer, "\nPlayback channel mapping:\n"); for (i = 0 ; i < hdspe->max_channels_out; i ++) { snd_iprintf(buffer, "Logical %d DMA %d '%s'\n", i, hdspe->channel_map_out[i], hdspe->port_names_out[i]); } } static void snd_hdspe_proc_ports_in(struct snd_info_entry *entry, struct snd_info_buffer *buffer) { struct hdspe *hdspe = entry->private_data; int i; snd_iprintf(buffer, "# generated by hdspe\n"); for (i = 0; i < hdspe->max_channels_in; i++) { snd_iprintf(buffer, "%d=%s\n", i+1, hdspe->port_names_in[i]); } } static void snd_hdspe_proc_ports_out(struct snd_info_entry *entry, struct snd_info_buffer *buffer) { struct hdspe *hdspe = entry->private_data; int i; snd_iprintf(buffer, "# generated by hdspe\n"); for (i = 0; i < hdspe->max_channels_out; i++) { snd_iprintf(buffer, "%d=%s\n", i+1, hdspe->port_names_out[i]); } } #ifdef CONFIG_SND_DEBUG static void snd_hdspe_proc_read_debug(struct snd_info_entry *entry, struct snd_info_buffer *buffer) { struct hdspe *hdspe = entry->private_data; int j,i; for (i = 0; i < 1024 /* 1024*64 */; i += j) { snd_iprintf(buffer, "%04d: ", i); for (j = 0; j < 16; j += 4) snd_iprintf(buffer, "%08X ", hdspe_read(hdspe, i + j)); snd_iprintf(buffer, "\n"); } } #endif void snd_hdspe_proc_init(struct hdspe *hdspe) { snd_card_ro_proc_new(hdspe->card, "hdspe", hdspe, hdspe->m.read_proc); snd_card_ro_proc_new(hdspe->card, "ports.in", hdspe, snd_hdspe_proc_ports_in); snd_card_ro_proc_new(hdspe->card, "ports.out", hdspe, snd_hdspe_proc_ports_out); if (hdspe->tco) snd_card_ro_proc_new(hdspe->card, "tco", hdspe, snd_hdspe_proc_read_tco); snd_card_ro_proc_new(hdspe->card, "mixer", hdspe, hdspe_mixer_read_proc); #ifdef CONFIG_SND_DEBUG /* debug file to read all hdspe registers */ snd_card_ro_proc_new(hdspe->card, "debug", hdspe, snd_hdspe_proc_read_debug); #endif } snd-hdspe-1.0.2/sound/pci/hdsp/hdspe/hdspe_raio.c000066400000000000000000000767541506037051300216470ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later /** * hdspe_raio.c * @brief RME HDSPe RayDAT / AIO / AIO Pro driver methods. * * 20210728 ... 1117 - Philippe.Bekaert@uhasselt.be * * Based on earlier work of the other MODULE_AUTHORS, * information kindly made available by RME (www.rme-audio.com), * and hardware kindly made available by Amptec Belgium (www.amptec.be). */ #include "hdspe.h" #include "hdspe_core.h" /* Maps RayDAT WR_SETTINGS / RD_STATUS1 sync ref 4-bit code to * hdspe_clock_source enum: identity mapping except for the unused codes. */ static enum hdspe_clock_source raydat_autosync_ref[16] = { HDSPE_CLOCK_SOURCE_WORD, HDSPE_CLOCK_SOURCE_AES, HDSPE_CLOCK_SOURCE_SPDIF, HDSPE_CLOCK_SOURCE_ADAT1, HDSPE_CLOCK_SOURCE_ADAT2, HDSPE_CLOCK_SOURCE_ADAT3, HDSPE_CLOCK_SOURCE_ADAT4, HDSPE_CLOCK_SOURCE_INTERN, // unused codes HDSPE_CLOCK_SOURCE_INTERN, HDSPE_CLOCK_SOURCE_TCO, HDSPE_CLOCK_SOURCE_SYNC_IN, HDSPE_CLOCK_SOURCE_INTERN, // unused codes HDSPE_CLOCK_SOURCE_INTERN, HDSPE_CLOCK_SOURCE_INTERN, HDSPE_CLOCK_SOURCE_INTERN, HDSPE_CLOCK_SOURCE_INTERN // master clock mode }; const char* const hdspe_raydat_clock_source_names[HDSPE_CLOCK_SOURCE_COUNT] = { HDSPE_CLOCK_SOURCE_NAME(HDSPE_RAYDAT, 0), HDSPE_CLOCK_SOURCE_NAME(HDSPE_RAYDAT, 1), HDSPE_CLOCK_SOURCE_NAME(HDSPE_RAYDAT, 2), HDSPE_CLOCK_SOURCE_NAME(HDSPE_RAYDAT, 3), HDSPE_CLOCK_SOURCE_NAME(HDSPE_RAYDAT, 4), HDSPE_CLOCK_SOURCE_NAME(HDSPE_RAYDAT, 5), HDSPE_CLOCK_SOURCE_NAME(HDSPE_RAYDAT, 6), HDSPE_CLOCK_SOURCE_NAME(HDSPE_RAYDAT, 7), HDSPE_CLOCK_SOURCE_NAME(HDSPE_RAYDAT, 8), HDSPE_CLOCK_SOURCE_NAME(HDSPE_RAYDAT, 9), HDSPE_CLOCK_SOURCE_NAME(HDSPE_RAYDAT, 10), HDSPE_CLOCK_SOURCE_NAME(HDSPE_RAYDAT, 11), HDSPE_CLOCK_SOURCE_NAME(HDSPE_RAYDAT, 12), HDSPE_CLOCK_SOURCE_NAME(HDSPE_RAYDAT, 13), HDSPE_CLOCK_SOURCE_NAME(HDSPE_RAYDAT, 14), HDSPE_CLOCK_SOURCE_NAME(HDSPE_RAYDAT, 15), }; /* Maps AIO / AIO Pro WR_SETTINGS / RD_STATUS1 sync ref 4-bit code to * hdspe_clock_source enum: identity mapping except for the unused codes. */ static enum hdspe_clock_source aio_autosync_ref[16] = { HDSPE_CLOCK_SOURCE_WORD, HDSPE_CLOCK_SOURCE_AES, HDSPE_CLOCK_SOURCE_SPDIF, HDSPE_CLOCK_SOURCE_ADAT, HDSPE_CLOCK_SOURCE_INTERN, // unused codes HDSPE_CLOCK_SOURCE_INTERN, HDSPE_CLOCK_SOURCE_INTERN, HDSPE_CLOCK_SOURCE_INTERN, HDSPE_CLOCK_SOURCE_INTERN, HDSPE_CLOCK_SOURCE_TCO, HDSPE_CLOCK_SOURCE_SYNC_IN, HDSPE_CLOCK_SOURCE_INTERN, // unused codes HDSPE_CLOCK_SOURCE_INTERN, HDSPE_CLOCK_SOURCE_INTERN, HDSPE_CLOCK_SOURCE_INTERN, HDSPE_CLOCK_SOURCE_INTERN // master clock mode }; const char* const hdspe_aio_clock_source_names[HDSPE_CLOCK_SOURCE_COUNT] = { HDSPE_CLOCK_SOURCE_NAME(HDSPE_AIO, 0), HDSPE_CLOCK_SOURCE_NAME(HDSPE_AIO, 1), HDSPE_CLOCK_SOURCE_NAME(HDSPE_AIO, 2), HDSPE_CLOCK_SOURCE_NAME(HDSPE_AIO, 3), HDSPE_CLOCK_SOURCE_NAME(HDSPE_AIO, 4), HDSPE_CLOCK_SOURCE_NAME(HDSPE_AIO, 5), HDSPE_CLOCK_SOURCE_NAME(HDSPE_AIO, 6), HDSPE_CLOCK_SOURCE_NAME(HDSPE_AIO, 7), HDSPE_CLOCK_SOURCE_NAME(HDSPE_AIO, 8), HDSPE_CLOCK_SOURCE_NAME(HDSPE_AIO, 9), HDSPE_CLOCK_SOURCE_NAME(HDSPE_AIO, 10), HDSPE_CLOCK_SOURCE_NAME(HDSPE_AIO, 11), HDSPE_CLOCK_SOURCE_NAME(HDSPE_AIO, 12), HDSPE_CLOCK_SOURCE_NAME(HDSPE_AIO, 13), HDSPE_CLOCK_SOURCE_NAME(HDSPE_AIO, 14), HDSPE_CLOCK_SOURCE_NAME(HDSPE_AIO, 15), }; /* Number of channels for different Speed Modes */ #define RAYDAT_SS_CHANNELS 36 #define RAYDAT_DS_CHANNELS 20 #define RAYDAT_QS_CHANNELS 12 #define AIO_IN_SS_CHANNELS 14 #define AIO_IN_DS_CHANNELS 10 #define AIO_IN_QS_CHANNELS 8 #define AIO_OUT_SS_CHANNELS 16 #define AIO_OUT_DS_CHANNELS 12 #define AIO_OUT_QS_CHANNELS 10 /* port names */ static const char * const texts_ports_raydat_ss[] = { "ADAT1.1", "ADAT1.2", "ADAT1.3", "ADAT1.4", "ADAT1.5", "ADAT1.6", "ADAT1.7", "ADAT1.8", "ADAT2.1", "ADAT2.2", "ADAT2.3", "ADAT2.4", "ADAT2.5", "ADAT2.6", "ADAT2.7", "ADAT2.8", "ADAT3.1", "ADAT3.2", "ADAT3.3", "ADAT3.4", "ADAT3.5", "ADAT3.6", "ADAT3.7", "ADAT3.8", "ADAT4.1", "ADAT4.2", "ADAT4.3", "ADAT4.4", "ADAT4.5", "ADAT4.6", "ADAT4.7", "ADAT4.8", "AES.L", "AES.R", "SPDIF.L", "SPDIF.R" }; static const char * const texts_ports_raydat_ds[] = { "ADAT1.1", "ADAT1.2", "ADAT1.3", "ADAT1.4", "ADAT2.1", "ADAT2.2", "ADAT2.3", "ADAT2.4", "ADAT3.1", "ADAT3.2", "ADAT3.3", "ADAT3.4", "ADAT4.1", "ADAT4.2", "ADAT4.3", "ADAT4.4", "AES.L", "AES.R", "SPDIF.L", "SPDIF.R" }; static const char * const texts_ports_raydat_qs[] = { "ADAT1.1", "ADAT1.2", "ADAT2.1", "ADAT2.2", "ADAT3.1", "ADAT3.2", "ADAT4.1", "ADAT4.2", "AES.L", "AES.R", "SPDIF.L", "SPDIF.R" }; static const char * const texts_ports_aio_in_ss[] = { "Analog.L", "Analog.R", "AES.L", "AES.R", "SPDIF.L", "SPDIF.R", "ADAT.1", "ADAT.2", "ADAT.3", "ADAT.4", "ADAT.5", "ADAT.6", "ADAT.7", "ADAT.8", "AEB.1", "AEB.2", "AEB.3", "AEB.4" }; static const char * const texts_ports_aio_out_ss[] = { "Analog.L", "Analog.R", "AES.L", "AES.R", "SPDIF.L", "SPDIF.R", "ADAT.1", "ADAT.2", "ADAT.3", "ADAT.4", "ADAT.5", "ADAT.6", "ADAT.7", "ADAT.8", "Phone.L", "Phone.R", "AEB.1", "AEB.2", "AEB.3", "AEB.4" }; static const char * const texts_ports_aio_in_ds[] = { "Analog.L", "Analog.R", "AES.L", "AES.R", "SPDIF.L", "SPDIF.R", "ADAT.1", "ADAT.2", "ADAT.3", "ADAT.4", "AEB.1", "AEB.2", "AEB.3", "AEB.4" }; static const char * const texts_ports_aio_out_ds[] = { "Analog.L", "Analog.R", "AES.L", "AES.R", "SPDIF.L", "SPDIF.R", "ADAT.1", "ADAT.2", "ADAT.3", "ADAT.4", "Phone.L", "Phone.R", "AEB.1", "AEB.2", "AEB.3", "AEB.4" }; static const char * const texts_ports_aio_in_qs[] = { "Analog.L", "Analog.R", "AES.L", "AES.R", "SPDIF.L", "SPDIF.R", "ADAT.1", "ADAT.2", "AEB.1", "AEB.2", "AEB.3", "AEB.4" }; static const char * const texts_ports_aio_out_qs[] = { "Analog.L", "Analog.R", "AES.L", "AES.R", "SPDIF.L", "SPDIF.R", "ADAT.1", "ADAT.2", "Phone.L", "Phone.R", "AEB.1", "AEB.2", "AEB.3", "AEB.4" }; /* These tables map the ALSA channels 1..N to the channels that we need to use in order to find the relevant channel buffer. RME refers to this kind of mapping as between "the ADAT channel and the DMA channel." We index it using the logical audio channel, and the value is the DMA channel (i.e. channel buffer number) where the data for that channel can be read/written from/to. */ static const char channel_map_raydat_ss[HDSPE_MAX_CHANNELS] = { 4, 5, 6, 7, 8, 9, 10, 11, /* ADAT 1 */ 12, 13, 14, 15, 16, 17, 18, 19, /* ADAT 2 */ 20, 21, 22, 23, 24, 25, 26, 27, /* ADAT 3 */ 28, 29, 30, 31, 32, 33, 34, 35, /* ADAT 4 */ 0, 1, /* AES */ 2, 3, /* SPDIF */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, }; static const char channel_map_raydat_ds[HDSPE_MAX_CHANNELS] = { 4, 5, 6, 7, /* ADAT 1 */ 8, 9, 10, 11, /* ADAT 2 */ 12, 13, 14, 15, /* ADAT 3 */ 16, 17, 18, 19, /* ADAT 4 */ 0, 1, /* AES */ 2, 3, /* SPDIF */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, }; static const char channel_map_raydat_qs[HDSPE_MAX_CHANNELS] = { 4, 5, /* ADAT 1 */ 6, 7, /* ADAT 2 */ 8, 9, /* ADAT 3 */ 10, 11, /* ADAT 4 */ 0, 1, /* AES */ 2, 3, /* SPDIF */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, }; static const char channel_map_aio_in_ss[HDSPE_MAX_CHANNELS] = { 0, 1, /* line in */ 8, 9, /* aes in, */ 10, 11, /* spdif in */ 12, 13, 14, 15, 16, 17, 18, 19, /* ADAT in */ 2, 3, 4, 5, /* AEB */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, }; static const char channel_map_aio_out_ss[HDSPE_MAX_CHANNELS] = { 0, 1, /* line out */ 8, 9, /* aes out */ 10, 11, /* spdif out */ 12, 13, 14, 15, 16, 17, 18, 19, /* ADAT out */ 6, 7, /* phone out */ 2, 3, 4, 5, /* AEB */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, }; static const char channel_map_aio_in_ds[HDSPE_MAX_CHANNELS] = { 0, 1, /* line in */ 8, 9, /* aes in */ 10, 11, /* spdif in */ 12, 13, 14, 15, /* adat in */ 2, 3, 4, 5, /* AEB */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; static const char channel_map_aio_out_ds[HDSPE_MAX_CHANNELS] = { 0, 1, /* line out */ 8, 9, /* aes out */ 10, 11, /* spdif out */ 12, 13, 14, 15, /* adat out */ 6, 7, /* phone out */ 2, 3, 4, 5, /* AEB */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; static const char channel_map_aio_in_qs[HDSPE_MAX_CHANNELS] = { 0, 1, /* line in */ 8, 9, /* aes in */ 10, 11, /* spdif in */ 12, 13, /* adat in */ 2, 3, 4, 5, /* AEB */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; static const char channel_map_aio_out_qs[HDSPE_MAX_CHANNELS] = { 0, 1, /* line out */ 8, 9, /* aes out */ 10, 11, /* spdif out */ 12, 13, /* adat out */ 6, 7, /* phone out */ 2, 3, 4, 5, /* AEB */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; #ifdef CONFIG_SND_DEBUG /* WR_CONTROL register bit names for AIO, AIO Pro and RayDAT */ static const char* const raio_control_bitNames[32] = { "START", "LAT_0", "LAT_1", "LAT_2", "(Master)", "IE_AUDIO", "freq0", "freq1", "freq2", "?09", "?10", "?11", "?12", "?13", "?14", "?15", "?16", "?17", "?18", "?19", "?20", "IEN2", "IEN0", "IEN1", "LineOut", "HDSPe_float_format", "IEN3", "?27", "?28", "?29", "?30", "freq3" }; /* WR_SETTINGS register bit names for RayDAT, AIO, AIO Pro */ static const char* const raio_settings_bitNames[32] = { "Master", "SyncRef0", "SyncRef1", "SyncRef2", "SyncRef3", "Wck48", "?06", "?07", "?08", "?09", "?10", "?11", "Input0", "Input1", "Spdif_Opt", "Pro", "clr_tms", "AEB1", "AEB2", "LineOut", "AD_GAIN0", "AD_GAIN1", "DA_GAIN0", "DA_GAIN1", "PH_GAIN0", "PH_GAIN1", "Sym6db", "?27", "?28", "?29", "?30", "?31", }; /* RD_STATUS1 register bit names for RayDAT, AIO, AIO Pro */ static const char* const raio_status1_bitNames[32] = { "lock0", "lock1", "lock2", "lock3", "lock4", "lock5", "lock6", "lock7", "sync0", "sync1", "sync2", "sync3", "sync4", "sync5", "sync6", "sync7", "wclk_freq0", "wclk_freq1", "wclk_freq2", "wclk_freq3", "tco_freq0", "tco_freq1", "tco_freq2", "tco_freq3", "wclk_lock", "wclk_sync", "tco_lock", "tco_sync", "sync_ref0", "sync_ref1", "sync_ref2", "sync_ref3" }; /* RD_STATUS2 register bit names for RayDAT, AIO, AIO Pro */ static const char* const raio_status2_bitNames[32] = { "?00", "?01", "?02", "?03", "?04", "?05", "tco_detect", "AEBO_D", "AEBI_D", "?09", "sync_in_lock", "sync_in_sync", "sync_in_freq0", "sync_in_freq1", "sync_in_freq2", "sync_in_freq3", "?16", "?17", "?18", "?19", "?20", "?21", "?22", "?23", "?24", "?25", "?26", "?27", "?28", "?29", "?30", "?31" }; #endif /*CONFIG_SND_DEBUG*/ static void hdspe_raio_read_status(struct hdspe* hdspe, struct hdspe_status* status) { int i; struct hdspe_settings_reg_raio settings = hdspe->reg.settings.raio; struct hdspe_status1_reg_raio status1 = hdspe_read_status1(hdspe).raio; struct hdspe_status2_reg_raio status2 = hdspe_read_status2(hdspe).raio; u32 fbits = hdspe_read_fbits(hdspe); status->version = HDSPE_VERSION; hdspe_read_sample_rate_status(hdspe, status); status->clock_mode = settings.Master; status->internal_freq = hdspe_internal_freq(hdspe); status->speed_mode = hdspe_speed_mode(hdspe); status->preferred_ref = settings.SyncRef; status->autosync_ref = status1.sync_ref; /* Word Clock Module (WCM) and Time Code Option (TCO) * share the same way of communicating status: as if WCM is a TCO */ if (!hdspe->tco) { if (status->preferred_ref == HDSPE_CLOCK_SOURCE_TCO) status->preferred_ref = HDSPE_CLOCK_SOURCE_WORD; if (status->autosync_ref == HDSPE_CLOCK_SOURCE_TCO) status->autosync_ref = HDSPE_CLOCK_SOURCE_WORD; } for (i = 0; i < HDSPE_CLOCK_SOURCE_COUNT; i++) { hdspe_set_sync_source(status, i, 0, false, false, false); } hdspe_set_sync_source(status, HDSPE_CLOCK_SOURCE_WORD, status2.tco_detect ? HDSPE_FREQ_NO_LOCK : status1.tco_freq, status1.tco_lock, status1.tco_sync, !status2.tco_detect); for (i = 0; i < (hdspe->io_type == HDSPE_RAYDAT ? 6 : 3); i++) { hdspe_set_sync_source(status, HDSPE_CLOCK_SOURCE_1+i, HDSPE_FBITS_FREQ(fbits, i), status1.lock & (1<external_freq = hdspe_speed_adapt( status->freq[status->autosync_ref], status->speed_mode); status->wck48 = settings.Wck48; status->clr_tms = settings.clr_tms; status->raio.aebo = !status2.AEBO_D; status->raio.aebi = !status2.AEBI_D; status->raio.aeb1 = settings.AEB1; status->raio.aeb2 = settings.AEB2; status->raio.spdif_in = settings.Input; status->raio.spdif_opt = settings.Spdif_Opt; status->raio.spdif_pro = settings.Pro; switch (hdspe->io_type) { case HDSPE_AIO: status->raio.aio.input_level = settings.AD_GAIN; status->raio.aio.output_level = settings.DA_GAIN; status->raio.aio.phones_level = settings.PH_GAIN; status->raio.aio.xlr = settings.Sym6db; break; case HDSPE_AIO_PRO: status->raio.aio_pro.input_level = settings.AD_GAIN; status->raio.aio_pro.output_level = settings.DA_GAIN + (settings.Sym6db ? 4 : 0); status->raio.aio_pro.phones_level = settings.PH_GAIN; break; case HDSPE_RAYDAT: break; default: snd_BUG(); } } #ifdef OLDSTUFF static bool hdspe_raio_has_status_changed(struct hdspe* hdspe) { __le32 status1 = hdspe_read_status1(hdspe).raw; __le32 status2 = hdspe_read_status2(hdspe).raw; u32 fbits = hdspe_read_fbits(hdspe); const __le32 mask2 = cpu_to_le32(0x0000fc00); // sync_in lock,sync,freq bool changed = ((status1 != hdspe->t.status1) || (status2 & mask2) != (hdspe->t.status2 & mask2) || (fbits != hdspe->t.fbits)); #ifdef NEVER if (changed) dev_dbg(hdspe->card->dev, "Status change: status1=%08x, status2=%08x, fbits=%08x\n", (status1 ^ hdspe->t.status1), (status2 ^ hdspe->t.status2) & mask2, (fbits ^ hdspe->t.fbits)); #endif /*NEVER*/ hdspe->t.status1 = status1; hdspe->t.status2 = status2; hdspe->t.fbits = fbits; return changed; } #endif /*OLDSTUFF*/ static void hdspe_raio_set_float_format(struct hdspe* hdspe, bool val) { hdspe->reg.control.raio.FloatFmt = val; hdspe_write_control(hdspe); } static bool hdspe_raio_get_float_format(struct hdspe* hdspe) { return hdspe->reg.control.raio.FloatFmt; } static enum hdspe_clock_mode hdspe_raio_get_clock_mode(struct hdspe* hdspe) { return hdspe->reg.settings.raio.Master; } static void hdspe_raio_set_clock_mode(struct hdspe* hdspe, enum hdspe_clock_mode master) { hdspe->reg.settings.raio.Master = master; hdspe_write_settings(hdspe); } static enum hdspe_clock_source hdspe_raio_get_preferred_sync_ref( struct hdspe* hdspe) { enum hdspe_clock_source *autosync_ref = hdspe->io_type == HDSPE_RAYDAT ? raydat_autosync_ref : aio_autosync_ref; enum hdspe_clock_source ref = autosync_ref[hdspe->reg.settings.raio.SyncRef]; if (ref == HDSPE_CLOCK_SOURCE_TCO && !hdspe->tco) ref = HDSPE_CLOCK_SOURCE_WORD; return ref; } static void hdspe_raio_set_preferred_sync_ref(struct hdspe* hdspe, enum hdspe_clock_source ref) { enum hdspe_clock_source *autosync_ref = hdspe->io_type == HDSPE_RAYDAT ? raydat_autosync_ref : aio_autosync_ref; if (autosync_ref[ref] == HDSPE_CLOCK_SOURCE_INTERN) ref = 0; if (ref == HDSPE_CLOCK_SOURCE_WORD) ref = HDSPE_CLOCK_SOURCE_TCO; hdspe->reg.settings.raio.SyncRef = ref; hdspe_write_settings(hdspe); } static enum hdspe_clock_source hdspe_raio_get_autosync_ref(struct hdspe* hdspe) { enum hdspe_clock_source *autosync_ref = hdspe->io_type == HDSPE_RAYDAT ? raydat_autosync_ref : aio_autosync_ref; enum hdspe_clock_source ref = autosync_ref[hdspe_read_status1(hdspe).raio.sync_ref]; if (ref == HDSPE_CLOCK_SOURCE_TCO && !hdspe->tco) ref = HDSPE_CLOCK_SOURCE_WORD; return ref; } #ifdef OLDSTUFF static enum hdspe_sync_status hdspe_raio_get_sync_status( struct hdspe* hdspe, enum hdspe_clock_source src) { struct hdspe_status1_reg_raio status1; struct hdspe_status2_reg_raio status2; int i; switch (src) { case HDSPE_CLOCK_SOURCE_WORD : status1 = hdspe_read_status1(hdspe).raio; return HDSPE_MAKE_SYNC_STATUS(status1.tco_lock, status1.tco_sync, !hdspe->tco); case HDSPE_CLOCK_SOURCE_AES : case HDSPE_CLOCK_SOURCE_SPDIF : case HDSPE_CLOCK_SOURCE_ADAT1 : i = src - HDSPE_CLOCK_SOURCE_1; status1 = hdspe_read_status1(hdspe).raio; return HDSPE_MAKE_SYNC_STATUS(status1.lock & (1<io_type == HDSPE_RAYDAT); case HDSPE_CLOCK_SOURCE_TCO : status1 = hdspe_read_status1(hdspe).raio; return HDSPE_MAKE_SYNC_STATUS(status1.tco_lock, status1.tco_sync, hdspe->tco); case HDSPE_CLOCK_SOURCE_SYNC_IN: status2 = hdspe_read_status2(hdspe).raio; return HDSPE_MAKE_SYNC_STATUS(status2.sync_in_lock, status2.sync_in_sync, true); default : return HDSPE_SYNC_STATUS_NOT_AVAILABLE; } } static enum hdspe_freq hdspe_raio_get_freq( struct hdspe* hdspe, enum hdspe_clock_source src) { switch (src) { case HDSPE_CLOCK_SOURCE_WORD : return hdspe->tco ? 0 : hdspe_read_status1(hdspe).raio.tco_freq; case HDSPE_CLOCK_SOURCE_AES : case HDSPE_CLOCK_SOURCE_SPDIF : case HDSPE_CLOCK_SOURCE_ADAT1 : case HDSPE_CLOCK_SOURCE_ADAT2 : case HDSPE_CLOCK_SOURCE_ADAT3 : case HDSPE_CLOCK_SOURCE_ADAT4 : return HDSPE_FBITS_FREQ(hdspe_read_fbits(hdspe), src - HDSPE_CLOCK_SOURCE_AES); case HDSPE_CLOCK_SOURCE_TCO : return hdspe->tco ? hdspe_read_status1(hdspe).raio.tco_freq : 0; case HDSPE_CLOCK_SOURCE_SYNC_IN: return hdspe_read_status2(hdspe).raio.sync_in_freq; default : return HDSPE_FREQ_NO_LOCK; } } static enum hdspe_freq hdspe_raio_get_external_freq(struct hdspe* hdspe) { enum hdspe_clock_source src = hdspe_raio_get_autosync_ref(hdspe); return hdspe_speed_adapt(hdspe_raio_get_freq(hdspe, src), hdspe_speed_mode(hdspe)); } #endif /*OLDSTUFF*/ static void hdspe_raio_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) { struct hdspe *hdspe = entry->private_data; struct hdspe_status s; hdspe_proc_read_common(buffer, hdspe, &s); if (hdspe->io_type == HDSPE_AIO || hdspe->io_type == HDSPE_AIO_PRO) { snd_iprintf(buffer, "Input AEB\t\t: %d %s\n", s.raio.aebi, HDSPE_BOOL_NAME(s.raio.aebi)); snd_iprintf(buffer, "Output AEB\t\t: %d %s\n", s.raio.aebo, HDSPE_BOOL_NAME(s.raio.aebo)); snd_iprintf(buffer, "ADAT internal\t\t: %d %s\n", s.raio.aeb1, HDSPE_BOOL_NAME(s.raio.aeb1)); } else if (hdspe->io_type == HDSPE_RAYDAT) { snd_iprintf(buffer, "ADAT1 internal\t\t: %d %s\n", s.raio.aeb1, HDSPE_BOOL_NAME(s.raio.aeb1)); snd_iprintf(buffer, "ADAT2 internal\t\t: %d %s\n", s.raio.aeb2, HDSPE_BOOL_NAME(s.raio.aeb2)); } snd_iprintf(buffer, "S/PDIF Input\t\t: %d %s\n", s.raio.spdif_in, HDSPE_RAIO_SPDIF_INPUT_NAME(s.raio.spdif_in)); snd_iprintf(buffer, "S/PDIF Optical output\t: %d %s\n", s.raio.spdif_opt, HDSPE_BOOL_NAME(s.raio.spdif_opt)); snd_iprintf(buffer, "S/PDIF Professional\t: %d %s\n", s.raio.spdif_pro, HDSPE_BOOL_NAME(s.raio.spdif_pro)); if (hdspe->io_type == HDSPE_AIO) { snd_iprintf(buffer, "Input Level\t\t: %d %s\n", s.raio.aio.input_level, HDSPE_AIO_LEVEL_NAME(s.raio.aio.input_level)); snd_iprintf(buffer, "Output Level\t\t: %d %s\n", s.raio.aio.output_level, HDSPE_AIO_LEVEL_NAME(s.raio.aio.output_level)); snd_iprintf(buffer, "Phones Level\t\t: %d %s\n", s.raio.aio.phones_level, HDSPE_AIO_LEVEL_NAME(s.raio.aio.phones_level)); snd_iprintf(buffer, "XLR \t\t: %d %s\n", s.raio.aio.xlr, HDSPE_BOOL_NAME(s.raio.aio.xlr)); } if (hdspe->io_type == HDSPE_AIO_PRO) { snd_iprintf(buffer, "Input Level\t\t: %d %s\n", s.raio.aio_pro.input_level, HDSPE_AIO_PRO_INPUT_LEVEL_NAME( s.raio.aio_pro.input_level)); snd_iprintf(buffer, "Output Level\t\t: %d %s\n", s.raio.aio_pro.output_level, HDSPE_AIO_PRO_OUTPUT_LEVEL_NAME( s.raio.aio_pro.output_level)); snd_iprintf(buffer, "Phones Level\t\t: %d %s\n", s.raio.aio_pro.phones_level, HDSPE_AIO_PRO_PHONES_LEVEL_NAME( s.raio.aio_pro.phones_level)); } snd_iprintf(buffer, "\n"); IPRINTREG(buffer, "CONTROL", hdspe->reg.control.raw, raio_control_bitNames); IPRINTREG(buffer, "SETTINGS", hdspe->reg.settings.raw, raio_settings_bitNames); { union hdspe_status1_reg status1 = hdspe_read_status1(hdspe); union hdspe_status2_reg status2 = hdspe_read_status2(hdspe); u32 fbits = hdspe_read_fbits(hdspe); IPRINTREG(buffer, "STATUS1", status1.raw, raio_status1_bitNames); IPRINTREG(buffer, "STATUS2", status2.raw, raio_status2_bitNames); hdspe_iprint_fbits(buffer, "FBITS", fbits); } hdspe_proc_read_common2(buffer, hdspe, &s); } static void hdspe_raio_get_card_info(struct hdspe* hdspe, struct hdspe_card_info *s) { struct hdspe_status2_reg_raio status2 = hdspe_read_status2(hdspe).raio; hdspe_get_card_info(hdspe, s); if (!status2.AEBI_D) s->expansion |= HDSPE_EXPANSION_AI4S; if (!status2.AEBO_D) s->expansion |= HDSPE_EXPANSION_AO4S; } static const struct hdspe_methods hdspe_raio_methods = { .get_card_info = hdspe_raio_get_card_info, .read_status = hdspe_raio_read_status, .get_float_format = hdspe_raio_get_float_format, .set_float_format = hdspe_raio_set_float_format, .read_proc = hdspe_raio_proc_read, #ifdef OLDSTUFF .get_freq = hdspe_raio_get_freq, .get_external_freq = hdspe_raio_get_external_freq, #endif /*OLDSTUFF*/ .get_autosync_ref = hdspe_raio_get_autosync_ref, .get_clock_mode = hdspe_raio_get_clock_mode, .set_clock_mode = hdspe_raio_set_clock_mode, .get_pref_sync_ref = hdspe_raio_get_preferred_sync_ref, .set_pref_sync_ref = hdspe_raio_set_preferred_sync_ref, #ifdef OLDSTUFF .get_sync_status = hdspe_raio_get_sync_status, .has_status_changed = hdspe_raio_has_status_changed #endif /*OLDSTUFF*/ }; static const struct hdspe_tables hdspe_raydat_tables = { .ss_in_channels = RAYDAT_SS_CHANNELS, .ss_out_channels = RAYDAT_SS_CHANNELS, .ds_in_channels = RAYDAT_DS_CHANNELS, .ds_out_channels = RAYDAT_DS_CHANNELS, .qs_in_channels = RAYDAT_QS_CHANNELS, .qs_out_channels = RAYDAT_QS_CHANNELS, .channel_map_in_ss = channel_map_raydat_ss, .channel_map_out_ss = channel_map_raydat_ss, .channel_map_in_ds = channel_map_raydat_ds, .channel_map_out_ds = channel_map_raydat_ds, .channel_map_in_qs = channel_map_raydat_qs, .channel_map_out_qs = channel_map_raydat_qs, .port_names_in_ss = texts_ports_raydat_ss, .port_names_out_ss = texts_ports_raydat_ss, .port_names_in_ds = texts_ports_raydat_ds, .port_names_out_ds = texts_ports_raydat_ds, .port_names_in_qs = texts_ports_raydat_qs, .port_names_out_qs = texts_ports_raydat_qs, .clock_source_names = hdspe_raydat_clock_source_names }; static void hdspe_raydat_init_tables(struct hdspe* hdspe) { hdspe->t = hdspe_raydat_tables; hdspe_init_autosync_tables( hdspe, ARRAY_SIZE(raydat_autosync_ref), raydat_autosync_ref); } static const struct hdspe_tables hdspe_aio_tables = { .ss_in_channels = AIO_IN_SS_CHANNELS, .ds_in_channels = AIO_IN_DS_CHANNELS, .qs_in_channels = AIO_IN_QS_CHANNELS, .ss_out_channels = AIO_OUT_SS_CHANNELS, .ds_out_channels = AIO_OUT_DS_CHANNELS, .qs_out_channels = AIO_OUT_QS_CHANNELS, .channel_map_out_ss = channel_map_aio_out_ss, .channel_map_out_ds = channel_map_aio_out_ds, .channel_map_out_qs = channel_map_aio_out_qs, .channel_map_in_ss = channel_map_aio_in_ss, .channel_map_in_ds = channel_map_aio_in_ds, .channel_map_in_qs = channel_map_aio_in_qs, .port_names_in_ss = texts_ports_aio_in_ss, .port_names_out_ss = texts_ports_aio_out_ss, .port_names_in_ds = texts_ports_aio_in_ds, .port_names_out_ds = texts_ports_aio_out_ds, .port_names_in_qs = texts_ports_aio_in_qs, .port_names_out_qs = texts_ports_aio_out_qs, .clock_source_names = hdspe_aio_clock_source_names }; static void hdspe_aio_init_tables(struct hdspe* hdspe) { hdspe->t = hdspe_aio_tables; if (!hdspe_read_status2(hdspe).raio.AEBI_D) { dev_info(hdspe->card->dev, "AI4S board found.\n"); hdspe->t.ss_in_channels += 4; hdspe->t.ds_in_channels += 4; hdspe->t.qs_in_channels += 4; } if (!hdspe_read_status2(hdspe).raio.AEBO_D) { dev_info(hdspe->card->dev, "AO4S board found.\n"); hdspe->t.ss_out_channels += 4; hdspe->t.ds_out_channels += 4; hdspe->t.qs_out_channels += 4; } hdspe_init_autosync_tables( hdspe, ARRAY_SIZE(aio_autosync_ref), aio_autosync_ref); } static struct hdspe_midi hdspe_raydat_midi_ports[3] = { {.portname = "MIDI 1", .dataIn = HDSPE_midiDataIn0, .statusIn = HDSPE_midiStatusIn0, .dataOut = HDSPE_midiDataOut0, .statusOut = HDSPE_midiStatusOut0, .ie = HDSPE_Midi0InterruptEnable, .irq = HDSPE_midi0IRQPending, }, {.portname = "MIDI 2", .dataIn = HDSPE_midiDataIn1, .statusIn = HDSPE_midiStatusIn1, .dataOut = HDSPE_midiDataOut1, .statusOut = HDSPE_midiStatusOut1, .ie = HDSPE_Midi1InterruptEnable, .irq = HDSPE_midi1IRQPending, }, {.portname = "MTC", .dataIn = HDSPE_midiDataIn2, .statusIn = HDSPE_midiStatusIn2, .dataOut = -1, .statusOut = -1, .ie = HDSPE_Midi2InterruptEnable, .irq = HDSPE_midi2IRQPending, } }; static struct hdspe_midi hdspe_aio_midi_ports[3] = { {.portname = "MIDI", .dataIn = HDSPE_midiDataIn0, .statusIn = HDSPE_midiStatusIn0, .dataOut = HDSPE_midiDataOut0, .statusOut = HDSPE_midiStatusOut0, .ie = HDSPE_Midi0InterruptEnable, .irq = HDSPE_midi0IRQPending, }, {.portname = "MTC", .dataIn = HDSPE_midiDataIn1, .statusIn = HDSPE_midiStatusIn1, .dataOut = -1, .statusOut = -1, .ie = HDSPE_Midi1InterruptEnable, .irq = HDSPE_midi1IRQPending, } }; int hdspe_init_raio(struct hdspe* hdspe) { int midi_count = 0; struct hdspe_midi *midi_ports = NULL; hdspe->reg.settings.raio.Master = true; hdspe->reg.settings.raio.Input = HDSPE_RAIO_SPDIF_INPUT_COAXIAL; hdspe->reg.settings.raio.LineOut = true; hdspe_write_settings(hdspe); hdspe->m = hdspe_raio_methods; switch (hdspe->io_type) { case HDSPE_RAYDAT: hdspe->card_name = "RME RayDAT"; midi_count = 2; midi_ports = hdspe_raydat_midi_ports; hdspe_raydat_init_tables(hdspe); break; case HDSPE_AIO: hdspe->card_name = "RME AIO"; midi_count = 1; midi_ports = hdspe_aio_midi_ports; hdspe_aio_init_tables(hdspe); break; case HDSPE_AIO_PRO: hdspe->card_name = "RME AIO Pro"; midi_count = 1; midi_ports = hdspe_aio_midi_ports; hdspe_aio_init_tables(hdspe); break; default: snd_BUG(); } hdspe_init_midi(hdspe, midi_count + (hdspe->tco ? 1 : 0), midi_ports); return 0; } void hdspe_terminate_raio(struct hdspe* hdspe) { if (hdspe->io_type == HDSPE_AIO_PRO) { hdspe->reg.settings.raio.LineOut = false; hdspe_write_settings(hdspe); } } #ifdef FROM_WIN_DRIVER //TODO void hwInitSettings(PDEVICE_EXTENSION deviceExtension) { if (deviceExtension->AutoSync) deviceExtension->Register0 |= c0_Master; else deviceExtension->Register0 &= ~c0_Master; deviceExtension->Register0 &= ~(c0_SyncRef0*0xF); deviceExtension->Register0 |= c0_SyncRef0*(deviceExtension->SyncRef & 0xF); if (deviceExtension->SingleSpeed) deviceExtension->Register0 |= c0_Wck48; else deviceExtension->Register0 &= ~c0_Wck48; if (deviceExtension->DoubleSpeed) deviceExtension->Register0 |= c0_DS_DoubleWire; else deviceExtension->Register0 &= ~c0_DS_DoubleWire; deviceExtension->Register0 &= ~(c0_QS_DoubleWire+c0_QS_QuadWire); switch (deviceExtension->QuadSpeed) { case 1: deviceExtension->Register0 |= c0_QS_DoubleWire; break; case 2: deviceExtension->Register0 |= c0_QS_QuadWire; break; } if (deviceExtension->Frame) deviceExtension->Register0 &= ~c0_Madi_Smux; else deviceExtension->Register0 |= c0_Madi_Smux; if (deviceExtension->Channels) deviceExtension->Register0 |= c0_Madi_64_Channels; else deviceExtension->Register0 &= ~c0_Madi_64_Channels; if (deviceExtension->AutoInput) deviceExtension->Register0 |= c0_Madi_AutoInput; else deviceExtension->Register0 &= ~c0_Madi_AutoInput; deviceExtension->Register0 &= ~(c0_Input0+c0_Input1); deviceExtension->Register0 |= c0_Input0*(deviceExtension->InputSource & 0x3); if (deviceExtension->OpticalOut) deviceExtension->Register0 |= c0_Spdif_Opt; else deviceExtension->Register0 &= ~c0_Spdif_Opt; if (deviceExtension->Professional) deviceExtension->Register0 |= c0_Pro; else deviceExtension->Register0 &= ~c0_Pro; if (!deviceExtension->Tms) deviceExtension->Register0 |= c0_clr_tms; else deviceExtension->Register0 &= ~c0_clr_tms; if (deviceExtension->Aeb) deviceExtension->Register0 |= c0_AEB1; else deviceExtension->Register0 &= ~c0_AEB1; if (deviceExtension->Emphasis) deviceExtension->Register0 |= c0_AEB2; // Emphasis == AEB2 else deviceExtension->Register0 &= ~c0_AEB2; if (deviceExtension->PhonesLevel == 2) deviceExtension->Register0 &= ~c0_LineOut; else deviceExtension->Register0 |= c0_LineOut; deviceExtension->Register0 &= ~(c0_AD_GAIN1+c0_AD_GAIN0); switch (deviceExtension->InputLevel) { case 0: break; case 1: deviceExtension->Register0 |= c0_AD_GAIN0; break; case 2: deviceExtension->Register0 |= c0_AD_GAIN1; break; case 3: deviceExtension->Register0 |= c0_AD_GAIN1+c0_AD_GAIN0; break; } deviceExtension->Register0 &= ~(c0_DA_GAIN1+c0_DA_GAIN0); switch (deviceExtension->OutputLevel) { case 0: break; case 1: deviceExtension->Register0 |= c0_DA_GAIN0; break; case 2: deviceExtension->Register0 |= c0_DA_GAIN1; break; case 3: deviceExtension->Register0 |= c0_DA_GAIN1+c0_DA_GAIN0; break; } deviceExtension->Register0 &= ~(c0_PH_GAIN1+c0_PH_GAIN0); switch (deviceExtension->PhonesLevel) { case 0: break; case 1: deviceExtension->Register0 |= c0_PH_GAIN0; break; case 2: deviceExtension->Register0 |= c0_PH_GAIN1; break; case 3: deviceExtension->Register0 |= c0_PH_GAIN1+c0_PH_GAIN0; break; } if (deviceExtension->Balanced) deviceExtension->Register0 |= c0_Sym6db; else deviceExtension->Register0 &= ~c0_Sym6db; WriteRegister(deviceExtension, WR_SETTINGS, deviceExtension->Register0); } #endif snd-hdspe-1.0.2/sound/pci/hdsp/hdspe/hdspe_tco.c000066400000000000000000001336021506037051300214640ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later /** * hdspe-tco.c * @brief RME HDSPe Time Code Option driver status and control interface. * * 20210728,0812,0902,24,28,1008,13,27,20220325,29,30 * - Philippe.Bekaert@uhasselt.be * * Based on earlier work of the other MODULE_AUTHORS, * information kindly made available by RME (www.rme-audio.com), * and hardware kindly made available by Amptec Belgium (www.amptec.be). */ #include "hdspe.h" #include "hdspe_core.h" #include "hdspe_control.h" #include "hdspe_ltc_math.h" #include #include #ifdef DEBUG_LTC #define LTC_TIMER_FREQ 100 #endif /*DEBUG_LTC*/ /** * TCO register definitions: * * TCO0 : contains time code. Status (at HDSPE_RD_TCO byte offset): report * current time code. Control (at HDSPE_WR_TCO offset): time code to set next. * * POS MASK STATUS CONTROL (same as STATUS) * * 00 f frames units * 04 30 frames tens * 06 40 unused * 07 80 sync (1) * 08 f00 seconds units * 12 7000 seconds tens * 15 8000 sync (1) * 16 f0000 minutes unts * 20 700000 minutes tens * 23 800000 sync (1) * 24 f000000 hour units * 28 30000000 hour tens * 30 40000000 unused * 31 80000000 sync (1) * * (1) the sync bits have no significance for what concerns the driver. * * TCO1 : status at byte offset HDSPE_RD_TCO+4, control at HDSPE_WR_TCO+4 * * POS MASK STATUS CONTROL * * 00 1 TCO lock internal WCK out defeat * 01 2 WCK input range WCK output range 0=SS, 1=DS, 2=QS * 02 4 " " * 03 8 LTC input valid * 04 10 WCK input valid * 05 20 video in format NTSC * 06 40 video in format PAL * 07 80 sync sync * 08 100 set TC upon rising edge * 09 200 LTC rx drop frame LTC tx drop frame 0 = full frame, 1 = DF * 10 400 LTC rx format LSB LTC tx format LSB 0=24, 1=25, 2=29.97, 3=30 * 11 800 LTC rx format MSB LTC tx format MSB rx only 24/25/30 * 12 1000 Q-frame nr LSB * 13 2000 Q-frame nr MSB * 14 4000 new quarter frame * 15 8000 sync sync * 16 10000 sample position LSB sample position LSB * 17 20000 " " * 18 40000 " " * 19 80000 " " * 20 100000 " " * 21 200000 " " * 22 400000 " " * 23 800000 sync sync * 24 1000000 sample position MSB sample position MSB * 25 2000000 " " * 26 4000000 " " * 27 8000000 " " * 28 10000000 " " * 29 20000000 " " * 30 40000000 " " * 31 80000000 sync sync * * TCO2 : status at byte offset HDSPE_RD_TCO+8, control at HDSPE_WR_TCO+8 * * POS MASK STATUS CONTROL * * 00 1 FS period counter LSB WCK period counted at * 01 2 " 25 MHz (10 bit), * 02 4 " 16-sample sliding * 03 8 " sum. * 04 10 " * 05 20 " * 06 40 " * 07 80 sync sync * 08 100 FS period counter MSB * 09 200 " * 10 400 " * 11 800 " * 12 1000 " * 13 2000 " * 14 4000 " * 15 8000 sync sync * 16 10000 TC run 0=pause, 1=run * 17 20000 WCK I/O rate 0=1:1, 1=44.1->48 * 18 40000 " 2=48->44.1 * 19 80000 output drop frames 0..2, 3=continuous * 20 100000 " * 21 200000 jam sync not implemented * 22 400000 flywheel not implemented * 23 800000 sync sync * 24 1000000 0.1 / 4 0=0.1%, 1=4% * 25 2000000 pull-down 0=off, 1=on * 26 4000000 pull-up 0=off, 1=on * 27 8000000 video in fps (1) sample rate 0=44.1KHz, 1=48KHz * 28 10000000 " 75 Ohm termination 0=off, 1=on * 29 20000000 " source select 0=WCK, 1=video, 2=LTC * 30 40000000 " " * 31 80000000 sync sync * * (1) firmware version 11 or later. 0=no lock, 1=23.98, 2=24, 3=25, 4=29.97 * 5=30, 6=47.95, 7=48, 8=50, 9=59.94, 10=60 * * TCO3 : status at byte offset HDSPE_RD_TCO+12, control at HDSPE_WR_TCO+12 * * POS MASK STATUS CONTROL * * 00 1 sync sync * 01 2 sync sync * 02 4 sync sync * 03 8 sync sync * 04 10 sync sync * 05 20 sync sync * 06 40 sync sync * 07 80 sync sync * 08 100 sync sync * 09 200 sync sync * 10 400 sync sync * 11 800 sync sync * 12 1000 sync sync * 13 2000 sync sync * 14 4000 sync sync * 15 8000 sync sync * 16 10000 * 17 20000 * 18 40000 * 19 80000 * 20 100000 * 21 200000 * 22 400000 TC number encoding not implemented * 23 800000 sync sync * 24 1000000 version LSB * 25 2000000 " * 26 4000000 " * 27 8000000 " * 28 10000000 " * 29 20000000 " * 30 40000000 version MSB * 31 80000000 sync sync * */ #define HDSPE_TCO1_TCO_lock 0x00000001 #define HDSPE_TCO1_WCK_Input_Range_LSB 0x00000002 #define HDSPE_TCO1_WCK_Input_Range_MSB 0x00000004 #define HDSPE_TCO1_LTC_Input_valid 0x00000008 #define HDSPE_TCO1_WCK_Input_valid 0x00000010 #define HDSPE_TCO1_Video_Input_Format_NTSC 0x00000020 #define HDSPE_TCO1_Video_Input_Format_PAL 0x00000040 #define HDSPE_TCO1_set_TC 0x00000100 #define HDSPE_TCO1_set_drop_frame_flag 0x00000200 #define HDSPE_TCO1_LTC_Format_LSB 0x00000400 #define HDSPE_TCO1_LTC_Format_MSB 0x00000800 #define HDSPE_TCO1_STATUS_MASK 0x00000c7f #define HDSPE_TCO2_TC_run 0x00010000 #define HDSPE_TCO2_WCK_IO_ratio_LSB 0x00020000 #define HDSPE_TCO2_WCK_IO_ratio_MSB 0x00040000 #define HDSPE_TCO2_set_num_drop_frames_LSB 0x00080000 /* unused */ #define HDSPE_TCO2_set_num_drop_frames_MSB 0x00100000 /* unused */ #define HDSPE_TCO2_set_jam_sync 0x00200000 /* unused */ #define HDSPE_TCO2_set_flywheel 0x00400000 /* unused */ #define HDSPE_TCO2_set_01_4 0x01000000 #define HDSPE_TCO2_set_pull_down 0x02000000 #define HDSPE_TCO2_set_pull_up 0x04000000 #define HDSPE_TCO2_set_freq 0x08000000 #define HDSPE_TCO2_set_term_75R 0x10000000 #define HDSPE_TCO2_set_input_LSB 0x20000000 #define HDSPE_TCO2_set_input_MSB 0x40000000 #define HDSPE_TCO2_set_freq_from_app 0x80000000 #ifdef CONFIG_SND_DEBUG static const char* const tco1_bitNames[32] = { "TCO_lock", "WCK_Input_Range_LSB", "WCK_Input_Range_MSB", "LTC_Input_valid", "WCK_Input_valid", "Video_Input_Format_NTSC", "Video_Input_Format_PAL", "sync", "set_TC", "set_drop_frame_flag", "LTC_Format_LSB", "LTC_Format_MSB", "Q-frame nr LSB", "Q-frame nr MSB", "new Q-frame", "sync", "off0", "off1", "off2", "off3", "off4", "off5", "off6", "sync", "off7", "off8", "off9", "off10", "off11", "off12", "off13", "sync" }; #ifdef NEVER static const char* const tco2_bitNames[32] = { "?00", "?01", "?02", "?03", "?04", "?05", "?06", "sync", "?08", "?09", "?10", "?11", "?12", "?13", "?14", "sync", "TC_run", "WCK_IO_ratio_LSB", "WCK_IO_ratio_MSB", "set_num_drop_frames_LSB", "set_num_drop_frames_MSB", "set_jam_sync", "set_flywheel", "sync", "set_01_4", "set_pull_down", "set_pull_up", "set_freq", "set_term_75R", "set_input_LSB", "set_input_MSB", "set_freq_from_app", }; #endif /*NEVER*/ #endif /*CONFIG_SND_DEBUG*/ /* * The TCO module sends quarter frame MTC messages when valid LTC input is * detected and running. Piece 0 and 4 quarter frame MTC interrupts are * generated at the precise instant a time code ends. * * Time code can also be queried at any time by reading TCO status register 0. * The most significant bits from TCO status register 1 contain the time offset, * measured in audio frames, since the start of the current time code. (The * offset comes in two groups of 7 bits.) * * When the cards audio engine is not running (audio interrupts not enabled), * the registers are updated continuously, and can be used e.g. to measure * (MTC) interrupt handling latency, or to correlate LTC with system time. * Accuracy is about the time to read a register from the card. * * However, when the cards audio engine is running (audio interrupts enabled), * the registers are updated only at audio period interrupt time and certain * MTC interrupts (at longer period sizes). They seem trustworthy only at the * time of an audio period interrupt. * * The reported time code is the last time code that was fully received. If * time codes are running forward, the current time code at the time of an * audio period interrupt, will be one frame ahead of what the status * register 0 tells. If running backward, the current time code is one frame * earlier. * * Setting LTC for output only works if audio interrupts are enabled. The * LTC code set in TCO control register 0 will start running after the * number of audio frames set in TCO control register 1 elapsed, past the * next audio period interrupt time. So, LTC and offset for output need to * be queued ahead of time. We do that at audio interrupt time, assuming the * command will be certainly processed by the card by the next audio interrupt * time. User specified offsets and time code are adapted accordingly. */ static inline __attribute__((always_inline)) u32 hdspe_read_tco(struct hdspe* hdspe, unsigned n) { return le32_to_cpu(hdspe_read(hdspe, HDSPE_RD_TCO+4*n)); } static inline __attribute__((always_inline)) void hdspe_write_tco(struct hdspe* hdspe, unsigned n, u32 value) { value &= 0x7f7f7f7f; hdspe_write(hdspe, HDSPE_WR_TCO+n*4, cpu_to_le32(value)); } static void hdspe_tco_read_status1(struct hdspe* hdspe, struct hdspe_tco_status* s) { u32 tco1 = hdspe_read_tco(hdspe, 1); s->tco_lock = FIELD_GET(HDSPE_TCO1_TCO_lock, tco1); s->ltc_valid = FIELD_GET(HDSPE_TCO1_LTC_Input_valid, tco1); s->ltc_in_fps = FIELD_GET(HDSPE_TCO1_LTC_Format_MSB| HDSPE_TCO1_LTC_Format_LSB, tco1); s->ltc_in_drop = FIELD_GET(HDSPE_TCO1_set_drop_frame_flag, tco1); s->video = FIELD_GET(HDSPE_TCO1_Video_Input_Format_NTSC| HDSPE_TCO1_Video_Input_Format_PAL, tco1); s->wck_valid = FIELD_GET(HDSPE_TCO1_WCK_Input_valid, tco1); s->wck_speed = FIELD_GET(HDSPE_TCO1_WCK_Input_Range_MSB| HDSPE_TCO1_WCK_Input_Range_LSB, tco1); /* Current time code started this many audio frames ago. * Note: offset and time code are updated only at audio period * interrupt time if audio interrupts are enabled. */ s->ltc_in_offset = ((tco1 >> 16) & 0x7F) | ((tco1 >> 17) & 0x3F80); } static void hdspe_tco_read_status2(struct hdspe* hdspe, struct hdspe_tco_status* s) { u32 tco2 = hdspe_read_tco(hdspe, 2); s->fs_period_counter = (tco2 & 0x7F) | ((tco2 & 0x7F00) >> 1); s->video_in_fps = (tco2 >> 27) & 0x0F; } static void hdspe_tco_copy_control(struct hdspe* hdspe, struct hdspe_tco_status* s) { snd_BUG_ON(!hdspe->tco); if (!hdspe->tco) return; s->input = hdspe->tco->input; s->ltc_fps = hdspe->tco->ltc_fps; s->ltc_drop = hdspe->tco->ltc_drop; s->sample_rate = hdspe->tco->sample_rate; s->pull = hdspe->tco->pull; s->wck_conversion = hdspe->tco->wck_conversion; s->term = hdspe->tco->term; s->ltc_run = hdspe->tco->ltc_run; s->ltc_flywheel = hdspe->tco->ltc_flywheel; s->wck_out_speed = hdspe->tco->wck_out_speed; } void hdspe_tco_read_status(struct hdspe* hdspe, struct hdspe_tco_status* s) { spin_lock(&hdspe->tco->lock); s->version = HDSPE_VERSION; s->fw_version = hdspe->tco->fw_version; s->ltc_in = hdspe_read_tco(hdspe, 0); hdspe_tco_read_status1(hdspe, s); hdspe_tco_read_status2(hdspe, s); hdspe_tco_copy_control(hdspe, s); spin_unlock(&hdspe->tco->lock); } static void hdspe_tco_write_settings(struct hdspe* hdspe) { static const int pullbits[HDSPE_PULL_COUNT] = { 0, HDSPE_TCO2_set_pull_up, HDSPE_TCO2_set_pull_down, HDSPE_TCO2_set_pull_up|HDSPE_TCO2_set_01_4, HDSPE_TCO2_set_pull_down|HDSPE_TCO2_set_01_4 }; u32* reg; bool sys_48KHz = (hdspe->reg.control.common.freq == 3); struct hdspe_tco* c = hdspe->tco; if (!c) { snd_BUG(); return; } reg = c->reg; reg[0] = reg[1] = reg[2] = reg[3] = 0; reg[1] |= FIELD_PREP(HDSPE_TCO1_WCK_Input_Range_MSB| HDSPE_TCO1_WCK_Input_Range_LSB, c->wck_out_speed); reg[1] |= FIELD_PREP(HDSPE_TCO1_LTC_Format_MSB| HDSPE_TCO1_LTC_Format_LSB, c->ltc_fps); reg[1] |= FIELD_PREP(HDSPE_TCO1_set_drop_frame_flag, c->ltc_drop); reg[2] |= FIELD_PREP(HDSPE_TCO2_set_input_MSB| HDSPE_TCO2_set_input_LSB, c->input); reg[2] |= FIELD_PREP(HDSPE_TCO2_WCK_IO_ratio_MSB| HDSPE_TCO2_WCK_IO_ratio_LSB, c->wck_conversion); reg[2] |= FIELD_PREP(HDSPE_TCO2_set_freq, c->sample_rate == HDSPE_TCO_SAMPLE_RATE_48 || (c->sample_rate == HDSPE_TCO_SAMPLE_RATE_FROM_APP && sys_48KHz)); reg[2] |= FIELD_PREP(HDSPE_TCO2_set_freq_from_app, c->sample_rate == HDSPE_TCO_SAMPLE_RATE_FROM_APP); reg[2] |= FIELD_PREP(HDSPE_TCO2_set_term_75R, c->term); reg[2] |= pullbits[c->pull % HDSPE_PULL_COUNT]; reg[2] |= FIELD_PREP(HDSPE_TCO2_TC_run, c->ltc_run); reg[2] |= FIELD_PREP(HDSPE_TCO2_set_flywheel, c->ltc_flywheel); hdspe_write_tco(hdspe, 0, reg[0]); hdspe_write_tco(hdspe, 1, reg[1]); hdspe_write_tco(hdspe, 2, reg[2]); hdspe_write_tco(hdspe, 3, reg[3]); } void hdspe_tco_set_app_sample_rate(struct hdspe* hdspe) { /* Set/clear TCO2_set_freq bit when internal frequency * of the sound card is changed to something not corresponding * with TCO card frequency, and TCO sample rate is "From App". */ struct hdspe_tco* c = hdspe->tco; bool tco_48KHz, sys_48KHz; if (!c) return; if (c->sample_rate != HDSPE_TCO_SAMPLE_RATE_FROM_APP) return; tco_48KHz = FIELD_GET(HDSPE_TCO2_set_freq, c->reg[2]); sys_48KHz = (hdspe->reg.control.common.freq == 3); if (tco_48KHz != sys_48KHz) { c->reg[2] &= ~HDSPE_TCO2_set_freq; c->reg[2] |= FIELD_PREP(HDSPE_TCO2_set_freq, sys_48KHz); hdspe_write_tco(hdspe, 2, c->reg[2]); dev_dbg(hdspe->card->dev, "%s: 48KHz %s.\n", __func__, sys_48KHz ? "ON" : "OFF"); } } //////////////////////////////////////////////////////////////////////////// static u32 hdspe_tco_get_sample_rate(struct hdspe* hdspe) { struct hdspe_tco* c = hdspe->tco; bool tco_48KHz = FIELD_GET(HDSPE_TCO2_set_freq, c->reg[2]); return tco_48KHz ? 48000 : 44100; } static void hdspe_tco_set_timecode(struct hdspe* hdspe, u32 timecode, u16 offset) { struct hdspe_tco* c = hdspe->tco; /* offset is stored as two groups of 7 bits */ uint32_t offset2 = ((offset & 0x3f80)<<1) | (offset & 0x7f); hdspe_write_tco(hdspe, 0, timecode); hdspe_write_tco(hdspe, 1, (offset2 << 16) | HDSPE_TCO1_set_TC | (c->reg[1] & 0xffff)); c->ltc_set = true; dev_dbg(hdspe->card->dev, "%s: timecode=%02x:%02x:%02x:%02x, offset=%d\n", __func__, (timecode>>24)&0x3f, (timecode>>16)&0x7f, (timecode>>8)&0x7f, timecode&0x3f, offset); } static void hdspe_tco_reset_timecode(struct hdspe* hdspe) { struct hdspe_tco* c = hdspe->tco; hdspe_write_tco(hdspe, 1, c->reg[1] & 0xffff & ~HDSPE_TCO1_set_TC); c->ltc_set = false; dev_dbg(hdspe->card->dev, "%s\n", __func__); } /* Linear Time Code and associated status */ struct hdspe_ltc { u64 fc; /* frame count at start */ u32 tc; /* 32-bit LTC code */ u16 scale; /* 999 or 1000 */ u8 fps; /* 24, 25 or 30 */ bool df; /* drop frame format */ }; static const u32 hdspe_fps_tab[4] = { 24, 25, 30, 30 }; static const u32 hdspe_scale_tab[4] = {1000, 1000, 999, 1000 }; /* Offsets needed when starting time code, experimentally determined and * verified. */ static u32 hdspe_ltc_offset(u32 fps, enum hdspe_freq f) { u32 offset = 0; if (fps == 24) { switch (f) { case 2: offset = 13; break; // 16; break; case 3: offset = 16; break; // 17; break; #ifdef NEVER case 5: offset = 31; break; // unused case 6: offset = 34; break; // unused case 8: offset = 62; break; // unused case 9: offset = 68; break; // unused #endif /*NEVER*/ default: offset = 0; } } else if (fps == 25) { switch (f) { case 2: offset = 15; break; case 3: offset = 16; break; #ifdef NEVER case 5: offset = 30; break; // unused case 6: offset = 32; break; // unused case 8: offset = 60; break; // unused case 9: offset = 64; break; // unused #endif /*NEVER*/ default: offset = 0; } } else if (fps == 30) { switch (f) { case 2: offset = 13; break; // 14; break; case 3: offset = 14; break; #ifdef NEVER case 5: offset = 28; break; // unused case 6: offset = 28; break; // unused case 8: offset = 56; break; // unused case 9: offset = 56; break; // unused #endif /*NEVER*/ default: offset = 0; } } return offset; } static void hdspe_tco_start_timecode(struct hdspe* hdspe) { struct hdspe_tco* c = hdspe->tco; u64 cfc = hdspe->frame_count; /* current frame count */ u32 fs; /* LTC frame size in samples */ u32 ps = hdspe_period_size(hdspe); /* period size in samples */ int n; /* compensate this many frames w.r.t. pickup at next period */ s32 offset; /* nr of samples to delay LTC start at next period */ u32 sr = hdspe_tco_get_sample_rate(hdspe); /* sample rate */ u32 speedfactor = hdspe_speed_factor(hdspe); /* 1, 2 or 4 */ struct hdspe_ltc ltc; ltc.tc = c->ltc_out; ltc.fc = c->ltc_out_frame_count; ltc.fps = hdspe_fps_tab[c->ltc_fps]; ltc.scale = hdspe_scale_tab[c->ltc_fps]; ltc.df = c->ltc_drop; ltc.fc /= speedfactor; /* need single speed offset, */ cfc /= speedfactor; /* frame count */ ps /= speedfactor; /* and period size */ fs = sr * 1000 / (ltc.fps * ltc.scale); if ((ltc.tc & 0x3f7f7f3f) == 0x3f7f7f3f) { /* this invalid time code means "real clock time" * frame count contains an offset in seconds, typically * timezone seconds east of UTC */ struct timespec64 ts; struct tm tm; ktime_get_real_ts64(&ts); time64_to_tm(ts.tv_sec + ltc.fc, 0, &tm); ltc.tc = hdspe_ltc32_compose(tm.tm_hour, tm.tm_min, tm.tm_sec, 0); ltc.fc = cfc - ts.tv_nsec / (1000000000 / sr); } if (ltc.fc == (u64)-1) /* means 'now' */ ltc.fc = cfc; /* reduce ltc.fc to valid offset, taking into account it will be picked * up by the hardware only at the next period interrupt */ n = 0; if (ltc.fc > cfc + 2 * ps + fs) { n = -(ltc.fc - (cfc + 2 * ps)) / fs; } else if (ltc.fc < cfc + 2 * ps) { n = ((cfc + 2 * ps) - ltc.fc) / fs + 1; } ltc.fc += n * fs; ltc.tc = hdspe_ltc32_add_frames(n, ltc.tc, ltc.fps, ltc.df); offset = ltc.fc - (cfc + ps); /* pickup at next audio period */ dev_dbg(hdspe->card->dev, "%s: compensate %d frames: tc=%08x, fc=%llu, offset=%d\n", __func__, n, ltc.tc&0x3f7f7f3f, ltc.fc, offset); offset -= hdspe_ltc_offset(ltc.fps, hdspe_sample_rate_freq(sr)); if (offset < 0 || (offset & ~0x3fff) != 0) { dev_warn(hdspe->card->dev, "%s: offset %d out of range 0..%d.\n", __func__, offset, 0x3fff); } hdspe_tco_set_timecode(hdspe, ltc.tc, offset); c->ltc_out = 0xffffffff; hdspe_write_tco(hdspe, 2, c->reg[2] |= HDSPE_TCO2_TC_run); c->ltc_run = true; HDSPE_CTL_NOTIFY(ltc_run); } static void hdspe_tco_stop_timecode(struct hdspe* hdspe) { struct hdspe_tco* c = hdspe->tco; dev_dbg(hdspe->card->dev, "%s\n", __func__); hdspe_write_tco(hdspe, 2, c->reg[2] &= ~HDSPE_TCO2_TC_run); c->ltc_run = false; } static void hdspe_tco_read_ltc(struct hdspe* hdspe, struct hdspe_ltc *ltc, const char* where) { u32 tco1, offset, framerate, tc; tc = hdspe_read_tco(hdspe, 0); tco1 = hdspe_read_tco(hdspe, 1); if ((ltc->tc = hdspe_read_tco(hdspe, 0)) != tc) { /* time code changed while we were reading tco1 */ tco1 = hdspe_read_tco(hdspe, 1); } /* the offset comes in two groups of 7 bits indeed */ offset = ((tco1 >> 16) & 0x7F) | ((tco1 >> 17) & 0x3F80); ltc->fc = hdspe->frame_count - offset * hdspe_speed_factor(hdspe); framerate = FIELD_GET(HDSPE_TCO1_LTC_Format_MSB| HDSPE_TCO1_LTC_Format_LSB, tco1); ltc->fps = hdspe_fps_tab[framerate]; ltc->scale = hdspe_scale_tab[framerate]; ltc->df = FIELD_GET(HDSPE_TCO1_set_drop_frame_flag, tco1); #ifdef DEBUG_LTC { struct timespec64 t; ktime_get_raw_ts64(&t); dev_dbg(hdspe->card->dev, "%lld.%05ld: %s: TC %02x:%02x:%02x:%02x, TC frame count=%lld, period frame count=%lld, TC offset=%u.\n", t.tv_sec, t.tv_nsec / 10000, where, (ltc->tc>>24)&0x3f, (ltc->tc>>16)&0x7f, (ltc->tc>>8)&0x7f, (ltc->tc&0x3f), ltc->frame_count, hdspe->frame_count, ltc_offset); } #endif /*DEBUG_LTC*/ } #ifdef DEBUG_MTC void hdspe_tco_qmtc(struct hdspe* hdspe, u8 quarter_frame_msg) { u8 piecenr = (quarter_frame_msg >> 4) & 0x0f; u8 bits = quarter_frame_msg & 0x0f; u32 mtc = (hdspe->tco->mtc & ~(0x0f << (4*piecenr))) | (bits << (4*piecenr)); hdspe->tco->mtc = mtc; { struct timespec64 t; ktime_get_raw_ts64(&t); dev_dbg(hdspe->card->dev, "%lld.%05ld: %s: MTC %02d:%02d:%02d:%02d piece %d.\n", t.tv_sec, t.tv_nsec / 10000, __func__, (mtc>>24)&0x1f, (mtc>>16)&0x3f, (mtc>> 8)&0x3f, (mtc )&0x1f, piecenr); } } #endif /*DEBUG_MTC*/ void hdspe_tco_mtc(struct hdspe* hdspe, const u8* buf, int count) { struct hdspe_tco *c = hdspe->tco; bool newtc = false; if (count == 10 && buf[0] == 0xf0 && buf[1] == 0x7f && buf[2] == 0x7f && buf[3] == 0x01 && buf[4] == 0x01 && buf[9] == 0xf7) { /* full time code message */ newtc = true; } if (count == 2 && buf[0] == 0xf1) { /* quarter frame message */ int piecenr = (buf[1]>>4) & 0xf; newtc = (piecenr == 0 || piecenr == 4); #ifdef DEBUG_MTC hdspe_tco_qmtc(hdspe, buf[1]); #endif /*DEBUG_MTC*/ } if (newtc) { uint64_t now = ktime_get_real_ns(); #ifdef DEBUG_LTC struct hdspe_ltc ltc; hdspe_tco_read_ltc(hdspe, <c, __func__); #endif /*DEBUG_LTC*/ spin_lock(&hdspe->tco->lock); if (c->prev_ltc_time > 0) { int n = c->ltc_count % LTC_CACHE_SIZE; c->ltc_duration_sum -= c->ltc_duration[n]; c->ltc_duration[n] = now - c->prev_ltc_time; c->ltc_duration_sum += c->ltc_duration[n]; } c->prev_ltc_time = now; c->ltc_count ++; hdspe->tco->ltc_changed = true; spin_unlock(&hdspe->tco->lock); } } /* Invoked at every audio interrupt */ void hdspe_tco_period_elapsed(struct hdspe* hdspe) { struct hdspe_tco* c = hdspe->tco; spin_lock(&hdspe->tco->lock); /* clock by which LTC frame start is measured. */ c->ltc_time = hdspe->frame_count; /* Incoming time code and offset are accurate only at this time of an * audio period interrupt, when audio interrupts are enabled. * Check for changes and notify here. */ if (c->ltc_changed) { /* time code changed */ s32 realfps1k; struct hdspe_ltc ltc; hdspe_tco_read_ltc(hdspe, <c, __func__); /* Add 1 frame, which is correct if running forward. * (The windows driver does that too.) */ ltc.tc = hdspe_ltc32_incr(ltc.tc, ltc.fps, ltc.df); c->ltc_in = ltc.tc; c->ltc_in_frame_count = ltc.fc; // if (hdspe->period_size >= 2048) // c->ltc_in_frame_count -= hdspe->period_size / 2; snd_ctl_notify(hdspe->card, SNDRV_CTL_EVENT_MASK_VALUE, hdspe->cid.ltc_in); c->ltc_changed = false; /* Estimate actual LTC input "pull factor", based on the * average duration in audio frames of the past LTC_CACHE_SIZE * incoming LTC frames. Pull factor 1000 = nominal speed, * 999 = NTSC pulldown. */ realfps1k = c->ltc_duration_sum == 0 ? ltc.fps*1000 : 1000000000 / (u32)div_u64(c->ltc_duration_sum, (LTC_CACHE_SIZE*1000)); c->ltc_in_pullfac = (realfps1k + ltc.fps/2) / ltc.fps; /* dev_dbg(hdspe->card->dev, "%s: realfps=%u/1000, setfps=%u, pull=%d\n", __func__, realfps1k, ltc.fps, c->ltc_in_pull); */ if (c->ltc_in_pullfac != c->last_ltc_in_pullfac) snd_ctl_notify(hdspe->card, SNDRV_CTL_EVENT_MASK_VALUE, hdspe->cid.ltc_in_pullfac); c->last_ltc_in_pullfac = c->ltc_in_pullfac; } spin_unlock(&hdspe->tco->lock); if (c->ltc_set) { /* Output time code set at the previous audio interrupt * is now picked up by the hardware. Reset the TCO1_set_TC * control bit and frame offset. */ spin_lock(&hdspe->tco->lock); hdspe_tco_reset_timecode(hdspe); spin_unlock(&hdspe->tco->lock); /* c->ltc_set is reset to false at this time. */ } if (c->ltc_out != 0xffffffff) { /* set timecode and start running LTC */ spin_lock(&hdspe->tco->lock); hdspe_tco_start_timecode(hdspe); spin_unlock(&hdspe->tco->lock); /* Output time code is picked up by the hardware at the next * audio period interrupt. * c->ltc_set is true at this point. * ltc_out is reset to 0xffffffff. */ } } #ifdef DEBUG_LTC static void hdspe_tco_timer(struct timer_list *t) { struct hdspe* hdspe = container_of(t, struct hdspe, tco_timer); struct hdspe_ltc ltc; hdspe_tco_read_ltc(hdspe, <c, __func__); mod_timer(&hdspe->tco_timer, jiffies+HZ/LTC_TIMER_FREQ); } #endif /*DEBUG_LTC*/ //////////////////////////////////////////////////////////////////////////// void snd_hdspe_proc_read_tco(struct snd_info_entry *entry, struct snd_info_buffer *buffer) { struct hdspe *hdspe = entry->private_data; struct hdspe_tco *c = hdspe->tco; struct hdspe_tco_status s; u32 ltc = hdspe_read_tco(hdspe, 0); u32 tco1 = hdspe_read_tco(hdspe, 1); u32 tco2 = hdspe_read_tco(hdspe, 2); u32 tco3 = hdspe_read_tco(hdspe, 3); if (!c) { snd_BUG(); return; } snd_iprintf(buffer, "TCO Status:\n\n"); hdspe_tco_read_status(hdspe, &s); snd_iprintf(buffer, "LTC : %02x:%02x:%02x%c%02x\n", (s.ltc_in>>24)&0x3f, (s.ltc_in>>16)&0x7f, (s.ltc_in>>8)&0x7f, s.ltc_in_drop ? '.' : ':', (s.ltc_in&0x3f)); snd_iprintf(buffer, "TCO Lock : %d %s\n", s.tco_lock, HDSPE_BOOL_NAME(s.tco_lock)); snd_iprintf(buffer, "LTC Valid : %d %s\n", s.ltc_valid, HDSPE_BOOL_NAME(s.ltc_valid)); snd_iprintf(buffer, "LTC In Frame Rate : %d %s\n", s.ltc_in_fps, HDSPE_LTC_FRAME_RATE_NAME(s.ltc_in_fps)); snd_iprintf(buffer, "LTC In Drop Frame : %d %s\n", s.ltc_in_drop, HDSPE_BOOL_NAME(s.ltc_in_drop)); snd_iprintf(buffer, "Video Input : %d %s\n", s.video, HDSPE_VIDEO_FORMAT_NAME(s.video)); snd_iprintf(buffer, "WordClk Valid : %d %s\n", s.wck_valid, HDSPE_BOOL_NAME(s.wck_valid)); snd_iprintf(buffer, "WordClk Speed : %d %s\n", s.wck_speed, HDSPE_SPEED_NAME(s.wck_speed)); snd_iprintf(buffer, "\n"); snd_iprintf(buffer, "LTC\t: 0x%08x\n", ltc); IPRINTREG(buffer, "TCO1", tco1, tco1_bitNames); snd_iprintf(buffer, "\nTCO Control:\n\n"); snd_iprintf(buffer, "Sync Source : %d %s\n", c->input, HDSPE_TCO_SOURCE_NAME(c->input)); snd_iprintf(buffer, "LTC Frame Rate : %d %s\n", c->ltc_fps, HDSPE_LTC_FRAME_RATE_NAME(c->ltc_fps)); snd_iprintf(buffer, "LTC Drop Frame : %d %s\n", c->ltc_drop, HDSPE_BOOL_NAME(c->ltc_drop)); snd_iprintf(buffer, "LTC Sample Rate : %d %s\n", c->sample_rate, HDSPE_TCO_SAMPLE_RATE_NAME(c->sample_rate)); snd_iprintf(buffer, "WordClk Conversion: %d %s\n", c->wck_conversion, HDSPE_WCK_CONVERSION_NAME(c->wck_conversion)); snd_iprintf(buffer, "Pull Up / Down : %d %s\n", c->pull, HDSPE_PULL_NAME(c->pull)); snd_iprintf(buffer, "75 Ohm Termination: %d %s\n", c->term, HDSPE_BOOL_NAME(c->term)); snd_iprintf(buffer, "\n"); snd_iprintf(buffer, "LTC Out : 0x%08x %02x:%02x:%02x%c%02x\n", c->ltc_out, (c->ltc_out>>24) & 0x3f, (c->ltc_out>>16) & 0x7f, (c->ltc_out>> 8) & 0x7f, (c->ltc_drop) ? '.' : ':', (c->ltc_out ) & 0x3f); snd_iprintf(buffer, "LTC Run : %d %s\n", c->ltc_run, HDSPE_BOOL_NAME(c->ltc_run)); snd_iprintf(buffer, "LTC Flywheel : %d %s\n", c->ltc_flywheel, HDSPE_BOOL_NAME(c->ltc_flywheel)); snd_iprintf(buffer, "LTC Set : %d %s\n", c->ltc_set, HDSPE_BOOL_NAME(c->ltc_set)); snd_iprintf(buffer, "TCO FW version : %d\n", (tco3 >> 24) & 0x7f); snd_iprintf(buffer, "TCO WCK period : %d/%d\n", ((tco2 & 0x7f00)>>1) | (tco2&0x7f), 25000000); snd_iprintf(buffer, "Video Input FPS : %d %s\n", (tco2 >> 27) & 0x0f, ""); } //////////////////////////////////////////////////////////////////////// static int hdspe_tco_get_status(struct hdspe* hdspe, int (*getter)(struct hdspe_tco_status*), const char* propname) { struct hdspe_tco_status status; int val; hdspe_tco_read_status1(hdspe, &status); val = getter(&status); dev_dbg(hdspe->card->dev, "%s(%s) = %d.\n", __func__, propname, val); return val; } static int hdspe_tco_get_status2(struct hdspe* hdspe, int (*getter)(struct hdspe_tco_status*), const char* propname) { struct hdspe_tco_status status; int val; hdspe_tco_read_status2(hdspe, &status); val = getter(&status); dev_dbg(hdspe->card->dev, "%s(%s) = %d.\n", __func__, propname, val); return val; } static int hdspe_tco_put_control(struct hdspe* hdspe, int val, int maxrange, int (*putter)(struct hdspe_tco*, int val), const char* propname) { int changed; dev_dbg(hdspe->card->dev, "%s(%s,%d) ...\n", __func__, propname, val); if (val < 0 || val >= maxrange) { dev_warn(hdspe->card->dev, "%s value %d out of range 0..%d\n", propname, val, maxrange-1); return -EINVAL; } spin_lock_irq(&hdspe->tco->lock); changed = putter(hdspe->tco, val); if (changed) hdspe_tco_write_settings(hdspe); spin_unlock_irq(&hdspe->tco->lock); dev_dbg(hdspe->card->dev, "... changed=%d.\n", changed); return changed; } #define HDSPE_TCO_STATUS_GET_WITHOUT_GETTER(prop, item, field) \ static int snd_hdspe_get_##prop(struct snd_kcontrol *kcontrol, \ struct snd_ctl_elem_value *ucontrol) \ { \ struct hdspe* hdspe = snd_kcontrol_chip(kcontrol); \ ucontrol->value.item[0] = hdspe_tco_get_status( \ hdspe, hdspe_tco_get_status_##field, #prop); \ return 0; \ } #define HDSPE_TCO_STATUS_GET(prop, item, field) \ static int hdspe_tco_get_status_##field(struct hdspe_tco_status* s) \ { \ return s->field; \ } \ HDSPE_TCO_STATUS_GET_WITHOUT_GETTER(prop, item, field) #define HDSPE_TCO_STATUS_ENUM_METHODS(prop, field) \ HDSPE_TCO_STATUS_GET(prop, enumerated.item, field) #define HDSPE_TCO_STATUS_INT_METHODS(prop, field) \ HDSPE_TCO_STATUS_GET(prop, integer.value, field) #define HDSPE_TCO_STATUS2_GET_WITHOUT_GETTER(prop, item, field) \ static int snd_hdspe_get_##prop(struct snd_kcontrol *kcontrol, \ struct snd_ctl_elem_value *ucontrol) \ { \ struct hdspe* hdspe = snd_kcontrol_chip(kcontrol); \ ucontrol->value.item[0] = hdspe_tco_get_status2( \ hdspe, hdspe_tco_get_status_##field, #prop); \ return 0; \ } #define HDSPE_TCO_STATUS2_GET(prop, item, field) \ static int hdspe_tco_get_status_##field(struct hdspe_tco_status* s) \ { \ return s->field; \ } \ HDSPE_TCO_STATUS2_GET_WITHOUT_GETTER(prop, item, field) #define HDSPE_TCO_STATUS2_ENUM_METHODS(prop, field) \ HDSPE_TCO_STATUS2_GET(prop, enumerated.item, field) #define HDSPE_TCO_STATUS2_INT_METHODS(prop, field) \ HDSPE_TCO_STATUS2_GET(prop, integer.value, field) #define HDSPE_TCO_CONTROL_GET_WITHOUT_GETTER(prop, item, field) \ static int snd_hdspe_get_##prop(struct snd_kcontrol *kcontrol, \ struct snd_ctl_elem_value *ucontrol) \ { \ struct hdspe* hdspe = snd_kcontrol_chip(kcontrol); \ int val = ucontrol->value.item[0] = \ hdspe_tco_get_control_##field(hdspe->tco); \ dev_dbg(hdspe->card->dev, "%s = %d.\n", __func__, val); \ return 0; \ } #define HDSPE_TCO_CONTROL_GET(prop, item, field) \ static int hdspe_tco_get_control_##field(struct hdspe_tco* c) \ { \ return c->field; \ } \ HDSPE_TCO_CONTROL_GET_WITHOUT_GETTER(prop, item, field) #define HDSPE_TCO_CONTROL_PUT_WITHOUT_PUTTER(prop, item, field, maxrange)\ static int snd_hdspe_put_##prop(struct snd_kcontrol *kcontrol, \ struct snd_ctl_elem_value *ucontrol) \ { \ struct hdspe* hdspe = snd_kcontrol_chip(kcontrol); \ int val = ucontrol->value.item[0]; \ return hdspe_tco_put_control(hdspe, val, maxrange, \ hdspe_tco_put_control_##field, #prop); \ } #define HDSPE_TCO_CONTROL_PUT(prop, item, field, maxrange) \ static int hdspe_tco_put_control_##field(struct hdspe_tco* c, int val) \ { \ int oldval = c->field; \ c->field = val; \ return val != oldval; \ } \ HDSPE_TCO_CONTROL_PUT_WITHOUT_PUTTER(prop, item, field, maxrange) #define HDSPE_TCO_CONTROL_ENUM_METHODS(prop, field, maxrange) \ HDSPE_TCO_CONTROL_GET(prop, enumerated.item, field) \ HDSPE_TCO_CONTROL_PUT(prop, enumerated.item, field, maxrange) /* ------------------------------------------------------------------- */ static int snd_hdspe_info_ltc_in_fps(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { static const char *const texts[] = { "24 fps", "25 fps", "29.97 fps", "30 fps" }; ENUMERATED_CTL_INFO(uinfo, texts); return 0; } HDSPE_TCO_STATUS_ENUM_METHODS(ltc_in_fps, ltc_in_fps) HDSPE_TCO_STATUS_ENUM_METHODS(ltc_in_drop, ltc_in_drop) HDSPE_TCO_STATUS_ENUM_METHODS(ltc_valid, ltc_valid) static int snd_hdspe_info_video(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { static const char *const texts[HDSPE_VIDEO_FORMAT_COUNT] = { HDSPE_VIDEO_FORMAT_NAME(0), HDSPE_VIDEO_FORMAT_NAME(1), HDSPE_VIDEO_FORMAT_NAME(2) }; ENUMERATED_CTL_INFO(uinfo, texts); return 0; } HDSPE_TCO_STATUS_ENUM_METHODS(video, video) static int snd_hdspe_info_video_in_fps(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { static const char *const texts[HDSPE_VIDEO_FPS_COUNT] = { HDSPE_VIDEO_FPS_NAME(0), HDSPE_VIDEO_FPS_NAME(1), HDSPE_VIDEO_FPS_NAME(2), HDSPE_VIDEO_FPS_NAME(3), HDSPE_VIDEO_FPS_NAME(4), HDSPE_VIDEO_FPS_NAME(5), HDSPE_VIDEO_FPS_NAME(6), HDSPE_VIDEO_FPS_NAME(7), HDSPE_VIDEO_FPS_NAME(8), HDSPE_VIDEO_FPS_NAME(9), HDSPE_VIDEO_FPS_NAME(10) }; ENUMERATED_CTL_INFO(uinfo, texts); return 0; } HDSPE_TCO_STATUS2_ENUM_METHODS(video_in_fps, video_in_fps) HDSPE_TCO_STATUS_ENUM_METHODS(wck_valid, wck_valid) static int snd_hdspe_info_wck_speed(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { static const char *const texts[HDSPE_SPEED_COUNT] = { HDSPE_SPEED_NAME(0), HDSPE_SPEED_NAME(1), HDSPE_SPEED_NAME(2) }; ENUMERATED_CTL_INFO(uinfo, texts); return 0; } HDSPE_TCO_STATUS_ENUM_METHODS(wck_speed, wck_speed) HDSPE_TCO_STATUS_ENUM_METHODS(tco_lock, tco_lock) static int snd_hdspe_info_ltc_in_pullfac(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; uinfo->count = 1; return 0; } static int snd_hdspe_get_ltc_in_pullfac(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct hdspe *hdspe = snd_kcontrol_chip(kcontrol); ucontrol->value.integer.value[0] = hdspe->tco->ltc_in_pullfac; return 0; } HDSPE_TCO_CONTROL_ENUM_METHODS(word_term, term, 2) static int snd_hdspe_info_sample_rate(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { static const char *const texts[HDSPE_TCO_SAMPLE_RATE_COUNT] = { HDSPE_TCO_SAMPLE_RATE_NAME(0), HDSPE_TCO_SAMPLE_RATE_NAME(1), HDSPE_TCO_SAMPLE_RATE_NAME(2) }; ENUMERATED_CTL_INFO(uinfo, texts); return 0; } HDSPE_TCO_CONTROL_ENUM_METHODS(sample_rate, sample_rate, HDSPE_TCO_SAMPLE_RATE_COUNT) static int snd_hdspe_info_pull(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { static const char *const texts[HDSPE_PULL_COUNT] = { HDSPE_PULL_NAME(0), HDSPE_PULL_NAME(1), HDSPE_PULL_NAME(2), HDSPE_PULL_NAME(3), HDSPE_PULL_NAME(4) }; ENUMERATED_CTL_INFO(uinfo, texts); return 0; } HDSPE_TCO_CONTROL_ENUM_METHODS(pull, pull, HDSPE_PULL_COUNT) static int snd_hdspe_info_wck_conversion(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { static const char *const texts[HDSPE_WCK_CONVERSION_COUNT] = { HDSPE_WCK_CONVERSION_NAME(0), HDSPE_WCK_CONVERSION_NAME(1), HDSPE_WCK_CONVERSION_NAME(2) }; ENUMERATED_CTL_INFO(uinfo, texts); return 0; } HDSPE_TCO_CONTROL_ENUM_METHODS(wck_conversion, wck_conversion, HDSPE_WCK_CONVERSION_COUNT) static int snd_hdspe_info_wck_out_speed(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { static const char *const texts[HDSPE_SPEED_COUNT] = { HDSPE_SPEED_NAME(0), HDSPE_SPEED_NAME(1), HDSPE_SPEED_NAME(2) }; ENUMERATED_CTL_INFO(uinfo, texts); return 0; } HDSPE_TCO_CONTROL_ENUM_METHODS(wck_out_speed, wck_out_speed, HDSPE_SPEED_COUNT); static int snd_hdspe_info_frame_rate(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { static const char *const texts[] = { "24 fps", "25 fps", "29.97 fps", "29.97 dfps", "30 fps", "30 dfps" }; ENUMERATED_CTL_INFO(uinfo, texts); return 0; } static int hdspe_tco_get_control_frame_rate(struct hdspe_tco* c) { static int fr[8] = { 0, 1, 2, 4, 0, 1, 3, 5 }; return fr[(c->ltc_drop!=0)*4 + c->ltc_fps%4]; } static int hdspe_tco_put_control_frame_rate(struct hdspe_tco* c, int val) { static int fps[6] = { 0, 1, 2, 2, 3, 3 }; static int df[6] = { 0, 0, 0, 1, 0, 1 }; int rc = 0; if (c->ltc_fps != fps[val]) { c->ltc_fps = fps[val]; rc = 1; } if (c->ltc_drop != df[val]) { c->ltc_drop = df[val]; rc = 1; } return rc; } HDSPE_TCO_CONTROL_GET_WITHOUT_GETTER(frame_rate, enumerated.item, frame_rate) HDSPE_TCO_CONTROL_PUT_WITHOUT_PUTTER(frame_rate, enumerated.item, frame_rate, 6) static int snd_hdspe_info_sync_source(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { static const char *const texts[HDSPE_TCO_SOURCE_COUNT] = { HDSPE_TCO_SOURCE_NAME(0), HDSPE_TCO_SOURCE_NAME(1), HDSPE_TCO_SOURCE_NAME(2) }; ENUMERATED_CTL_INFO(uinfo, texts); return 0; } HDSPE_TCO_CONTROL_ENUM_METHODS(sync_source, input, HDSPE_TCO_SOURCE_COUNT) #ifdef NEVER HDSPE_TCO_CONTROL_ENUM_METHODS(ltc_jam_sync, ltc_jam, 2) HDSPE_TCO_CONTROL_ENUM_METHODS(ltc_flywheel, ltc_flywheel, 2) #endif /*NEVER*/ HDSPE_TCO_CONTROL_ENUM_METHODS(ltc_run, ltc_run, 2) static int snd_hdspe_info_ltc_in(struct snd_kcontrol* kcontrol, struct snd_ctl_elem_info *uinfo) { uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER64; uinfo->count = 2; return 0; } static int snd_hdspe_get_ltc_in(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct hdspe *hdspe = snd_kcontrol_chip(kcontrol); u32 ltc = hdspe->tco->ltc_in; u64 tc; spin_lock_irq(&hdspe->tco->lock); // dev_dbg(hdspe->card->dev, "%s ...\n", __func__); /* The TCO module reports no user bits. They will be 0. */ tc = ((u64)(ltc&0xf0000000) << 28) | ((u64)(ltc&0x0f000000) << 24) | ((u64)(ltc&0x00f00000) << 20) | ((u64)(ltc&0x000f0000) << 16) | ((u64)(ltc&0x0000f000) << 12) | ((u64)(ltc&0x00000f00) << 8) | ((u64)(ltc&0x000000f0) << 4) | ((u64)(ltc&0x0000000f) << 0); ucontrol->value.integer64.value[0] = tc; ucontrol->value.integer64.value[1] = hdspe->tco->ltc_in_frame_count; spin_unlock_irq(&hdspe->tco->lock); return 0; } static int snd_hdspe_info_ltc_time(struct snd_kcontrol* kcontrol, struct snd_ctl_elem_info *uinfo) { uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER64; uinfo->count = 1; return 0; } static int snd_hdspe_get_ltc_time(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct hdspe *hdspe = snd_kcontrol_chip(kcontrol); spin_lock_irq(&hdspe->tco->lock); ucontrol->value.integer64.value[0] = hdspe->tco->ltc_time; spin_unlock_irq(&hdspe->tco->lock); return 0; } static int snd_hdspe_info_ltc_out(struct snd_kcontrol* kcontrol, struct snd_ctl_elem_info *uinfo) { uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER64; uinfo->count = 2; return 0; } static int snd_hdspe_put_ltc_out(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct hdspe* hdspe = snd_kcontrol_chip(kcontrol); u64 tc = ucontrol->value.integer64.value[0]; spin_lock_irq(&hdspe->tco->lock); /* Discard the user bits. The TCO module does not handle them. */ hdspe->tco->ltc_out = ((tc >> 28) & 0xf0000000) | ((tc >> 24) & 0x0f000000) | ((tc >> 20) & 0x00f00000) | ((tc >> 16) & 0x000f0000) | ((tc >> 12) & 0x0000f000) | ((tc >> 8) & 0x00000f00) | ((tc >> 4) & 0x000000f0) | ((tc >> 0) & 0x0000000f); hdspe->tco->ltc_out_frame_count = ucontrol->value.integer64.value[1]; spin_unlock_irq(&hdspe->tco->lock); return 0; /* do not notify */ } #ifdef NEVER static int snd_hdspe_info_wck_out_rate(struct snd_kcontrol* kcontrol, struct snd_ctl_elem_info *uinfo) { uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; uinfo->count = 2; return 0; } static int snd_hdspe_get_wck_out_rate(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct hdspe *hdspe = snd_kcontrol_chip(kcontrol); struct hdspe_tco_status s; hdspe_tco_read_status2(hdspe, &s); ucontrol->value.integer.value[0] = 25000000 * 16; /* 25 MHz * 16*/ ucontrol->value.integer.value[1] = s.fs_period_counter; dev_dbg(hdspe->card->dev, "%s = %d %d\n", __func__, (int)ucontrol->value.integer.value[0], (int)ucontrol->value.integer.value[1]); return 0; } #endif /*NEVER*/ static int snd_hdspe_info_fw_version(struct snd_kcontrol* kcontrol, struct snd_ctl_elem_info *uinfo) { uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; uinfo->count = 1; return 0; } static int snd_hdspe_get_fw_version(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct hdspe *hdspe = snd_kcontrol_chip(kcontrol); ucontrol->value.integer.value[0] = hdspe->tco->fw_version; return 0; } /* Control elements for the optional TCO module */ static const struct snd_kcontrol_new snd_hdspe_controls_tco[] = { HDSPE_RO_KCTL(CARD, "TCO Firmware", fw_version), HDSPE_RW_KCTL(CARD, "LTC Sample Rate", sample_rate), HDSPE_RW_KCTL(CARD, "TCO Pull", pull), HDSPE_RW_KCTL(CARD, "TCO WordClk Conversion", wck_conversion), HDSPE_RW_KCTL(CARD, "LTC Frame Rate", frame_rate), HDSPE_RW_KCTL(CARD, "TCO Sync Source", sync_source), HDSPE_RW_BOOL_KCTL(CARD, "TCO WordClk Term", word_term), HDSPE_WO_KCTL(CARD, "LTC Out", ltc_out), HDSPE_RV_KCTL(CARD, "LTC Time", ltc_time), HDSPE_RW_KCTL(CARD, "TCO WordClk Out Speed", wck_out_speed) }; #define CHECK_STATUS_CHANGE(prop) \ if (n.prop != o.prop) { \ dev_dbg(hdspe->card->dev, "%s changed %d -> %d\n", \ #prop, o.prop, n.prop); \ HDSPE_CTL_NOTIFY(prop); \ changed = true; \ } \ bool hdspe_tco_notify_status_change(struct hdspe* hdspe) { bool changed = false; struct hdspe_tco_status o = hdspe->tco->last_status; struct hdspe_tco_status n; hdspe_tco_read_status1(hdspe, &n); CHECK_STATUS_CHANGE(ltc_valid); CHECK_STATUS_CHANGE(ltc_in_fps); CHECK_STATUS_CHANGE(ltc_in_drop); CHECK_STATUS_CHANGE(video); CHECK_STATUS_CHANGE(video_in_fps); CHECK_STATUS_CHANGE(wck_valid); CHECK_STATUS_CHANGE(wck_speed); CHECK_STATUS_CHANGE(tco_lock); hdspe->tco->last_status = n; return changed; } int hdspe_create_tco_controls(struct hdspe* hdspe) { if (!hdspe->tco) return 0; HDSPE_ADD_RV_CONTROL_ID(CARD, "LTC In", ltc_in); HDSPE_ADD_RV_BOOL_CONTROL_ID(CARD, "LTC In Valid", ltc_valid); HDSPE_ADD_RV_CONTROL_ID(CARD, "LTC In Frame Rate", ltc_in_fps); HDSPE_ADD_RV_BOOL_CONTROL_ID(CARD, "LTC In Drop Frame", ltc_in_drop); HDSPE_ADD_RV_CONTROL_ID(CARD, "LTC In Pull Factor", ltc_in_pullfac); HDSPE_ADD_RV_CONTROL_ID(CARD, "TCO Video Format", video); HDSPE_ADD_RV_CONTROL_ID(CARD, "TCO Video Frame Rate", video_in_fps); HDSPE_ADD_RV_BOOL_CONTROL_ID(CARD, "TCO WordClk Valid", wck_valid); HDSPE_ADD_RV_CONTROL_ID(CARD, "TCO WordClk Speed", wck_speed); HDSPE_ADD_RV_BOOL_CONTROL_ID(CARD, "TCO Lock", tco_lock); #ifdef NEVER HDSPE_ADD_RV_CONTROL_ID(CARD, "TCO WordClk Out Rate", wck_out_rate); #endif /*NEVER*/ HDSPE_ADD_RW_BOOL_CONTROL_ID(CARD, "LTC Run", ltc_run); return hdspe_add_controls( hdspe, ARRAY_SIZE(snd_hdspe_controls_tco), snd_hdspe_controls_tco); } ////////////////////////////////////////////////////////////////////////// /* Return whether the optional TCO module is present or not. */ static bool hdspe_tco_detect(struct hdspe* hdspe) { switch (hdspe->io_type) { case HDSPE_MADI: case HDSPE_AES: // (AES and MADI have the same tco_detect bit) return hdspe_read_status0(hdspe).madi.tco_detect; case HDSPE_RAYDAT: case HDSPE_AIO: case HDSPE_AIO_PRO: return hdspe_read_status2(hdspe).raio.tco_detect; default: return false; }; } int hdspe_init_tco(struct hdspe* hdspe) { hdspe->tco = NULL; if (!hdspe_tco_detect(hdspe)) goto bailout; hdspe->tco = kzalloc(sizeof(*hdspe->tco), GFP_KERNEL); if (!hdspe->tco) goto bailout; spin_lock_init(&hdspe->tco->lock); hdspe->midiPorts++; /* hdspe->tco->ltc_out = 0xffffffff; would not set LTC output */ hdspe_tco_write_settings(hdspe); hdspe->tco->fw_version = (hdspe_read_tco(hdspe, 3) >> 24) & 0x7f; dev_info(hdspe->card->dev, "TCO module found. Firmware version %d.\n", hdspe->tco->fw_version); #ifdef DEBUG_LTC timer_setup(&hdspe->tco_timer, hdspe_tco_timer, 0); mod_timer(&hdspe->tco_timer, jiffies+HZ/LTC_TIMER_FREQ); #endif /*DEBUG_LTC*/ bailout: return 0; } void hdspe_terminate_tco(struct hdspe* hdspe) { if (!hdspe->tco) return; #ifdef DEBUG_LTC del_timer_sync(&hdspe->tco_timer); #endif /*DEBUG_LTC*/ hdspe_tco_stop_timecode(hdspe); hdspe_tco_reset_timecode(hdspe); kfree(hdspe->tco); }