diff --git a/seeed-voicecard/.github/ISSUE_TEMPLATE/BUG_ISSUE.yml b/seeed-voicecard/.github/ISSUE_TEMPLATE/BUG_ISSUE.yml new file mode 100644 index 0000000..bec6291 --- /dev/null +++ b/seeed-voicecard/.github/ISSUE_TEMPLATE/BUG_ISSUE.yml @@ -0,0 +1,53 @@ +name: Bug Report +description: File a bug report +title: "[Bug]: " +labels: [bug, triage] +assignees: + - Pillar1989 +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! + + - type: textarea + id: what-happened + attributes: + label: Describe the bug + description: A clear and concise description of what the bug is. + value: | + **To Reproduce** + Steps to reproduce the behavior: + 1. Go to '...' + 2. Click on '....' + 3. Scroll down to '....' + 4. See error + validations: + required: true + + - type: textarea + id: what-expected + attributes: + label: Expected behavior + description: A clear and concise description of what you expected to happen. + validations: + required: true + + - type: textarea + id: platform + attributes: + label: Platform + description: What platform are you running the code on. + value: | + - Device: [e.g. Raspberry Pi 4] + - OS: [e.g. Raspbian OS 32bit kernel version ...] + - Version/commit number [e.g. d1816f5] + validations: + required: true + + - type: textarea + id: logs + attributes: + label: Relevant log output + description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. + render: shell diff --git a/seeed-voicecard/.github/ISSUE_TEMPLATE/config.yml b/seeed-voicecard/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..bc35e9c --- /dev/null +++ b/seeed-voicecard/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Seeed Tech Support Forum + url: https://forum.seeedstudio.com/ + about: Please ask and answer general questions here. \ No newline at end of file diff --git a/seeed-voicecard/.github/workflows/stale.yml b/seeed-voicecard/.github/workflows/stale.yml new file mode 100644 index 0000000..c2e15cd --- /dev/null +++ b/seeed-voicecard/.github/workflows/stale.yml @@ -0,0 +1,23 @@ +name: Mark stale issues and pull requests + +on: + schedule: + - cron: '20 0 * * *' + +jobs: + stale: + + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + + steps: + - uses: actions/stale@v4.0.0 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: 'Stale issue message' + stale-pr-message: 'Stale pull request message' + stale-issue-label: 'no-issue-activity' + stale-pr-label: 'no-pr-activity' + days-before-stale: 15 diff --git a/seeed-voicecard/LICENSE b/seeed-voicecard/LICENSE new file mode 100644 index 0000000..9cecc1d --- /dev/null +++ b/seeed-voicecard/LICENSE @@ -0,0 +1,674 @@ + 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. + + {one line to give the program's name and a brief idea of what it does.} + Copyright (C) {year} {name of author} + + 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: + + {project} Copyright (C) {year} {fullname} + 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 +. diff --git a/seeed-voicecard/Makefile b/seeed-voicecard/Makefile new file mode 100644 index 0000000..b9de7f4 --- /dev/null +++ b/seeed-voicecard/Makefile @@ -0,0 +1,52 @@ +# +# Peter Yang +# Copyright (c) 2019 Seeed Studio +# +# MIT License +# + +uname_r=$(shell uname -r) + +# If KERNELRELEASE is defined, we've been invoked from the +# kernel build system and can use its language +ifneq ($(KERNELRELEASE),) +# $(warning KERNELVERSION=$(KERNELVERSION)) + +snd-soc-wm8960-objs := wm8960.o +snd-soc-ac108-objs := ac108.o ac101.o +snd-soc-seeed-voicecard-objs := seeed-voicecard.o + + +obj-m += snd-soc-wm8960.o +obj-m += snd-soc-ac108.o +obj-m += snd-soc-seeed-voicecard.o + +ifdef DEBUG +ifneq ($(DEBUG),0) + ccflags-y += -DDEBUG -DAC101_DEBG +endif +endif + + + +else + +DEST := /lib/modules/$(uname_r)/kernel + +all: + make -C /lib/modules/$(uname_r)/build M=$(PWD) modules + +clean: + make -C /lib/modules/$(uname_r)/build M=$(PWD) clean + +install: + sudo cp snd-soc-ac108.ko ${DEST}/sound/soc/codecs/ + sudo cp snd-soc-wm8960.ko ${DEST}/sound/soc/codecs/ + sudo cp snd-soc-seeed-voicecard.ko ${DEST}/sound/soc/bcm/ + sudo depmod -a + + +.PHONY: all clean install + +endif + diff --git a/seeed-voicecard/README.md b/seeed-voicecard/README.md new file mode 100644 index 0000000..d6f5d0d --- /dev/null +++ b/seeed-voicecard/README.md @@ -0,0 +1,67 @@ +# seeed-voicecard + +The drivers for [ReSpeaker Mic Hat](https://www.seeedstudio.com/ReSpeaker-2-Mics-Pi-HAT-p-2874.html), [ReSpeaker 4 Mic Array](https://www.seeedstudio.com/ReSpeaker-4-Mic-Array-for-Raspberry-Pi-p-2941.html), [6-Mics Circular Array Kit](), and [4-Mics Linear Array Kit]() for Raspberry Pi. + +### Install seeed-voicecard +Get the seeed voice card source code and install all linux kernel drivers +```bash +git clone https://github.com/respeaker/seeed-voicecard +cd seeed-voicecard +sudo ./install.sh +sudo reboot +``` + +**Note** If you have 64-bit version of Rasperry Pi OS, use install_arm64.sh script for driver installation. + +## ReSpeaker Documentation + +Up to date documentation for reSpeaker products can be found in [Seeed Studio Wiki](https://wiki.seeedstudio.com/ReSpeaker/)! +![](https://files.seeedstudio.com/wiki/ReSpeakerProductGuide/img/Raspberry_Pi_Mic_Array_Solutions.png) + + +### Coherence + +Estimate the magnitude squared coherence using Welch’s method. +![4-mics-linear-array-kit coherence](https://user-images.githubusercontent.com/3901856/37277486-beb1dd96-261f-11e8-898b-84405bfc7cea.png) +Note: 'CO 1-2' means the coherence between channel 1 and channel 2. + +```bash +# How to get the coherence of the captured audio(a.wav for example). +sudo apt install python-numpy python-scipy python-matplotlib +python tools/coherence.py a.wav + +# Requirement of the input audio file: +- format: WAV(Microsoft) signed 16-bit PCM +- channels: >=2 +``` + +### uninstall seeed-voicecard +If you want to upgrade the driver , you need uninstall the driver first. + +``` +pi@raspberrypi:~/seeed-voicecard $ sudo ./uninstall.sh +... +------------------------------------------------------ +Please reboot your raspberry pi to apply all settings +Thank you! +------------------------------------------------------ +``` + +Enjoy ! + +### Technical support + +For hardware testing purposes we made a Rasperry Pi OS 5.10.17-v7l+ 32-bit image with reSpeaker drivers pre-installed, which you can download by clicking on [this link](https://files.seeedstudio.com/linux/Raspberry%20Pi%204%20reSpeaker/2021-05-07-raspios-buster-armhf-lite-respeaker.img.xz). + +We provide official support for using reSpeaker with the following OS: +- 32-bit Raspberry Pi OS +- 64-bit Raspberry Pi OS (experimental support) + +And following hardware platforms: +- Raspberry Pi 3 (all models), Raspberry Pi 4 (all models), Raspberry Pi Zero and Zero W + +Anything beyond the scope of official support is considered to be community supported. Support for other OS/hardware platforms can be added, provided MOQ requirements can be met. + +If you have a technical problem when using reSpeaker with one of the officially supported platforms/OS, feel free to create an issue on Github. For general questions or suggestions, please use [Seeed forum](https://forum.seeedstudio.com/c/products/respeaker/15). + + diff --git a/seeed-voicecard/ac101.c b/seeed-voicecard/ac101.c new file mode 100644 index 0000000..23837a7 --- /dev/null +++ b/seeed-voicecard/ac101.c @@ -0,0 +1,1699 @@ +/* + * ac101.c + * + * (C) Copyright 2017-2018 + * Seeed Technology Co., Ltd. + * + * PeterYang + * + * (C) Copyright 2014-2017 + * Reuuimlla Technology Co., Ltd. + * + * huangxin + * liushaohua + * + * X-Powers AC101 codec driver + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + */ + +/* #undef AC101_DEBG + * use 'make DEBUG=1' to enable debugging + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ac101_regs.h" +#include "ac10x.h" + +/* + * *** To sync channels *** + * + * 1. disable clock in codec hw_params() + * 2. clear fifo in bcm2835 hw_params() + * 3. clear fifo in bcm2385 prepare() + * 4. enable RX in bcm2835 trigger() + * 5. enable clock in machine trigger() + */ + +/*Default initialize configuration*/ +static bool speaker_double_used = 1; +static int double_speaker_val = 0x1B; +static int single_speaker_val = 0x19; +static int headset_val = 0x3B; +static int mainmic_val = 0x4; +static int headsetmic_val = 0x4; +static bool dmic_used = 0; +static int adc_digital_val = 0xb0b0; +static bool drc_used = false; + +#define AC101_RATES (SNDRV_PCM_RATE_8000_96000 & \ + ~(SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_64000 | \ + SNDRV_PCM_RATE_88200)) +#define AC101_FORMATS (/*SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE |*/ \ + SNDRV_PCM_FMTBIT_S32_LE | \ + 0) + +static struct ac10x_priv* static_ac10x; + + +int ac101_read(struct snd_soc_codec *codec, unsigned reg) { + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + int r, v = 0; + + if ((r = regmap_read(ac10x->regmap101, reg, &v)) < 0) { + dev_err(codec->dev, "read reg %02X fail\n", + reg); + return r; + } + return v; +} + +int ac101_write(struct snd_soc_codec *codec, unsigned reg, unsigned val) { + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + int v; + + v = regmap_write(ac10x->regmap101, reg, val); + return v; +} + +int ac101_update_bits(struct snd_soc_codec *codec, unsigned reg, + unsigned mask, unsigned value +) { + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + int v; + + v = regmap_update_bits(ac10x->regmap101, reg, mask, value); + return v; +} + + + +#ifdef CONFIG_AC101_SWITCH_DETECT +/******************************************************************************/ +/********************************switch****************************************/ +/******************************************************************************/ +#define KEY_HEADSETHOOK 226 /* key define */ +#define HEADSET_FILTER_CNT (10) + +/* + * switch_hw_config:config the 53 codec register + */ +static void switch_hw_config(struct snd_soc_codec *codec) +{ + int r; + + AC101_DBG(); + + /*HMIC/MMIC BIAS voltage level select:2.5v*/ + ac101_update_bits(codec, OMIXER_BST1_CTRL, (0xf<state:%d\n", ac10x->state); + + input_report_switch(ac10x->inpdev, SW_HEADPHONE_INSERT, ac10x->state); + input_sync(ac10x->inpdev); + return; +} + +/* + * work_cb_clear_irq: clear audiocodec pending and Record the interrupt. + */ +static void work_cb_clear_irq(struct work_struct *work) +{ + int reg_val = 0; + struct ac10x_priv *ac10x = container_of(work, struct ac10x_priv, work_clear_irq); + struct snd_soc_codec *codec = ac10x->codec; + + ac10x->irq_cntr++; + + reg_val = ac101_read(codec, HMIC_STS); + if (BIT(HMIC_PULLOUT_PEND) & reg_val) { + ac10x->pullout_cntr++; + AC101_DBG("ac10x->pullout_cntr: %d\n", ac10x->pullout_cntr); + } + + reg_val |= HMIC_PEND_ALL; + ac101_write(codec, HMIC_STS, reg_val); + + reg_val = ac101_read(codec, HMIC_STS); + if ((reg_val & HMIC_PEND_ALL) != 0){ + reg_val |= HMIC_PEND_ALL; + ac101_write(codec, HMIC_STS, reg_val); + } + + if (cancel_work_sync(&ac10x->work_switch) != 0) { + ac10x->irq_cntr--; + } + + if (0 == schedule_work(&ac10x->work_switch)) { + ac10x->irq_cntr--; + AC101_DBG("[work_cb_clear_irq] add work struct failed!\n"); + } +} + +enum { + HBIAS_LEVEL_1 = 0x02, + HBIAS_LEVEL_2 = 0x0B, + HBIAS_LEVEL_3 = 0x13, + HBIAS_LEVEL_4 = 0x17, + HBIAS_LEVEL_5 = 0x19, +}; + +static int __ac101_get_hmic_data(struct snd_soc_codec *codec) { + #ifdef AC101_DEBG + static long counter; + #endif + int r, d; + + d = GET_HMIC_DATA(ac101_read(codec, HMIC_STS)); + + r = 0x1 << HMIC_DATA_PEND; + ac101_write(codec, HMIC_STS, r); + + /* prevent i2c accessing too frequently */ + usleep_range(1500, 3000); + + AC101_DBG("HMIC_DATA(%3ld): %02X\n", counter++, d); + return d; +} + +/* + * work_cb_earphone_switch: judge the status of the headphone + */ +static void work_cb_earphone_switch(struct work_struct *work) +{ + struct ac10x_priv *ac10x = container_of(work, struct ac10x_priv, work_switch); + struct snd_soc_codec *codec = ac10x->codec; + + static int hook_flag1 = 0, hook_flag2 = 0; + static int KEY_VOLUME_FLAG = 0; + + unsigned filter_buf = 0; + int filt_index = 0; + int t = 0; + + ac10x->irq_cntr--; + + /* read HMIC_DATA */ + t = __ac101_get_hmic_data(codec); + + if ((t >= HBIAS_LEVEL_2) && (ac10x->mode == FOUR_HEADPHONE_PLUGIN)) { + t = __ac101_get_hmic_data(codec); + + if (t >= HBIAS_LEVEL_5){ + msleep(150); + t = __ac101_get_hmic_data(codec); + if (((t < HBIAS_LEVEL_2 && t >= HBIAS_LEVEL_1 - 1) || t >= HBIAS_LEVEL_5) + && (ac10x->pullout_cntr == 0)) { + input_report_key(ac10x->inpdev, KEY_HEADSETHOOK, 1); + input_sync(ac10x->inpdev); + + AC101_DBG("KEY_HEADSETHOOK1\n"); + + if (hook_flag1 != hook_flag2) + hook_flag1 = hook_flag2 = 0; + hook_flag1++; + } + if (ac10x->pullout_cntr) + ac10x->pullout_cntr--; + } else if (t >= HBIAS_LEVEL_4) { + msleep(80); + t = __ac101_get_hmic_data(codec); + if (t < HBIAS_LEVEL_5 && t >= HBIAS_LEVEL_4 && (ac10x->pullout_cntr == 0)) { + KEY_VOLUME_FLAG = 1; + input_report_key(ac10x->inpdev, KEY_VOLUMEUP, 1); + input_sync(ac10x->inpdev); + input_report_key(ac10x->inpdev, KEY_VOLUMEUP, 0); + input_sync(ac10x->inpdev); + + AC101_DBG("HMIC_DATA: %d KEY_VOLUMEUP\n", t); + } + if (ac10x->pullout_cntr) + ac10x->pullout_cntr--; + } else if (t >= HBIAS_LEVEL_3){ + msleep(80); + t = __ac101_get_hmic_data(codec); + if (t < HBIAS_LEVEL_4 && t >= HBIAS_LEVEL_3 && (ac10x->pullout_cntr == 0)){ + KEY_VOLUME_FLAG = 1; + input_report_key(ac10x->inpdev, KEY_VOLUMEDOWN, 1); + input_sync(ac10x->inpdev); + input_report_key(ac10x->inpdev, KEY_VOLUMEDOWN, 0); + input_sync(ac10x->inpdev); + AC101_DBG("KEY_VOLUMEDOWN\n"); + } + if (ac10x->pullout_cntr) + ac10x->pullout_cntr--; + } + } else if ((t < HBIAS_LEVEL_2 && t >= HBIAS_LEVEL_1) && (ac10x->mode == FOUR_HEADPHONE_PLUGIN)) { + t = __ac101_get_hmic_data(codec); + if (t < HBIAS_LEVEL_2 && t >= HBIAS_LEVEL_1) { + if (KEY_VOLUME_FLAG) { + KEY_VOLUME_FLAG = 0; + } + if (hook_flag1 == (++hook_flag2)) { + hook_flag1 = hook_flag2 = 0; + input_report_key(ac10x->inpdev, KEY_HEADSETHOOK, 0); + input_sync(ac10x->inpdev); + + AC101_DBG("KEY_HEADSETHOOK0\n"); + } + } + } else { + while (ac10x->irq_cntr == 0 && ac10x->irq != 0) { + msleep(20); + + t = __ac101_get_hmic_data(codec); + + if (filt_index <= HEADSET_FILTER_CNT) { + if (filt_index++ == 0) { + filter_buf = t; + } else if (filter_buf != t) { + filt_index = 0; + } + continue; + } + + filt_index = 0; + if (filter_buf >= HBIAS_LEVEL_2) { + ac10x->mode = THREE_HEADPHONE_PLUGIN; + ac10x->state = 2; + } else if (filter_buf >= HBIAS_LEVEL_1 - 1) { + ac10x->mode = FOUR_HEADPHONE_PLUGIN; + ac10x->state = 1; + } else { + ac10x->mode = HEADPHONE_IDLE; + ac10x->state = 0; + } + switch_status_update(ac10x); + ac10x->pullout_cntr = 0; + break; + } + } +} + +/* + * audio_hmic_irq: the interrupt handlers + */ +static irqreturn_t audio_hmic_irq(int irq, void *para) +{ + struct ac10x_priv *ac10x = (struct ac10x_priv *)para; + if (ac10x == NULL) { + return -EINVAL; + } + + if (0 == schedule_work(&ac10x->work_clear_irq)){ + AC101_DBG("[audio_hmic_irq] work already in queue_codec_irq, adding failed!\n"); + } + return IRQ_HANDLED; +} + +static int ac101_switch_probe(struct ac10x_priv *ac10x) { + struct i2c_client *i2c = ac10x->i2c101; + long ret; + + ac10x->gpiod_irq = devm_gpiod_get_optional(&i2c->dev, "switch-irq", GPIOD_IN); + if (IS_ERR(ac10x->gpiod_irq)) { + ac10x->gpiod_irq = NULL; + dev_err(&i2c->dev, "failed get switch-irq in device tree\n"); + goto _err_irq; + } + + gpiod_direction_input(ac10x->gpiod_irq); + + ac10x->irq = gpiod_to_irq(ac10x->gpiod_irq); + if (IS_ERR_VALUE(ac10x->irq)) { + pr_warn("[ac101] map gpio to irq failed, errno = %ld\n", ac10x->irq); + ac10x->irq = 0; + goto _err_irq; + } + + /* request irq, set irq type to falling edge trigger */ + ret = devm_request_irq(ac10x->codec->dev, ac10x->irq, audio_hmic_irq, IRQF_TRIGGER_FALLING, "SWTICH_EINT", ac10x); + if (IS_ERR_VALUE(ret)) { + pr_warn("[ac101] request virq %ld failed, errno = %ld\n", ac10x->irq, ret); + goto _err_irq; + } + + ac10x->mode = HEADPHONE_IDLE; + ac10x->state = -1; + + /*use for judge the state of switch*/ + INIT_WORK(&ac10x->work_switch, work_cb_earphone_switch); + INIT_WORK(&ac10x->work_clear_irq, work_cb_clear_irq); + + /********************create input device************************/ + ac10x->inpdev = devm_input_allocate_device(ac10x->codec->dev); + if (!ac10x->inpdev) { + AC101_DBG("input_allocate_device: not enough memory for input device\n"); + ret = -ENOMEM; + goto _err_input_allocate_device; + } + + ac10x->inpdev->name = "seed-voicecard-headset"; + ac10x->inpdev->phys = dev_name(ac10x->codec->dev); + ac10x->inpdev->id.bustype = BUS_I2C; + ac10x->inpdev->dev.parent = ac10x->codec->dev; + input_set_drvdata(ac10x->inpdev, ac10x->codec); + + ac10x->inpdev->evbit[0] = BIT_MASK(EV_KEY) | BIT(EV_SW); + + set_bit(KEY_HEADSETHOOK, ac10x->inpdev->keybit); + set_bit(KEY_VOLUMEUP, ac10x->inpdev->keybit); + set_bit(KEY_VOLUMEDOWN, ac10x->inpdev->keybit); + input_set_capability(ac10x->inpdev, EV_SW, SW_HEADPHONE_INSERT); + + ret = input_register_device(ac10x->inpdev); + if (ret) { + AC101_DBG("input_register_device: input_register_device failed\n"); + goto _err_input_register_device; + } + + /* the first headset state checking */ + switch_hw_config(ac10x->codec); + ac10x->irq_cntr = 1; + schedule_work(&ac10x->work_switch); + + return 0; + +_err_input_register_device: +_err_input_allocate_device: + + if (ac10x->irq) { + devm_free_irq(&i2c->dev, ac10x->irq, ac10x); + ac10x->irq = 0; + } +_err_irq: + return ret; +} +/******************************************************************************/ +/********************************switch****************************************/ +/******************************************************************************/ +#endif + + + +void drc_config(struct snd_soc_codec *codec) +{ + int reg_val; + reg_val = ac101_read(codec, 0xa3); + reg_val &= ~(0x7ff<<0); + reg_val |= 1<<0; + ac101_write(codec, 0xa3, reg_val); + ac101_write(codec, 0xa4, 0x2baf); + + reg_val = ac101_read(codec, 0xa5); + reg_val &= ~(0x7ff<<0); + reg_val |= 1<<0; + ac101_write(codec, 0xa5, reg_val); + ac101_write(codec, 0xa6, 0x2baf); + + reg_val = ac101_read(codec, 0xa7); + reg_val &= ~(0x7ff<<0); + ac101_write(codec, 0xa7, reg_val); + ac101_write(codec, 0xa8, 0x44a); + + reg_val = ac101_read(codec, 0xa9); + reg_val &= ~(0x7ff<<0); + ac101_write(codec, 0xa9, reg_val); + ac101_write(codec, 0xaa, 0x1e06); + + reg_val = ac101_read(codec, 0xab); + reg_val &= ~(0x7ff<<0); + reg_val |= (0x352<<0); + ac101_write(codec, 0xab, reg_val); + ac101_write(codec, 0xac, 0x6910); + + reg_val = ac101_read(codec, 0xad); + reg_val &= ~(0x7ff<<0); + reg_val |= (0x77a<<0); + ac101_write(codec, 0xad, reg_val); + ac101_write(codec, 0xae, 0xaaaa); + + reg_val = ac101_read(codec, 0xaf); + reg_val &= ~(0x7ff<<0); + reg_val |= (0x2de<<0); + ac101_write(codec, 0xaf, reg_val); + ac101_write(codec, 0xb0, 0xc982); + + ac101_write(codec, 0x16, 0x9f9f); + +} + +void drc_enable(struct snd_soc_codec *codec,bool on) +{ + int reg_val; + if (on) { + ac101_write(codec, 0xb5, 0xA080); + reg_val = ac101_read(codec, MOD_CLK_ENA); + reg_val |= (0x1<<6); + ac101_write(codec, MOD_CLK_ENA, reg_val); + reg_val = ac101_read(codec, MOD_RST_CTRL); + reg_val |= (0x1<<6); + ac101_write(codec, MOD_RST_CTRL, reg_val); + + reg_val = ac101_read(codec, 0xa0); + reg_val |= (0x7<<0); + ac101_write(codec, 0xa0, reg_val); + } else { + ac101_write(codec, 0xb5, 0x0); + reg_val = ac101_read(codec, MOD_CLK_ENA); + reg_val &= ~(0x1<<6); + ac101_write(codec, MOD_CLK_ENA, reg_val); + reg_val = ac101_read(codec, MOD_RST_CTRL); + reg_val &= ~(0x1<<6); + ac101_write(codec, MOD_RST_CTRL, reg_val); + + reg_val = ac101_read(codec, 0xa0); + reg_val &= ~(0x7<<0); + ac101_write(codec, 0xa0, reg_val); + } +} + +void set_configuration(struct snd_soc_codec *codec) +{ + if (speaker_double_used) { + ac101_update_bits(codec, SPKOUT_CTRL, (0x1f<dac_mutex); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + AC101_DBG(); + if (ac10x->dac_enable == 0){ + /*enable dac module clk*/ + ac101_update_bits(codec, MOD_CLK_ENA, (0x1<dac_enable++; + break; + case SND_SOC_DAPM_POST_PMD: + if (ac10x->dac_enable != 0){ + ac10x->dac_enable = 0; + + ac101_update_bits(codec, DAC_DIG_CTRL, (0x1<dac_mutex); + return 0; +} + +static int ac101_headphone_event(struct snd_soc_codec* codec, int event) { + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /*open*/ + AC101_DBG("post:open\n"); + ac101_update_bits(codec, OMIXER_DACA_CTRL, (0xf<codec, SYSCLK_CTRL); + return (reg_val & (0x1<aif1_clken == 0){ + ret = ac101_update_bits(codec, SYSCLK_CTRL, (0x1<aif1_clken++; + } + } + break; + case SND_SOC_DAPM_POST_PMD: + if (ac10x->aif1_clken != 0) { + /* disable aif1clk & sysclk */ + ret = ac101_update_bits(codec, SYSCLK_CTRL, (0x1<aif1_clken = 0; + } + break; + } + } + + AC101_DBG("event=%d pre_up/%d post_down/%d\n", event, SND_SOC_DAPM_PRE_PMU, SND_SOC_DAPM_POST_PMD); + + return ret; +} + +/** + * snd_ac101_get_volsw - single mixer get callback + * @kcontrol: mixer control + * @ucontrol: control element information + * + * Callback to get the value of a single mixer control, or a double mixer + * control that spans 2 registers. + * + * Returns 0 for success. + */ +static int snd_ac101_get_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol +){ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int val, mask = (1 << fls(mc->max)) - 1; + unsigned int invert = mc->invert; + int ret; + + if ((ret = ac101_read(static_ac10x->codec, mc->reg)) < 0) + return ret; + + val = (ret >> mc->shift) & mask; + ucontrol->value.integer.value[0] = val - mc->min; + if (invert) { + ucontrol->value.integer.value[0] = + mc->max - ucontrol->value.integer.value[0]; + } + + if (snd_soc_volsw_is_stereo(mc)) { + val = (ret >> mc->rshift) & mask; + ucontrol->value.integer.value[1] = val - mc->min; + if (invert) { + ucontrol->value.integer.value[1] = + mc->max - ucontrol->value.integer.value[1]; + } + } + return 0; +} + +/** + * snd_ac101_put_volsw - single mixer put callback + * @kcontrol: mixer control + * @ucontrol: control element information + * + * Callback to set the value of a single mixer control, or a double mixer + * control that spans 2 registers. + * + * Returns 0 for success. + */ +static int snd_ac101_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol +){ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int sign_bit = mc->sign_bit; + unsigned int val, mask = (1 << fls(mc->max)) - 1; + unsigned int invert = mc->invert; + int ret; + + if (sign_bit) + mask = BIT(sign_bit + 1) - 1; + + val = ((ucontrol->value.integer.value[0] + mc->min) & mask); + if (invert) { + val = mc->max - val; + } + + ret = ac101_update_bits(static_ac10x->codec, mc->reg, mask << mc->shift, val << mc->shift); + + if (! snd_soc_volsw_is_stereo(mc)) { + return ret; + } + val = ((ucontrol->value.integer.value[1] + mc->min) & mask); + if (invert) { + val = mc->max - val; + } + + ret = ac101_update_bits(static_ac10x->codec, mc->reg, mask << mc->rshift, val << mc->rshift); + return ret; +} + + +static const DECLARE_TLV_DB_SCALE(dac_vol_tlv, -11925, 75, 0); +static const DECLARE_TLV_DB_SCALE(dac_mix_vol_tlv, -600, 600, 0); +static const DECLARE_TLV_DB_SCALE(dig_vol_tlv, -7308, 116, 0); +static const DECLARE_TLV_DB_SCALE(speaker_vol_tlv, -4800, 150, 0); +static const DECLARE_TLV_DB_SCALE(headphone_vol_tlv, -6300, 100, 0); + +static struct snd_kcontrol_new ac101_controls[] = { + /*DAC*/ + SOC_DOUBLE_TLV("DAC volume", DAC_VOL_CTRL, DAC_VOL_L, DAC_VOL_R, 0xff, 0, dac_vol_tlv), + SOC_DOUBLE_TLV("DAC mixer gain", DAC_MXR_GAIN, DACL_MXR_GAIN, DACR_MXR_GAIN, 0xf, 0, dac_mix_vol_tlv), + SOC_SINGLE_TLV("digital volume", DAC_DBG_CTRL, DVC, 0x3f, 1, dig_vol_tlv), + SOC_SINGLE_TLV("Speaker Playback Volume", SPKOUT_CTRL, SPK_VOL, 0x1f, 0, speaker_vol_tlv), + SOC_SINGLE_TLV("Headphone Playback Volume", HPOUT_CTRL, HP_VOL, 0x3f, 0, headphone_vol_tlv), +}; + +/* PLL divisors */ +struct pll_div { + unsigned int pll_in; + unsigned int pll_out; + int m; + int n_i; + int n_f; +}; + +struct aif1_fs { + unsigned samp_rate; + int bclk_div; + int srbit; + #define _SERIES_24_576K 0 + #define _SERIES_22_579K 1 + int series; +}; + +struct kv_map { + int val; + int bit; +}; + +/* + * Note : pll code from original tdm/i2s driver. + * freq_out = freq_in * N/(M*(2k+1)) , k=1,N=N_i+N_f,N_f=factor*0.2; + * N_i[0,1023], N_f_factor[0,7], m[1,64]=REG_VAL[1-63,0] + */ +static const struct pll_div codec_pll_div[] = { + {128000, _FREQ_22_579K, 1, 529, 1}, + {192000, _FREQ_22_579K, 1, 352, 4}, + {256000, _FREQ_22_579K, 1, 264, 3}, + {384000, _FREQ_22_579K, 1, 176, 2}, /*((176+2*0.2)*6000000)/(38*(2*1+1))*/ + {1411200, _FREQ_22_579K, 1, 48, 0}, + {2822400, _FREQ_22_579K, 1, 24, 0}, /* accurate, 11025 * 256 */ + {5644800, _FREQ_22_579K, 1, 12, 0}, /* accurate, 22050 * 256 */ + {6000000, _FREQ_22_579K, 38, 429, 0}, /*((429+0*0.2)*6000000)/(38*(2*1+1))*/ + {11289600, _FREQ_22_579K, 1, 6, 0}, /* accurate, 44100 * 256 */ + {13000000, _FREQ_22_579K, 19, 99, 0}, + {19200000, _FREQ_22_579K, 25, 88, 1}, + {24000000, _FREQ_22_579K, 63, 177, 4}, /* 22577778 Hz */ + + {128000, _FREQ_24_576K, 1, 576, 0}, + {192000, _FREQ_24_576K, 1, 384, 0}, + {256000, _FREQ_24_576K, 1, 288, 0}, + {384000, _FREQ_24_576K, 1, 192, 0}, + {2048000, _FREQ_24_576K, 1, 36, 0}, /* accurate, 8000 * 256 */ + {3072000, _FREQ_24_576K, 1, 24, 0}, /* accurate, 12000 * 256 */ + {4096000, _FREQ_24_576K, 1, 18, 0}, /* accurate, 16000 * 256 */ + {6000000, _FREQ_24_576K, 25, 307, 1}, + {6144000, _FREQ_24_576K, 4, 48, 0}, /* accurate, 24000 * 256 */ + {12288000, _FREQ_24_576K, 8, 48, 0}, /* accurate, 48000 * 256 */ + {13000000, _FREQ_24_576K, 42, 238, 1}, + {19200000, _FREQ_24_576K, 25, 96, 0}, + {24000000, _FREQ_24_576K, 25, 76, 4}, /* accurate */ + + {_FREQ_22_579K, _FREQ_22_579K, 8, 24, 0}, /* accurate, 88200 * 256 */ + {_FREQ_24_576K, _FREQ_24_576K, 8, 24, 0}, /* accurate, 96000 * 256 */ +}; + +static const struct aif1_fs codec_aif1_fs[] = { + {8000, 12, 0}, + {11025, 8, 1, _SERIES_22_579K}, + {12000, 8, 2}, + {16000, 6, 3}, + {22050, 4, 4, _SERIES_22_579K}, + {24000, 4, 5}, + /* {32000, 3, 6}, dividing by 3 is not support */ + {44100, 2, 7, _SERIES_22_579K}, + {48000, 2, 8}, + {96000, 1, 9}, +}; + +static const struct kv_map codec_aif1_lrck[] = { + {16, 0}, + {32, 1}, + {64, 2}, + {128, 3}, + {256, 4}, +}; + +static const struct kv_map codec_aif1_wsize[] = { + {8, 0}, + {16, 1}, + {20, 2}, + {24, 3}, + {32, 3}, +}; + +static const unsigned ac101_bclkdivs[] = { + 1, 2, 4, 6, + 8, 12, 16, 24, + 32, 48, 64, 96, + 128, 192, 0, 0, +}; + +static int ac101_aif_play(struct ac10x_priv* ac10x) { + struct snd_soc_codec * codec = ac10x->codec; + + late_enable_dac(codec, SND_SOC_DAPM_PRE_PMU); + ac101_headphone_event(codec, SND_SOC_DAPM_POST_PMU); + if (drc_used) { + drc_enable(codec, 1); + } + + /* Enable Left & Right Speaker */ + ac101_update_bits(codec, SPKOUT_CTRL, (0x1 << LSPK_EN) | (0x1 << RSPK_EN), (0x1 << LSPK_EN) | (0x1 << RSPK_EN)); + if (ac10x->gpiod_spk_amp_gate) { + gpiod_set_value(ac10x->gpiod_spk_amp_gate, 1); + } + return 0; +} + +static void ac10x_work_aif_play(struct work_struct *work) { + struct ac10x_priv *ac10x = container_of(work, struct ac10x_priv, dlywork.work); + + ac101_aif_play(ac10x); + return; +} + +int ac101_aif_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + + AC101_DBG("mute=%d\n", mute); + + ac101_write(codec, DAC_VOL_CTRL, mute? 0: 0xA0A0); + + if (!mute) { + #if _MASTER_MULTI_CODEC != _MASTER_AC101 + /* enable global clock */ + ac10x->aif1_clken = 0; + ac101_aif1clk(codec, SND_SOC_DAPM_PRE_PMU, 0); + ac101_aif_play(ac10x); + #else + schedule_delayed_work(&ac10x->dlywork, msecs_to_jiffies(50)); + #endif + } else { + #if _MASTER_MULTI_CODEC == _MASTER_AC101 + cancel_delayed_work_sync(&ac10x->dlywork); + #endif + + if (ac10x->gpiod_spk_amp_gate) { + gpiod_set_value(ac10x->gpiod_spk_amp_gate, 0); + } + /* Disable Left & Right Speaker */ + ac101_update_bits(codec, SPKOUT_CTRL, (0x1 << LSPK_EN) | (0x1 << RSPK_EN), (0x0 << LSPK_EN) | (0x0 << RSPK_EN)); + if (drc_used) { + drc_enable(codec, 0); + } + ac101_headphone_event(codec, SND_SOC_DAPM_PRE_PMD); + late_enable_dac(codec, SND_SOC_DAPM_POST_PMD); + + #if _MASTER_MULTI_CODEC != _MASTER_AC101 + ac10x->aif1_clken = 1; + ac101_aif1clk(codec, SND_SOC_DAPM_POST_PMD, 0); + #endif + } + return 0; +} + +void ac101_aif_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *codec_dai) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + + AC101_DBG("stream = %s, play: %d, capt: %d, active: %d\n", + snd_pcm_stream_str(substream), + codec_dai->stream_active[SNDRV_PCM_STREAM_PLAYBACK], codec_dai->stream_active[SNDRV_PCM_STREAM_CAPTURE], + snd_soc_dai_active(codec_dai)); + + if (!snd_soc_dai_active(codec_dai)) { + ac10x->aif1_clken = 1; + ac101_aif1clk(codec, SND_SOC_DAPM_POST_PMD, 0); + } else { + ac101_aif1clk(codec, SND_SOC_DAPM_PRE_PMU, 0); + } +} + +static int ac101_set_pll(struct snd_soc_dai *codec_dai, int pll_id, int source, + unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_codec *codec = codec_dai->codec; + int i, m, n_i, n_f; + + AC101_DBG("pll_id:%d\n", pll_id); + + /* clear volatile reserved bits*/ + ac101_update_bits(codec, SYSCLK_CTRL, 0xFF & ~(0x1 << SYSCLK_ENA), 0x0); + + /* select aif1 clk srouce from mclk1 */ + ac101_update_bits(codec, SYSCLK_CTRL, (0x3< _FREQ_24_576K)) { + return -EINVAL; + } else if ((freq_in == _FREQ_24_576K) || (freq_in == _FREQ_22_579K)) { + if (pll_id == AC101_MCLK1) { + /*select aif1 clk source from mclk1*/ + ac101_update_bits(codec, SYSCLK_CTRL, (0x3<codec; + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + int reg_val, freq_out; + unsigned channels; + + AC101_DBG("+++\n"); + + if (_MASTER_MULTI_CODEC == _MASTER_AC101 && ac101_sysclk_started()) { + /* not configure hw_param twice if stream is playback, tell the caller it's started */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + return 1; + } + } + + /* get channels count & slot size */ + channels = params_channels(params); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S24_LE: + case SNDRV_PCM_FORMAT_S32_LE: + aif1_slot_size = 32; + break; + case SNDRV_PCM_FORMAT_S16_LE: + default: + aif1_slot_size = 16; + break; + } + + /* set LRCK/BCLK ratio */ + aif1_lrck_div = aif1_slot_size * channels; + for (i = 0; i < ARRAY_SIZE(codec_aif1_lrck); i++) { + if (codec_aif1_lrck[i].val == aif1_lrck_div) { + break; + } + } + ac101_update_bits(codec, AIF_CLK_CTRL, (0x7<stream_active[SNDRV_PCM_STREAM_CAPTURE] && dmic_used && codec_aif1_fs[i].samp_rate == 44100) { + ac101_update_bits(codec, AIF_SR_CTRL, (0xf< 2) reg_val = 2; + ac101_update_bits(codec, AIF1_ADCDAT_CTRL, 0x3 << AIF1_SLOT_SIZ, reg_val << AIF1_SLOT_SIZ); + + /* setting pll if it's master mode */ + reg_val = ac101_read(codec, AIF_CLK_CTRL); + if ((reg_val & (0x1 << AIF1_MSTR_MOD)) == 0) { + unsigned bclkdiv; + + ac101_set_pll(codec_dai, AC101_MCLK1, 0, ac10x->sysclk, freq_out); + + bclkdiv = freq_out / (aif1_lrck_div * params_rate(params)); + for (i = 0; i < ARRAY_SIZE(ac101_bclkdivs) - 1; i++) { + if (ac101_bclkdivs[i] >= bclkdiv) { + break; + } + } + ac101_update_bits(codec, AIF_CLK_CTRL, (0xf<codec; + + AC101_DBG(); + + /* + * master or slave selection + * 0 = Master mode + * 1 = Slave mode + */ + reg_val = ac101_read(codec, AIF_CLK_CTRL); + reg_val &= ~(0x1<codec; + + AC101_DBG("\n\n\n"); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + } + return 0; +} + +#if _MASTER_MULTI_CODEC == _MASTER_AC101 +static int ac101_set_clock(int y_start_n_stop, struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) { + int r; + + if (y_start_n_stop) { + /* enable global clock */ + r = ac101_aif1clk(static_ac10x->codec, SND_SOC_DAPM_PRE_PMU, 1); + } else { + /* disable global clock */ + static_ac10x->aif1_clken = 1; + r = ac101_aif1clk(static_ac10x->codec, SND_SOC_DAPM_POST_PMD, 0); + } + return r; +} +#endif + +int ac101_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + int ret = 0; + unsigned long flags; + + AC101_DBG("stream=%s cmd=%d\n", + snd_pcm_stream_str(substream), + cmd); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + #if _MASTER_MULTI_CODEC == _MASTER_AC101 + spin_lock_irqsave(&ac10x->lock, flags); + if (ac10x->aif1_clken == 0){ + /* + * enable aif1clk, it' here due to reduce time between 'AC108 Sysclk Enable' and 'AC101 Sysclk Enable' + * Or else the two AC108 chips lost the sync. + */ + ret = 0; + ret = ret || ac101_update_bits(codec, MOD_CLK_ENA, (0x1<lock, flags); + #endif + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + break; + default: + ret = -EINVAL; + } + AC101_DBG("stream=%s cmd=%d;finished %d\n", + snd_pcm_stream_str(substream), + cmd, ret); + return ret; +} + +#if 0 +static int ac101_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + + AC101_DBG("id=%d freq=%d, dir=%d\n", + clk_id, freq, dir); + + ac10x->sysclk = freq; + + return 0; +} + +static const struct snd_soc_dai_ops ac101_aif1_dai_ops = { + //.startup = ac101_audio_startup, + //.shutdown = ac101_aif_shutdown, + //.set_sysclk = ac101_set_dai_sysclk, + //.set_pll = ac101_set_pll, + //.set_fmt = ac101_set_dai_fmt, + //.hw_params = ac101_hw_params, + //.trigger = ac101_trigger, + //.digital_mute = ac101_aif_mute, +}; + +static struct snd_soc_dai_driver ac101_dai[] = { + { + .name = "ac10x-aif1", + .id = AIF1_CLK, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 8, + .rates = AC101_RATES, + .formats = AC101_FORMATS, + }, + #if 0 + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 8, + .rates = AC101_RATES, + .formats = AC101_FORMATS, + }, + #endif + .ops = &ac101_aif1_dai_ops, + } +}; +#endif + +static void codec_resume_work(struct work_struct *work) +{ + struct ac10x_priv *ac10x = container_of(work, struct ac10x_priv, codec_resume); + struct snd_soc_codec *codec = ac10x->codec; + + AC101_DBG("+++\n"); + + set_configuration(codec); + if (drc_used) { + drc_config(codec); + } + /*enable this bit to prevent leakage from ldoin*/ + ac101_update_bits(codec, ADDA_TUNE3, (0x1<bias_level = level; + return 0; +} + +int ac101_codec_probe(struct snd_soc_codec *codec) +{ + int ret = 0; + struct ac10x_priv *ac10x; + + ac10x = dev_get_drvdata(codec->dev); + if (ac10x == NULL) { + AC101_DBG("not set client data!\n"); + return -ENOMEM; + } + ac10x->codec = codec; + + INIT_DELAYED_WORK(&ac10x->dlywork, ac10x_work_aif_play); + INIT_WORK(&ac10x->codec_resume, codec_resume_work); + ac10x->dac_enable = 0; + ac10x->aif1_clken = 0; + mutex_init(&ac10x->dac_mutex); + + #if _MASTER_MULTI_CODEC == _MASTER_AC101 + seeed_voice_card_register_set_clock(SNDRV_PCM_STREAM_PLAYBACK, ac101_set_clock); + #endif + + set_configuration(ac10x->codec); + + /*enable this bit to prevent leakage from ldoin*/ + ac101_update_bits(codec, ADDA_TUNE3, (0x1<get = snd_ac101_get_volsw; + skn->put = snd_ac101_put_volsw; + } + ret = snd_soc_add_codec_controls(codec, ac101_controls, ARRAY_SIZE(ac101_controls)); + if (ret) { + pr_err("[ac10x] Failed to register audio mode control, " + "will continue without it.\n"); + } + + #ifdef CONFIG_AC101_SWITCH_DETECT + ret = ac101_switch_probe(ac10x); + if (ret) { + // not care the switch return value + } + #endif + + return 0; +} + +/* power down chip */ +int ac101_codec_remove(struct snd_soc_codec *codec) +{ + #ifdef CONFIG_AC101_SWITCH_DETECT + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + + if (ac10x->irq) { + devm_free_irq(codec->dev, ac10x->irq, ac10x); + ac10x->irq = 0; + } + + if (cancel_work_sync(&ac10x->work_switch) != 0) { + } + + if (cancel_work_sync(&ac10x->work_clear_irq) != 0) { + } + + if (ac10x->inpdev) { + input_unregister_device(ac10x->inpdev); + ac10x->inpdev = NULL; + } + #endif + + return 0; +} + +int ac101_codec_suspend(struct snd_soc_codec *codec) +{ + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + + AC101_DBG("[codec]:suspend\n"); + regcache_cache_only(ac10x->regmap101, true); + return 0; +} + +int ac101_codec_resume(struct snd_soc_codec *codec) +{ + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + int ret; + + AC101_DBG("[codec]:resume"); + + /* Sync reg_cache with the hardware */ + regcache_cache_only(ac10x->regmap101, false); + ret = regcache_sync(ac10x->regmap101); + if (ret != 0) { + dev_err(codec->dev, "Failed to sync register cache: %d\n", ret); + regcache_cache_only(ac10x->regmap101, true); + return ret; + } + + #ifdef CONFIG_AC101_SWITCH_DETECT + ac10x->mode = HEADPHONE_IDLE; + ac10x->state = -1; + #endif + + ac101_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + schedule_work(&ac10x->codec_resume); + return 0; +} + +/***************************************************************************/ +static ssize_t ac101_debug_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct ac10x_priv *ac10x = dev_get_drvdata(dev); + int val = 0, flag = 0; + u16 value_w, value_r; + u8 reg, num, i=0; + + val = simple_strtol(buf, NULL, 16); + flag = (val >> 24) & 0xF; + if (flag) { + reg = (val >> 16) & 0xFF; + value_w = val & 0xFFFF; + ac101_write(ac10x->codec, reg, value_w); + printk("write 0x%x to reg:0x%x\n", value_w, reg); + } else { + reg = (val >> 8) & 0xFF; + num = val & 0xff; + printk("\n"); + printk("read:start add:0x%x,count:0x%x\n", reg, num); + + regcache_cache_bypass(ac10x->regmap101, true); + do { + value_r = ac101_read(ac10x->codec, reg); + printk("0x%x: 0x%04x ", reg++, value_r); + if (++i % 4 == 0 || i == num) + printk("\n"); + } while (i < num); + regcache_cache_bypass(ac10x->regmap101, false); + } + return count; +} +static ssize_t ac101_debug_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + printk("echo flag|reg|val > ac10x\n"); + printk("eg read star addres=0x06,count 0x10:echo 0610 >ac10x\n"); + printk("eg write value:0x13fe to address:0x06 :echo 10613fe > ac10x\n"); + return 0; +} +static DEVICE_ATTR(ac10x, 0644, ac101_debug_show, ac101_debug_store); + +static struct attribute *audio_debug_attrs[] = { + &dev_attr_ac10x.attr, + NULL, +}; + +static struct attribute_group audio_debug_attr_group = { + .name = "ac101_debug", + .attrs = audio_debug_attrs, +}; +/***************************************************************************/ + +/************************************************************/ +static bool ac101_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case PLL_CTRL2: + case HMIC_STS: + return true; + } + return false; +} + +static const struct regmap_config ac101_regmap = { + .reg_bits = 8, + .val_bits = 16, + .reg_stride = 1, + .max_register = 0xB5, + .cache_type = REGCACHE_FLAT, + .volatile_reg = ac101_volatile_reg, +}; + +/* Sync reg_cache from the hardware */ +int ac10x_fill_regcache(struct device* dev, struct regmap* map) { + int r, i, n; + int v; + + n = regmap_get_max_register(map); + for (i = 0; i < n; i++) { + regcache_cache_bypass(map, true); + r = regmap_read(map, i, &v); + if (r) { + dev_err(dev, "failed to read register %d\n", i); + continue; + } + regcache_cache_bypass(map, false); + + regcache_cache_only(map, true); + r = regmap_write(map, i, v); + regcache_cache_only(map, false); + } + regcache_cache_bypass(map, false); + regcache_cache_only(map, false); + + return 0; +} + +int ac101_probe(struct i2c_client *i2c, const struct i2c_device_id *id) +{ + struct ac10x_priv *ac10x = i2c_get_clientdata(i2c); + int ret = 0; + unsigned v = 0; + + AC101_DBG(); + + static_ac10x = ac10x; + + ac10x->regmap101 = devm_regmap_init_i2c(i2c, &ac101_regmap); + if (IS_ERR(ac10x->regmap101)) { + ret = PTR_ERR(ac10x->regmap101); + dev_err(&i2c->dev, "Fail to initialize I/O: %d\n", ret); + return ret; + } + + /* Chip reset */ + regcache_cache_only(ac10x->regmap101, false); + ret = regmap_write(ac10x->regmap101, CHIP_AUDIO_RST, 0); + msleep(50); + + /* sync regcache for FLAT type */ + ac10x_fill_regcache(&i2c->dev, ac10x->regmap101); + + ret = regmap_read(ac10x->regmap101, CHIP_AUDIO_RST, &v); + if (ret < 0) { + dev_err(&i2c->dev, "failed to read vendor ID: %d\n", ret); + return ret; + } + + if (v != AC101_CHIP_ID) { + dev_err(&i2c->dev, "chip is not AC101 (%X)\n", v); + dev_err(&i2c->dev, "Expected %X\n", AC101_CHIP_ID); + return -ENODEV; + } + + ret = sysfs_create_group(&i2c->dev.kobj, &audio_debug_attr_group); + if (ret) { + pr_err("failed to create attr group\n"); + } + + ac10x->gpiod_spk_amp_gate = devm_gpiod_get_optional(&i2c->dev, "spk-amp-switch", GPIOD_OUT_LOW); + if (IS_ERR(ac10x->gpiod_spk_amp_gate)) { + ac10x->gpiod_spk_amp_gate = NULL; + dev_err(&i2c->dev, "failed get spk-amp-switch in device tree\n"); + } + + return 0; +} + +void ac101_shutdown(struct i2c_client *i2c) +{ + struct ac10x_priv *ac10x = i2c_get_clientdata(i2c); + struct snd_soc_codec *codec = ac10x->codec; + int reg_val; + + if (codec == NULL) { + pr_err(": no sound card.\n"); + return; + } + + /*set headphone volume to 0*/ + reg_val = ac101_read(codec, HPOUT_CTRL); + reg_val &= ~(0x3f<dev.kobj, &audio_debug_attr_group); + return 0; +} + +MODULE_DESCRIPTION("ASoC ac10x driver"); +MODULE_AUTHOR("huangxin,liushaohua"); +MODULE_AUTHOR("PeterYang"); diff --git a/seeed-voicecard/ac101_regs.h b/seeed-voicecard/ac101_regs.h new file mode 100644 index 0000000..8be570e --- /dev/null +++ b/seeed-voicecard/ac101_regs.h @@ -0,0 +1,432 @@ +/* + * ac101_regs.h + * + * (C) Copyright 2017-2018 + * Seeed Technology Co., Ltd. + * + * PeterYang + * + * (C) Copyright 2010-2017 + * Reuuimlla Technology Co., Ltd. + * huangxin + * + * some simple description for this code + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + */ +#ifndef __AC101_REGS_H__ +#define __AC101_REGS_H__ + +/*pll source*/ +#define AC101_MCLK1 1 +#define AC101_MCLK2 2 +#define AC101_BCLK1 3 +#define AC101_BCLK2 4 + +#define AIF1_CLK 1 +#define AIF2_CLK 2 + +#define CHIP_AUDIO_RST 0x0 +#define PLL_CTRL1 0x1 +#define PLL_CTRL2 0x2 +#define SYSCLK_CTRL 0x3 +#define MOD_CLK_ENA 0x4 +#define MOD_RST_CTRL 0x5 +#define AIF_SR_CTRL 0x6 + +#define AIF1_CLK_CTRL 0x10 +#define AIF1_ADCDAT_CTRL 0x11 +#define AIF1_DACDAT_CTRL 0x12 +#define AIF1_MXR_SRC 0x13 +#define AIF1_VOL_CTRL1 0x14 +#define AIF1_VOL_CTRL2 0x15 +#define AIF1_VOL_CTRL3 0x16 +#define AIF1_VOL_CTRL4 0x17 +#define AIF1_MXR_GAIN 0x18 +#define AIF1_RXD_CTRL 0x19 +#define ADC_DIG_CTRL 0x40 +#define ADC_VOL_CTRL 0x41 +#define ADC_DBG_CTRL 0x42 + +#define HMIC_CTRL1 0x44 +#define HMIC_CTRL2 0x45 +#define HMIC_STS 0x46 + +#define DAC_DIG_CTRL 0x48 +#define DAC_VOL_CTRL 0x49 +#define DAC_DBG_CTRL 0x4a +#define DAC_MXR_SRC 0x4c +#define DAC_MXR_GAIN 0x4d + +#define ADC_APC_CTRL 0x50 +#define ADC_SRC 0x51 +#define ADC_SRCBST_CTRL 0x52 +#define OMIXER_DACA_CTRL 0x53 +#define OMIXER_SR 0x54 +#define OMIXER_BST1_CTRL 0x55 +#define HPOUT_CTRL 0x56 +#define ESPKOUT_CTRL 0x57 +#define SPKOUT_CTRL 0x58 +#define LOUT_CTRL 0x59 +#define ADDA_TUNE1 0x5a +#define ADDA_TUNE2 0x5b +#define ADDA_TUNE3 0x5c +#define HPOUT_STR 0x5d + +/*CHIP_AUDIO_RST*/ +#define AC101_CHIP_ID 0x0101 + +/*PLL_CTRL1*/ +#define DPLL_DAC_BIAS 14 +#define PLL_POSTDIV_M 8 +#define CLOSE_LOOP 6 +#define INT 0 + +/*PLL_CTRL2*/ +#define PLL_EN 15 +#define PLL_LOCK_STATUS 14 +#define PLL_PREDIV_NI 4 +#define PLL_POSTDIV_NF 0 + +/*SYSCLK_CTRL*/ +#define PLLCLK_ENA 15 +#define PLLCLK_SRC 12 +#define AIF1CLK_ENA 11 +#define AIF1CLK_SRC 8 +#define AIF2CLK_ENA 7 +#define AIF2CLK_SRC 4 +#define SYSCLK_ENA 3 +#define SYSCLK_SRC 0 + +/*MOD_CLK_ENA*/ +#define MOD_CLK_AIF1 15 +#define MOD_CLK_AIF2 14 +#define MOD_CLK_AIF3 13 +#define MOD_CLK_SRC1 11 +#define MOD_CLK_SRC2 10 +#define MOD_CLK_HPF_AGC 7 +#define MOD_CLK_HPF_DRC 6 +#define MOD_CLK_ADC_DIG 3 +#define MOD_CLK_DAC_DIG 2 + +/*MOD_RST_CTRL*/ +#define MOD_RESET_CTL 0 +#define MOD_RESET_AIF1 15 +#define MOD_RESET_AIF2 14 +#define MOD_RESET_AIF3 13 +#define MOD_RESET_SRC1 11 +#define MOD_RESET_SRC2 10 +#define MOD_RESET_HPF_AGC 7 +#define MOD_RESET_HPF_DRC 6 +#define MOD_RESET_ADC_DIG 3 +#define MOD_RESET_DAC_DIG 2 + +/*AIF_SR_CTRL*/ +#define AIF1_FS 12 //AIF1 Sample Rate +#define AIF2_FS 8 //AIF2 Sample Rate +#define SRC1_ENA 3 +#define SRC1_SRC 2 +#define SRC2_ENA 1 +#define SRC2_SRC 0 + +/*AIF1LCK_CTRL*/ +#define AIF1_MSTR_MOD 15 +#define AIF1_BCLK_INV 14 +#define AIF1_LRCK_INV 13 +#define AIF1_BCLK_DIV 9 +#define AIF1_LRCK_DIV 6 +#define AIF1_WORK_SIZ 4 +#define AIF1_DATA_FMT 2 +#define DSP_MONO_PCM 1 +#define AIF1_TDMM_ENA 0 + +/*AIF1_ADCDAT_CTRL*/ +#define AIF1_AD0L_ENA 15 +#define AIF1_AD0R_ENA 14 +#define AIF1_AD1L_ENA 13 +#define AIF1_AD1R_ENA 12 +#define AIF1_AD0L_SRC 10 +#define AIF1_AD0R_SRC 8 +#define AIF1_AD1L_SRC 6 +#define AIF1_AD1R_SRC 4 +#define AIF1_ADCP_ENA 3 +#define AIF1_ADUL_ENA 2 +#define AIF1_SLOT_SIZ 0 + +/*AIF1_DACDAT_CTRL*/ +#define AIF1_DA0L_ENA 15 +#define AIF1_DA0R_ENA 14 +#define AIF1_DA1L_ENA 13 +#define AIF1_DA1R_ENA 12 +#define AIF1_DA0L_SRC 10 +#define AIF1_DA0R_SRC 8 +#define AIF1_DA1L_SRC 6 +#define AIF1_DA1R_SRC 4 +#define AIF1_DACP_ENA 3 +#define AIF1_DAUL_ENA 2 +#define AIF1_SLOT_SIZ 0 + +/*AIF1_MXR_SRC*/ +#define AIF1_AD0L_AIF1_DA0L_MXR 15 +#define AIF1_AD0L_AIF2_DACL_MXR 14 +#define AIF1_AD0L_ADCL_MXR 13 +#define AIF1_AD0L_AIF2_DACR_MXR 12 +#define AIF1_AD0R_AIF1_DA0R_MXR 11 +#define AIF1_AD0R_AIF2_DACR_MXR 10 +#define AIF1_AD0R_ADCR_MXR 9 +#define AIF1_AD0R_AIF2_DACL_MXR 8 +#define AIF1_AD1L_AIF2_DACL_MXR 7 +#define AIF1_AD1L_ADCL_MXR 6 +#define AIF1_AD1L_MXR_SRC 6 +#define AIF1_AD1R_AIF2_DACR_MXR 3 +#define AIF1_AD1R_ADCR_MXR 2 +#define AIF1_AD1R_MXR_SRC 2 + +/*AIF1_VOL_CTRL1*/ +#define AIF1_AD0L_VOL 8 +#define AIF1_AD0R_VOL 0 + +/*AIF1_VOL_CTRL2*/ +#define AIF1_AD1L_VOL 8 +#define AIF1_AD1R_VOL 0 + +/*AIF1_VOL_CTRL3*/ +#define AIF1_DA0L_VOL 8 +#define AIF1_DA0R_VOL 0 + +/*AIF1_VOL_CTRL4*/ +#define AIF1_DA1L_VOL 8 +#define AIF1_DA1R_VOL 0 + +/*AIF1_MXR_GAIN*/ +#define AIF1_AD0L_MXR_GAIN 12 +#define AIF1_AD0R_MXR_GAIN 8 +#define AIF1_AD1L_MXR_GAIN 6 +#define AIF1_AD1R_MXR_GAIN 2 + +/*AIF1_RXD_CTRL*/ +#define AIF1_N_DATA_DISCARD 8 + +/*ADC_DIG_CTRL*/ +#define ENAD 15 +#define ENDM 14 +#define ADFIR32 13 +#define ADOUT_DTS 2 +#define ADOUT_DLY 1 + +/*ADC_VOL_CTRL*/ +#define ADC_VOL_L 8 +#define ADC_VOL_R 0 + +/*ADC_DBG_CTRL*/ +#define ADSW 15 +#define DMIC_CLK_PIN_CTRL 12 + +/*HMIC_CTRL1*/ +#define HMIC_M 12 +#define HMIC_N 8 +#define HMIC_DATA_IRQ_MODE 7 +#define HMIC_TH1_HYSTERESIS 5 +#define HMIC_PULLOUT_IRQ 4 +#define HMIC_PLUGIN_IRQ 3 +#define HMIC_KEYUP_IRQ 2 +#define HMIC_KEYDOWN_IRQ 1 +#define HMIC_DATA_IRQ_EN 0 + +/*HMIC_CTRL2*/ +#define HMIC_SAMPLE_SELECT 14 +#define HMIC_TH2_HYSTERESIS 13 +#define HMIC_TH2 8 +#define HMIC_SF 6 +#define KEYUP_CLEAR 5 +#define HMIC_TH1 0 + +/*HMIC_STS*/ +#define HMIC_DATA 8 +#define GET_HMIC_DATA(r) (((r) >> HMIC_DATA) & 0x1F) +#define HMIC_PULLOUT_PEND 4 +#define HMIC_PLUGIN_PEND 3 +#define HMIC_KEYUP_PEND 2 +#define HMKC_KEYDOWN_PEND 1 +#define HMIC_DATA_PEND 0 +#define HMIC_PEND_ALL (0x1F) + +/*DAC_DIG_CTRL*/ +#define ENDA 15 +#define ENHPF 14 +#define DAFIR32 13 +#define MODQU 8 + +/*DAC_VOL_CTRL*/ +#define DAC_VOL_L 8 +#define DAC_VOL_R 0 + +/*DAC_DBG_CTRL*/ +#define DASW 15 +#define ENDWA_N 14 +#define DAC_MOD_DBG 13 +#define DAC_PTN_SEL 6 +#define DVC 0 + +/*DAC_MXR_SRC*/ +#define DACL_MXR_AIF1_DA0L 15 +#define DACL_MXR_AIF1_DA1L 14 +#define DACL_MXR_AIF2_DACL 13 +#define DACL_MXR_ADCL 12 +#define DACL_MXR_SRC 12 +#define DACR_MXR_AIF1_DA0R 11 +#define DACR_MXR_AIF1_DA1R 10 +#define DACR_MXR_AIF2_DACR 9 +#define DACR_MXR_ADCR 8 +#define DACR_MXR_SRC 8 + +/*DAC_MXR_GAIN*/ +#define DACL_MXR_GAIN 12 +#define DACR_MXR_GAIN 8 + +/*ADC_APC_CTRL*/ +#define ADCREN 15 +#define ADCRG 12 +#define ADCLEN 11 +#define ADCLG 8 +#define MBIASEN 7 +#define MMIC_BIAS_CHOP_EN 6 +#define MMIC_BIAS_CHOP_CKS 4 +#define HBIASMOD 2 +#define HBIASEN 1 +#define HBIASADCEN 0 + +/*ADC_SRC*/ +#define RADCMIXMUTEMIC1BOOST (13) +#define RADCMIXMUTEMIC2BOOST (12) +#define RADCMIXMUTELINEINLR (11) +#define RADCMIXMUTELINEINR (10) +#define RADCMIXMUTEAUXINR (9) +#define RADCMIXMUTEROUTPUT (8) +#define RADCMIXMUTELOUTPUT (7) +#define LADCMIXMUTEMIC1BOOST (6) +#define LADCMIXMUTEMIC2BOOST (5) +#define LADCMIXMUTELINEINLR (4) +#define LADCMIXMUTELINEINL (3) +#define LADCMIXMUTEAUXINL (2) +#define LADCMIXMUTELOUTPUT (1) +#define LADCMIXMUTEROUTPUT (0) + + +/*ADC_SRCBST_CTRL*/ +#define MIC1AMPEN 15 +#define ADC_MIC1G 12 +#define MIC2AMPEN 11 +#define ADC_MIC2G 8 +#define MIC2SLT 7 +#define LINEIN_PREG 4 +#define AUXI_PREG 0 + +/*OMIXER_DACA_CTRL*/ +#define DACAREN 15 +#define DACALEN 14 +#define RMIXEN 13 +#define LMIXEN 12 +#define HPOUTPUTENABLE 8 + +/*OMIXER_SR*/ +#define RMIXMUTEMIC1BOOST (13) +#define RMIXMUTEMIC2BOOST (12) +#define RMIXMUTELINEINLR (11) +#define RMIXMUTELINEINR (10) +#define RMIXMUTEAUXINR (9) +#define RMIXMUTEDACR (8) +#define RMIXMUTEDACL (7) +#define LMIXMUTEMIC1BOOST (6) +#define LMIXMUTEMIC2BOOST (5) +#define LMIXMUTELINEINLR (4) +#define LMIXMUTELINEINL (3) +#define LMIXMUTEAUXINL (2) +#define LMIXMUTEDACL (1) +#define LMIXMUTEDACR (0) + +/*OMIXER_BST1_CTRL*/ +#define BIASVOLTAGE 12 +#define AXG 9 +#define OMIXER_MIC1G 6 +#define OMIXER_MIC2G 3 +#define LINEING 0 + +/*HPOUT_CTRL*/ +#define RHPS 15 +#define LHPS 14 +#define RHPPA_MUTE 13 +#define LHPPA_MUTE 12 +#define HPPA_EN 11 +#define HP_VOL 4 +#define HPPA_DEL 2 +#define HPPA_IS 0 + +/*ESPKOUT_CTRL*/ +#define EAR_RAMP_TIME 11 +#define ESPA_OUT_CURRENT 9 +#define ESPSR 7 +#define ESPPA_MUTE 6 +#define ESPPA_EN 5 +#define ESP_VOL 0 + +/*SPKOUT_CTRL*/ +#define HPCALICKS 13 +#define RSPKS 12 +#define RSPKINVEN 11 +#define RSPK_EN 9 +#define LSPKS 8 +#define LSPKINVEN 7 +#define LSPK_EN 5 +#define SPK_VOL 0 + +/*LOUT_CTRL*/ +#define LINEOUTG 5 +#define LINEOUTEN 4 +#define LINEOUTS0 3 +#define LINEOUTS1 2 +#define LINEOUTS2 1 +#define LINEOUTS3 0 + +/*ADDA_TUNE1*/ +#define CURRENT_TEST_SELECT 14 +#define BIHE_CTRL 12 +#define DITHER 11 +#define DITHER_CLK 9 +#define ZERO_CROSSOVER_EN 8 +#define ZERO_CROSSOVER_TIME 7 +#define EAR_SPEED_SELECT 6 +#define REF_CHOPPEN_CKS 4 +#define OPMIC_BIAS_CUR 0 + +/*ADDA_TUNE2*/ +#define OPDAC_BIAS_CUR 14 +#define OPDRV_BIAS_CUR 12 +#define OPMIX_BIAS_CUR 10 +#define OPEAR_BIAS_CUR 8 +#define OPVR_BIAS_CUR 6 +#define OPAAF_BIAS_CUR 4 +#define OPADC1_BIAS_CUR 2 +#define OPADC2_BIAS_CUR 0 + +/*ADDA_TUNE3*/ +#define LDOEN 15 +#define LDO_SEL 12 +#define BIASCALIVERIFY 11 +#define BIASMODE 10 +#define BIASCALIDATA 9 +#define OSCS 1 +#define OSCEN 0 + +/*HPOUT_STR*/ +#define HPVL_SOFT_MOD 14 +#define HPVL_STEP_CTRL 8 +#define DACA_CHND_ENA 7 +#define HPPA_MXRD_ENA 6 +#define HPVL_CTRL_OUT 0 + +#endif//__AC101_REGS_H__ diff --git a/seeed-voicecard/ac108.c b/seeed-voicecard/ac108.c new file mode 100644 index 0000000..d5dd12d --- /dev/null +++ b/seeed-voicecard/ac108.c @@ -0,0 +1,1559 @@ +/* + * ac10x.c -- ac10x ALSA SoC Audio driver + * + * + * Author: Baozhu Zuo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +/* #undef DEBUG + * use 'make DEBUG=1' to enable debugging + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ac108.h" +#include "ac10x.h" + +#define _USE_CAPTURE 1 +#define _MASTER_INDEX 0 + +/** + * TODO: + * 1, add PM API: ac108_suspend,ac108_resume + * 2,0x65-0x6a + * 3,0x76-0x79 high 4bit + */ +struct pll_div { + unsigned int freq_in; + unsigned int freq_out; + unsigned int m1; + unsigned int m2; + unsigned int n; + unsigned int k1; + unsigned int k2; +}; + +static struct ac10x_priv *ac10x; + +struct real_val_to_reg_val { + unsigned int real_val; + unsigned int reg_val; +}; + +static const struct real_val_to_reg_val ac108_sample_rate[] = { + { 8000, 0 }, + { 11025, 1 }, + { 12000, 2 }, + { 16000, 3 }, + { 22050, 4 }, + { 24000, 5 }, + { 32000, 6 }, + { 44100, 7 }, + { 48000, 8 }, + { 96000, 9 }, +}; + +/* Sample resolution */ +static const struct real_val_to_reg_val ac108_samp_res[] = { + { 8, 1 }, + { 12, 2 }, + { 16, 3 }, + { 20, 4 }, + { 24, 5 }, + { 28, 6 }, + { 32, 7 }, +}; + +static const unsigned ac108_bclkdivs[] = { + 0, 1, 2, 4, + 6, 8, 12, 16, + 24, 32, 48, 64, + 96, 128, 176, 192, +}; + +/* FOUT =(FIN * N) / [(M1+1) * (M2+1)*(K1+1)*(K2+1)] ; M1[0,31], M2[0,1], N[0,1023], K1[0,31], K2[0,1] */ +static const struct pll_div ac108_pll_div_list[] = { + { 400000, _FREQ_24_576K, 0, 0, 614, 4, 1 }, + { 512000, _FREQ_24_576K, 0, 0, 960, 9, 1 }, //_FREQ_24_576K/48 + { 768000, _FREQ_24_576K, 0, 0, 640, 9, 1 }, //_FREQ_24_576K/32 + { 800000, _FREQ_24_576K, 0, 0, 614, 9, 1 }, + { 1024000, _FREQ_24_576K, 0, 0, 480, 9, 1 }, //_FREQ_24_576K/24 + { 1600000, _FREQ_24_576K, 0, 0, 307, 9, 1 }, + { 2048000, _FREQ_24_576K, 0, 0, 240, 9, 1 }, /* accurate, 8000 * 256 */ + { 3072000, _FREQ_24_576K, 0, 0, 160, 9, 1 }, /* accurate, 12000 * 256 */ + { 4096000, _FREQ_24_576K, 2, 0, 360, 9, 1 }, /* accurate, 16000 * 256 */ + { 6000000, _FREQ_24_576K, 4, 0, 410, 9, 1 }, + { 12000000, _FREQ_24_576K, 9, 0, 410, 9, 1 }, + { 13000000, _FREQ_24_576K, 8, 0, 340, 9, 1 }, + { 15360000, _FREQ_24_576K, 12, 0, 415, 9, 1 }, + { 16000000, _FREQ_24_576K, 12, 0, 400, 9, 1 }, + { 19200000, _FREQ_24_576K, 15, 0, 410, 9, 1 }, + { 19680000, _FREQ_24_576K, 15, 0, 400, 9, 1 }, + { 24000000, _FREQ_24_576K, 4, 0, 128,24, 0 }, // accurate, 24M -> 24.576M */ + + { 400000, _FREQ_22_579K, 0, 0, 566, 4, 1 }, + { 512000, _FREQ_22_579K, 0, 0, 880, 9, 1 }, + { 768000, _FREQ_22_579K, 0, 0, 587, 9, 1 }, + { 800000, _FREQ_22_579K, 0, 0, 567, 9, 1 }, + { 1024000, _FREQ_22_579K, 0, 0, 440, 9, 1 }, + { 1600000, _FREQ_22_579K, 1, 0, 567, 9, 1 }, + { 2048000, _FREQ_22_579K, 0, 0, 220, 9, 1 }, + { 3072000, _FREQ_22_579K, 0, 0, 148, 9, 1 }, + { 4096000, _FREQ_22_579K, 2, 0, 330, 9, 1 }, + { 6000000, _FREQ_22_579K, 2, 0, 227, 9, 1 }, + { 12000000, _FREQ_22_579K, 8, 0, 340, 9, 1 }, + { 13000000, _FREQ_22_579K, 9, 0, 350, 9, 1 }, + { 15360000, _FREQ_22_579K, 10, 0, 325, 9, 1 }, + { 16000000, _FREQ_22_579K, 11, 0, 340, 9, 1 }, + { 19200000, _FREQ_22_579K, 13, 0, 330, 9, 1 }, + { 19680000, _FREQ_22_579K, 14, 0, 345, 9, 1 }, + { 24000000, _FREQ_22_579K, 24, 0, 588,24, 0 }, // accurate, 24M -> 22.5792M */ + + + { _FREQ_24_576K / 1, _FREQ_24_576K, 9, 0, 200, 9, 1 }, //_FREQ_24_576K + { _FREQ_24_576K / 2, _FREQ_24_576K, 9, 0, 400, 9, 1 }, /*12288000,accurate, 48000 * 256 */ + { _FREQ_24_576K / 4, _FREQ_24_576K, 4, 0, 400, 9, 1 }, /*6144000, accurate, 24000 * 256 */ + { _FREQ_24_576K / 16, _FREQ_24_576K, 0, 0, 320, 9, 1 }, //1536000 + { _FREQ_24_576K / 64, _FREQ_24_576K, 0, 0, 640, 4, 1 }, //384000 + { _FREQ_24_576K / 96, _FREQ_24_576K, 0, 0, 960, 4, 1 }, //256000 + { _FREQ_24_576K / 128, _FREQ_24_576K, 0, 0, 512, 1, 1 }, //192000 + { _FREQ_24_576K / 176, _FREQ_24_576K, 0, 0, 880, 4, 0 }, //140000 + { _FREQ_24_576K / 192, _FREQ_24_576K, 0, 0, 960, 4, 0 }, //128000 + + { _FREQ_22_579K / 1, _FREQ_22_579K, 9, 0, 200, 9, 1 }, //_FREQ_22_579K + { _FREQ_22_579K / 2, _FREQ_22_579K, 9, 0, 400, 9, 1 }, /*11289600,accurate, 44100 * 256 */ + { _FREQ_22_579K / 4, _FREQ_22_579K, 4, 0, 400, 9, 1 }, /*5644800, accurate, 22050 * 256 */ + { _FREQ_22_579K / 16, _FREQ_22_579K, 0, 0, 320, 9, 1 }, //1411200 + { _FREQ_22_579K / 64, _FREQ_22_579K, 0, 0, 640, 4, 1 }, //352800 + { _FREQ_22_579K / 96, _FREQ_22_579K, 0, 0, 960, 4, 1 }, //235200 + { _FREQ_22_579K / 128, _FREQ_22_579K, 0, 0, 512, 1, 1 }, //176400 + { _FREQ_22_579K / 176, _FREQ_22_579K, 0, 0, 880, 4, 0 }, //128290 + { _FREQ_22_579K / 192, _FREQ_22_579K, 0, 0, 960, 4, 0 }, //117600 + + { _FREQ_22_579K / 6, _FREQ_22_579K, 2, 0, 360, 9, 1 }, //3763200 + { _FREQ_22_579K / 8, _FREQ_22_579K, 0, 0, 160, 9, 1 }, /*2822400, accurate, 11025 * 256 */ + { _FREQ_22_579K / 12, _FREQ_22_579K, 0, 0, 240, 9, 1 }, //1881600 + { _FREQ_22_579K / 24, _FREQ_22_579K, 0, 0, 480, 9, 1 }, //940800 + { _FREQ_22_579K / 32, _FREQ_22_579K, 0, 0, 640, 9, 1 }, //705600 + { _FREQ_22_579K / 48, _FREQ_22_579K, 0, 0, 960, 9, 1 }, //470400 +}; + + +/* AC108 definition */ +#define AC108_CHANNELS_MAX 8 /* range[1, 16] */ +#define AC108_RATES (SNDRV_PCM_RATE_8000_96000 & \ + ~(SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_64000 | \ + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)) +#define AC108_FORMATS (/*SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE |*/ \ + SNDRV_PCM_FMTBIT_S32_LE) + +static const DECLARE_TLV_DB_SCALE(tlv_adc_pga_gain, 0, 100, 0); +static const DECLARE_TLV_DB_SCALE(tlv_ch_digital_vol, -11925,75,0); + +int ac10x_read(u8 reg, u8* rt_val, struct regmap* i2cm) { + int r, v = 0; + + if ((r = regmap_read(i2cm, reg, &v)) < 0) { + pr_err("ac10x_read error->[REG-0x%02x]\n", reg); + } else { + *rt_val = v; + } + return r; +} + +int ac10x_write(u8 reg, u8 val, struct regmap* i2cm) { + int r; + + if ((r = regmap_write(i2cm, reg, val)) < 0) { + pr_err("ac10x_write error->[REG-0x%02x,val-0x%02x]\n", reg, val); + } + return r; +} + +int ac10x_update_bits(u8 reg, u8 mask, u8 val, struct regmap* i2cm) { + int r; + + if ((r = regmap_update_bits(i2cm, reg, mask, val)) < 0) { + pr_err("%s() error->[REG-0x%02x,val-0x%02x]\n", __func__, reg, val); + } + return r; +} + +/** + * snd_ac108_get_volsw - single mixer get callback + * @kcontrol: mixer control + * @ucontrol: control element information + * + * Callback to get the value of a single mixer control, or a double mixer + * control that spans 2 registers. + * + * Returns 0 for success. + */ +static int snd_ac108_get_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol +){ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int mask = (1 << fls(mc->max)) - 1; + unsigned int invert = mc->invert; + int ret, chip = mc->autodisable; + u8 val; + + if ((ret = ac10x_read(mc->reg, &val, ac10x->i2cmap[chip])) < 0) + return ret; + + val = ((val >> mc->shift) & mask) - mc->min; + if (invert) { + val = mc->max - val; + } + ucontrol->value.integer.value[0] = val; + return 0; +} + +/** + * snd_ac108_put_volsw - single mixer put callback + * @kcontrol: mixer control + * @ucontrol: control element information + * + * Callback to set the value of a single mixer control, or a double mixer + * control that spans 2 registers. + * + * Returns 0 for success. + */ +static int snd_ac108_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol +){ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int sign_bit = mc->sign_bit; + unsigned int val, mask = (1 << fls(mc->max)) - 1; + unsigned int invert = mc->invert; + int ret, chip = mc->autodisable; + + if (sign_bit) + mask = BIT(sign_bit + 1) - 1; + + val = ((ucontrol->value.integer.value[0] + mc->min) & mask); + if (invert) { + val = mc->max - val; + } + + mask = mask << mc->shift; + val = val << mc->shift; + + ret = ac10x_update_bits(mc->reg, mask, val, ac10x->i2cmap[chip]); + return ret; +} + +#define SOC_AC108_SINGLE_TLV(xname, reg, shift, max, invert, chip, tlv_array) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ + SNDRV_CTL_ELEM_ACCESS_READWRITE,\ + .tlv.p = (tlv_array), \ + .info = snd_soc_info_volsw, .get = snd_ac108_get_volsw,\ + .put = snd_ac108_put_volsw, \ + .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert, chip) } + +/* single ac108 */ +static const struct snd_kcontrol_new ac108_snd_controls[] = { + /* ### chip 0 ### */ + /*0x70: ADC1 Digital Channel Volume Control Register*/ + SOC_AC108_SINGLE_TLV("CH1 digital volume", ADC1_DVOL_CTRL, 0, 0xff, 0, 0, tlv_ch_digital_vol), + /*0x71: ADC2 Digital Channel Volume Control Register*/ + SOC_AC108_SINGLE_TLV("CH2 digital volume", ADC2_DVOL_CTRL, 0, 0xff, 0, 0, tlv_ch_digital_vol), + /*0x72: ADC3 Digital Channel Volume Control Register*/ + SOC_AC108_SINGLE_TLV("CH3 digital volume", ADC3_DVOL_CTRL, 0, 0xff, 0, 0, tlv_ch_digital_vol), + /*0x73: ADC4 Digital Channel Volume Control Register*/ + SOC_AC108_SINGLE_TLV("CH4 digital volume", ADC4_DVOL_CTRL, 0, 0xff, 0, 0, tlv_ch_digital_vol), + + /*0x90: Analog PGA1 Control Register*/ + SOC_AC108_SINGLE_TLV("ADC1 PGA gain", ANA_PGA1_CTRL, ADC1_ANALOG_PGA, 0x1f, 0, 0, tlv_adc_pga_gain), + /*0x91: Analog PGA2 Control Register*/ + SOC_AC108_SINGLE_TLV("ADC2 PGA gain", ANA_PGA2_CTRL, ADC2_ANALOG_PGA, 0x1f, 0, 0, tlv_adc_pga_gain), + /*0x92: Analog PGA3 Control Register*/ + SOC_AC108_SINGLE_TLV("ADC3 PGA gain", ANA_PGA3_CTRL, ADC3_ANALOG_PGA, 0x1f, 0, 0, tlv_adc_pga_gain), + /*0x93: Analog PGA4 Control Register*/ + SOC_AC108_SINGLE_TLV("ADC4 PGA gain", ANA_PGA4_CTRL, ADC4_ANALOG_PGA, 0x1f, 0, 0, tlv_adc_pga_gain), +}; +/* multiple ac108s */ +static const struct snd_kcontrol_new ac108tdm_snd_controls[] = { + /* ### chip 1 ### */ + /*0x70: ADC1 Digital Channel Volume Control Register*/ + SOC_AC108_SINGLE_TLV("CH1 digital volume", ADC1_DVOL_CTRL, 0, 0xff, 0, 1, tlv_ch_digital_vol), + /*0x71: ADC2 Digital Channel Volume Control Register*/ + SOC_AC108_SINGLE_TLV("CH2 digital volume", ADC2_DVOL_CTRL, 0, 0xff, 0, 1, tlv_ch_digital_vol), + /*0x72: ADC3 Digital Channel Volume Control Register*/ + SOC_AC108_SINGLE_TLV("CH3 digital volume", ADC3_DVOL_CTRL, 0, 0xff, 0, 1, tlv_ch_digital_vol), + /*0x73: ADC4 Digital Channel Volume Control Register*/ + SOC_AC108_SINGLE_TLV("CH4 digital volume", ADC4_DVOL_CTRL, 0, 0xff, 0, 1, tlv_ch_digital_vol), + + /*0x90: Analog PGA1 Control Register*/ + SOC_AC108_SINGLE_TLV("ADC1 PGA gain", ANA_PGA1_CTRL, ADC1_ANALOG_PGA, 0x1f, 0, 1, tlv_adc_pga_gain), + /*0x91: Analog PGA2 Control Register*/ + SOC_AC108_SINGLE_TLV("ADC2 PGA gain", ANA_PGA2_CTRL, ADC2_ANALOG_PGA, 0x1f, 0, 1, tlv_adc_pga_gain), + /*0x92: Analog PGA3 Control Register*/ + SOC_AC108_SINGLE_TLV("ADC3 PGA gain", ANA_PGA3_CTRL, ADC3_ANALOG_PGA, 0x1f, 0, 1, tlv_adc_pga_gain), + /*0x93: Analog PGA4 Control Register*/ + SOC_AC108_SINGLE_TLV("ADC4 PGA gain", ANA_PGA4_CTRL, ADC4_ANALOG_PGA, 0x1f, 0, 1, tlv_adc_pga_gain), + + /* ### chip 0 ### */ + /*0x70: ADC1 Digital Channel Volume Control Register*/ + SOC_AC108_SINGLE_TLV("CH5 digital volume", ADC1_DVOL_CTRL, 0, 0xff, 0, 0, tlv_ch_digital_vol), + /*0x71: ADC2 Digital Channel Volume Control Register*/ + SOC_AC108_SINGLE_TLV("CH6 digital volume", ADC2_DVOL_CTRL, 0, 0xff, 0, 0, tlv_ch_digital_vol), + /*0x72: ADC3 Digital Channel Volume Control Register*/ + SOC_AC108_SINGLE_TLV("CH7 digital volume", ADC3_DVOL_CTRL, 0, 0xff, 0, 0, tlv_ch_digital_vol), + /*0x73: ADC4 Digital Channel Volume Control Register*/ + SOC_AC108_SINGLE_TLV("CH8 digital volume", ADC4_DVOL_CTRL, 0, 0xff, 0, 0, tlv_ch_digital_vol), + + /*0x90: Analog PGA1 Control Register*/ + SOC_AC108_SINGLE_TLV("ADC5 PGA gain", ANA_PGA1_CTRL, ADC1_ANALOG_PGA, 0x1f, 0, 0, tlv_adc_pga_gain), + /*0x91: Analog PGA2 Control Register*/ + SOC_AC108_SINGLE_TLV("ADC6 PGA gain", ANA_PGA2_CTRL, ADC2_ANALOG_PGA, 0x1f, 0, 0, tlv_adc_pga_gain), + /*0x92: Analog PGA3 Control Register*/ + SOC_AC108_SINGLE_TLV("ADC7 PGA gain", ANA_PGA3_CTRL, ADC3_ANALOG_PGA, 0x1f, 0, 0, tlv_adc_pga_gain), + /*0x93: Analog PGA4 Control Register*/ + SOC_AC108_SINGLE_TLV("ADC8 PGA gain", ANA_PGA4_CTRL, ADC4_ANALOG_PGA, 0x1f, 0, 0, tlv_adc_pga_gain), +}; + + +static const struct snd_soc_dapm_widget ac108_dapm_widgets[] = { + //input widgets + SND_SOC_DAPM_INPUT("MIC1P"), + SND_SOC_DAPM_INPUT("MIC1N"), + + SND_SOC_DAPM_INPUT("MIC2P"), + SND_SOC_DAPM_INPUT("MIC2N"), + + SND_SOC_DAPM_INPUT("MIC3P"), + SND_SOC_DAPM_INPUT("MIC3N"), + + SND_SOC_DAPM_INPUT("MIC4P"), + SND_SOC_DAPM_INPUT("MIC4N"), + + SND_SOC_DAPM_INPUT("DMIC1"), + SND_SOC_DAPM_INPUT("DMIC2"), + + /*0xa0: ADC1 Analog Control 1 Register*/ + /*0xa1-0xa6:use the defualt value*/ + SND_SOC_DAPM_AIF_IN("Channel 1 AAF", "Capture", 0, ANA_ADC1_CTRL1, ADC1_DSM_ENABLE, 1), + SND_SOC_DAPM_SUPPLY("Channel 1 EN", ANA_ADC1_CTRL1, ADC1_PGA_ENABLE, 1, NULL, 0), + SND_SOC_DAPM_MICBIAS("MIC1BIAS", ANA_ADC1_CTRL1, ADC1_MICBIAS_EN, 1), + + /*0xa7: ADC2 Analog Control 1 Register*/ + /*0xa8-0xad:use the defualt value*/ + SND_SOC_DAPM_AIF_IN("Channel 2 AAF", "Capture", 0, ANA_ADC2_CTRL1, ADC2_DSM_ENABLE, 1), + SND_SOC_DAPM_SUPPLY("Channel 2 EN", ANA_ADC2_CTRL1, ADC2_PGA_ENABLE, 1, NULL, 0), + SND_SOC_DAPM_MICBIAS("MIC2BIAS", ANA_ADC2_CTRL1, ADC2_MICBIAS_EN, 1), + + /*0xae: ADC3 Analog Control 1 Register*/ + /*0xaf-0xb4:use the defualt value*/ + SND_SOC_DAPM_AIF_IN("Channel 3 AAF", "Capture", 0, ANA_ADC3_CTRL1, ADC3_DSM_ENABLE, 1), + SND_SOC_DAPM_SUPPLY("Channel 3 EN", ANA_ADC3_CTRL1, ADC3_PGA_ENABLE, 1, NULL, 0), + SND_SOC_DAPM_MICBIAS("MIC3BIAS", ANA_ADC3_CTRL1, ADC3_MICBIAS_EN, 1), + + /*0xb5: ADC4 Analog Control 1 Register*/ + /*0xb6-0xbb:use the defualt value*/ + SND_SOC_DAPM_AIF_IN("Channel 4 AAF", "Capture", 0, ANA_ADC4_CTRL1, ADC4_DSM_ENABLE, 1), + SND_SOC_DAPM_SUPPLY("Channel 4 EN", ANA_ADC4_CTRL1, ADC4_PGA_ENABLE, 1, NULL, 0), + SND_SOC_DAPM_MICBIAS("MIC4BIAS", ANA_ADC4_CTRL1, ADC4_MICBIAS_EN, 1), + + + /*0x61: ADC Digital Part Enable Register*/ + SND_SOC_DAPM_SUPPLY("ADC EN", ADC_DIG_EN, 4, 1, NULL, 0), + SND_SOC_DAPM_ADC("ADC1", "Capture", ADC_DIG_EN, 0, 1), + SND_SOC_DAPM_ADC("ADC2", "Capture", ADC_DIG_EN, 1, 1), + SND_SOC_DAPM_ADC("ADC3", "Capture", ADC_DIG_EN, 2, 1), + SND_SOC_DAPM_ADC("ADC4", "Capture", ADC_DIG_EN, 3, 1), + + SND_SOC_DAPM_SUPPLY("ADC1 CLK", ANA_ADC4_CTRL7, ADC1_CLK_GATING, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC2 CLK", ANA_ADC4_CTRL7, ADC2_CLK_GATING, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC3 CLK", ANA_ADC4_CTRL7, ADC3_CLK_GATING, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC4 CLK", ANA_ADC4_CTRL7, ADC4_CLK_GATING, 1, NULL, 0), + + SND_SOC_DAPM_SUPPLY("DSM EN", ANA_ADC4_CTRL6, DSM_DEMOFF, 1, NULL, 0), + + /*0x62:Digital MIC Enable Register*/ + SND_SOC_DAPM_MICBIAS("DMIC1 enable", DMIC_EN, 0, 0), + SND_SOC_DAPM_MICBIAS("DMIC2 enable", DMIC_EN, 1, 0), +}; + +static const struct snd_soc_dapm_route ac108_dapm_routes[] = { + + { "ADC1", NULL, "Channel 1 AAF" }, + { "ADC2", NULL, "Channel 2 AAF" }, + { "ADC3", NULL, "Channel 3 AAF" }, + { "ADC4", NULL, "Channel 4 AAF" }, + + { "Channel 1 AAF", NULL, "MIC1BIAS" }, + { "Channel 2 AAF", NULL, "MIC2BIAS" }, + { "Channel 3 AAF", NULL, "MIC3BIAS" }, + { "Channel 4 AAF", NULL, "MIC4BIAS" }, + + { "MIC1BIAS", NULL, "ADC1 CLK" }, + { "MIC2BIAS", NULL, "ADC2 CLK" }, + { "MIC3BIAS", NULL, "ADC3 CLK" }, + { "MIC4BIAS", NULL, "ADC4 CLK" }, + + + { "ADC1 CLK", NULL, "DSM EN" }, + { "ADC2 CLK", NULL, "DSM EN" }, + { "ADC3 CLK", NULL, "DSM EN" }, + { "ADC4 CLK", NULL, "DSM EN" }, + + + { "DSM EN", NULL, "ADC EN" }, + + { "Channel 1 EN", NULL, "DSM EN" }, + { "Channel 2 EN", NULL, "DSM EN" }, + { "Channel 3 EN", NULL, "DSM EN" }, + { "Channel 4 EN", NULL, "DSM EN" }, + + + { "MIC1P", NULL, "Channel 1 EN" }, + { "MIC1N", NULL, "Channel 1 EN" }, + + { "MIC2P", NULL, "Channel 2 EN" }, + { "MIC2N", NULL, "Channel 2 EN" }, + + { "MIC3P", NULL, "Channel 3 EN" }, + { "MIC3N", NULL, "Channel 3 EN" }, + + { "MIC4P", NULL, "Channel 4 EN" }, + { "MIC4N", NULL, "Channel 4 EN" }, + +}; + +static int ac108_multi_write(u8 reg, u8 val, struct ac10x_priv *ac10x) { + u8 i; + for (i = 0; i < ac10x->codec_cnt; i++) { + ac10x_write(reg, val, ac10x->i2cmap[i]); + } + return 0; +} + +static int ac108_multi_update_bits(u8 reg, u8 mask, u8 val, struct ac10x_priv *ac10x) { + int r = 0; + u8 i; + + for (i = 0; i < ac10x->codec_cnt; i++) { + r |= ac10x_update_bits(reg, mask, val, ac10x->i2cmap[i]); + } + return r; +} + +static unsigned int ac108_codec_read(struct snd_soc_codec *codec, unsigned int reg) { + unsigned char val_r; + struct ac10x_priv *ac10x = dev_get_drvdata(codec->dev); + /*read one chip is fine*/ + ac10x_read(reg, &val_r, ac10x->i2cmap[_MASTER_INDEX]); + return val_r; +} + +static int ac108_codec_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int val) { + struct ac10x_priv *ac10x = dev_get_drvdata(codec->dev); + ac108_multi_write(reg, val, ac10x); + return 0; +} + +/** + * The Power management related registers are Reg01h~Reg09h + * 0x01-0x05,0x08,use the default value + * @author baozhu (17-6-21) + * + * @param ac10x + */ +static void ac108_configure_power(struct ac10x_priv *ac10x) { + /** + * 0x06:Enable Analog LDO + */ + ac108_multi_update_bits(PWR_CTRL6, 0x01 << LDO33ANA_ENABLE, 0x01 << LDO33ANA_ENABLE, ac10x); + /** + * 0x07: + * Control VREF output and micbias voltage ? + * REF faststart disable, enable Enable VREF (needed for Analog + * LDO and MICBIAS) + */ + ac108_multi_update_bits(PWR_CTRL7, 0x1f << VREF_SEL | 0x01 << VREF_FASTSTART_ENABLE | 0x01 << VREF_ENABLE, + 0x13 << VREF_SEL | 0x00 << VREF_FASTSTART_ENABLE | 0x01 << VREF_ENABLE, ac10x); + /** + * 0x09: + * Disable fast-start circuit on VREFP + * VREFP_RESCTRL=00=1 MOhm + * IGEN_TRIM=100=+25% + * Enable VREFP (needed by all audio input channels) + */ + ac108_multi_update_bits(PWR_CTRL9, 0x01 << VREFP_FASTSTART_ENABLE | 0x03 << VREFP_RESCTRL | 0x07 << IGEN_TRIM | 0x01 << VREFP_ENABLE, + 0x00 << VREFP_FASTSTART_ENABLE | 0x00 << VREFP_RESCTRL | 0x04 << IGEN_TRIM | 0x01 << VREFP_ENABLE, + ac10x); +} + +/** + * The clock management related registers are Reg20h~Reg25h + * The PLL management related registers are Reg10h~Reg18h. + * @author baozhu (17-6-20) + * + * @param ac10x + * @param rate : sample rate + * + * @return int : fail or success + */ +static int ac108_config_pll(struct ac10x_priv *ac10x, unsigned rate, unsigned lrck_ratio) { + unsigned int i = 0; + struct pll_div ac108_pll_div = { 0 }; + + if (ac10x->clk_id == SYSCLK_SRC_PLL) { + unsigned pll_src, pll_freq_in; + + if (lrck_ratio == 0) { + /* PLL clock source from MCLK */ + pll_freq_in = ac10x->sysclk; + pll_src = 0x0; + } else { + /* PLL clock source from BCLK */ + pll_freq_in = rate * lrck_ratio; + pll_src = 0x1; + } + + /* FOUT =(FIN * N) / [(M1+1) * (M2+1)*(K1+1)*(K2+1)] */ + for (i = 0; i < ARRAY_SIZE(ac108_pll_div_list); i++) { + if (ac108_pll_div_list[i].freq_in == pll_freq_in && ac108_pll_div_list[i].freq_out % rate == 0) { + ac108_pll_div = ac108_pll_div_list[i]; + dev_dbg(&ac10x->i2c[_MASTER_INDEX]->dev, "AC108 PLL freq_in match:%u, freq_out:%u\n\n", + ac108_pll_div.freq_in, ac108_pll_div.freq_out); + break; + } + } + /* 0x11,0x12,0x13,0x14: Config PLL DIV param M1/M2/N/K1/K2 */ + ac108_multi_update_bits(PLL_CTRL5, 0x1f << PLL_POSTDIV1 | 0x01 << PLL_POSTDIV2, + ac108_pll_div.k1 << PLL_POSTDIV1 | ac108_pll_div.k2 << PLL_POSTDIV2, ac10x); + ac108_multi_update_bits(PLL_CTRL4, 0xff << PLL_LOOPDIV_LSB, (unsigned char)ac108_pll_div.n << PLL_LOOPDIV_LSB, ac10x); + ac108_multi_update_bits(PLL_CTRL3, 0x03 << PLL_LOOPDIV_MSB, (ac108_pll_div.n >> 8) << PLL_LOOPDIV_MSB, ac10x); + ac108_multi_update_bits(PLL_CTRL2, 0x1f << PLL_PREDIV1 | 0x01 << PLL_PREDIV2, + ac108_pll_div.m1 << PLL_PREDIV1 | ac108_pll_div.m2 << PLL_PREDIV2, ac10x); + + /*0x18: PLL clk lock enable*/ + ac108_multi_update_bits(PLL_LOCK_CTRL, 0x1 << PLL_LOCK_EN, 0x1 << PLL_LOCK_EN, ac10x); + + /** + * 0x20: enable pll, pll source from mclk/bclk, sysclk source from pll, enable sysclk + */ + ac108_multi_update_bits(SYSCLK_CTRL, 0x01 << PLLCLK_EN | 0x03 << PLLCLK_SRC | 0x01 << SYSCLK_SRC | 0x01 << SYSCLK_EN, + 0x01 << PLLCLK_EN |pll_src<< PLLCLK_SRC | 0x01 << SYSCLK_SRC | 0x01 << SYSCLK_EN, ac10x); + ac10x->mclk = ac108_pll_div.freq_out; + } + if (ac10x->clk_id == SYSCLK_SRC_MCLK) { + /** + *0x20: sysclk source from mclk, enable sysclk + */ + ac108_multi_update_bits(SYSCLK_CTRL, 0x01 << PLLCLK_EN | 0x01 << SYSCLK_SRC | 0x01 << SYSCLK_EN, + 0x00 << PLLCLK_EN | 0x00 << SYSCLK_SRC | 0x01 << SYSCLK_EN, ac10x); + ac10x->mclk = ac10x->sysclk; + } + + return 0; +} + +/* + * support no more than 16 slots. + */ +static int ac108_multi_chips_slots(struct ac10x_priv *ac, int slots) { + int i; + + /* + * codec0 enable slots 2,3,0,1 when 1 codec + * + * codec0 enable slots 6,7,0,1 when 2 codec + * codec1 enable slots 2,3,4,5 + * + * ... + */ + for (i = 0; i < ac->codec_cnt; i++) { + /* rotate map, due to channels rotated by CPU_DAI */ + const unsigned vec_mask[] = { + 0x3 << 6 | 0x3, // slots 6,7,0,1 + 0xF << 2, // slots 2,3,4,5 + 0, + 0, + }; + const unsigned vec_maps[] = { + /* + * chip 0, + * mic 0 sample -> slot 6 + * mic 1 sample -> slot 7 + * mic 2 sample -> slot 0 + * mic 3 sample -> slot 1 + */ + 0x0 << 12 | 0x1 << 14 | 0x2 << 0 | 0x3 << 2, + /* + * chip 1, + * mic 0 sample -> slot 2 + * mic 1 sample -> slot 3 + * mic 2 sample -> slot 4 + * mic 3 sample -> slot 5 + */ + 0x0 << 4 | 0x1 << 6 | 0x2 << 8 | 0x3 << 10, + 0, + 0, + }; + unsigned vec; + + /* 0x38-0x3A I2S_TX1_CTRLx */ + if (ac->codec_cnt == 1) { + vec = 0xFUL; + } else { + vec = vec_mask[i]; + } + ac10x_write(I2S_TX1_CTRL1, slots - 1, ac->i2cmap[i]); + ac10x_write(I2S_TX1_CTRL2, (vec >> 0) & 0xFF, ac->i2cmap[i]); + ac10x_write(I2S_TX1_CTRL3, (vec >> 8) & 0xFF, ac->i2cmap[i]); + + /* 0x3C-0x3F I2S_TX1_CHMP_CTRLx */ + if (ac->codec_cnt == 1) { + vec = (0x2 << 0 | 0x3 << 2 | 0x0 << 4 | 0x1 << 6); + } else if (ac->codec_cnt == 2) { + vec = vec_maps[i]; + } + + ac10x_write(I2S_TX1_CHMP_CTRL1, (vec >> 0) & 0xFF, ac->i2cmap[i]); + ac10x_write(I2S_TX1_CHMP_CTRL2, (vec >> 8) & 0xFF, ac->i2cmap[i]); + ac10x_write(I2S_TX1_CHMP_CTRL3, (vec >> 16) & 0xFF, ac->i2cmap[i]); + ac10x_write(I2S_TX1_CHMP_CTRL4, (vec >> 24) & 0xFF, ac->i2cmap[i]); + } + return 0; +} + +static int ac108_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { + unsigned int i, channels, samp_res, rate; + struct snd_soc_codec *codec = dai->codec; + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + unsigned bclkdiv; + int ret = 0; + u8 v; + + dev_dbg(dai->dev, "%s() stream=%s play:%d capt:%d +++\n", __func__, + snd_pcm_stream_str(substream), + dai->stream_active[SNDRV_PCM_STREAM_PLAYBACK], dai->stream_active[SNDRV_PCM_STREAM_CAPTURE]); + + if (ac10x->i2c101) { + ret = ac101_hw_params(substream, params, dai); + if (ret > 0) { + dev_dbg(dai->dev, "%s() L%d returned\n", __func__, __LINE__); + /* not configure hw_param twice */ + return 0; + } + } + + if ((substream->stream == SNDRV_PCM_STREAM_CAPTURE && dai->stream_active[SNDRV_PCM_STREAM_PLAYBACK]) + || (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && dai->stream_active[SNDRV_PCM_STREAM_CAPTURE])) { + /* not configure hw_param twice */ + /* return 0; */ + } + + channels = params_channels(params); + + /* Master mode, to clear cpu_dai fifos, output bclk without lrck */ + ac10x_read(I2S_CTRL, &v, ac10x->i2cmap[_MASTER_INDEX]); + if (v & (0x01 << BCLK_IOEN)) { + ac10x_update_bits(I2S_CTRL, 0x1 << LRCK_IOEN, 0x0 << LRCK_IOEN, ac10x->i2cmap[_MASTER_INDEX]); + } + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + samp_res = 0; + break; + case SNDRV_PCM_FORMAT_S16_LE: + samp_res = 2; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + samp_res = 3; + break; + case SNDRV_PCM_FORMAT_S24_LE: + samp_res = 4; + break; + case SNDRV_PCM_FORMAT_S32_LE: + samp_res = 6; + break; + default: + pr_err("AC108 don't supported the sample resolution: %u\n", params_format(params)); + return -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(ac108_sample_rate); i++) { + if (ac108_sample_rate[i].real_val == params_rate(params) / (ac10x->data_protocol + 1UL)) { + rate = i; + break; + } + } + if (i >= ARRAY_SIZE(ac108_sample_rate)) { + return -EINVAL; + } + + if (channels == 8 && ac108_sample_rate[rate].real_val == 96000) { + /* 24.576M bit clock is not support by ac108 */ + return -EINVAL; + } + + dev_dbg(dai->dev, "rate: %d , channels: %d , samp_res: %d", + ac108_sample_rate[rate].real_val, + channels, + ac108_samp_res[samp_res].real_val); + + /** + * 0x33: + * The 8-Low bit of LRCK period value. It is used to program + * the number of BCLKs per channel of sample frame. This value + * is interpreted as follow: + * The 8-Low bit of LRCK period value. It is used to program + * the number of BCLKs per channel of sample frame. This value + * is interpreted as follow: PCM mode: Number of BCLKs within + * (Left + Right) channel width I2S / Left-Justified / + * Right-Justified mode: Number of BCLKs within each individual + * channel width (Left or Right) N+1 + * For example: + * n = 7: 8 BCLK width + * … + * n = 1023: 1024 BCLKs width + * 0X32[0:1]: + * The 2-High bit of LRCK period value. + */ + if (ac10x->i2s_mode != PCM_FORMAT) { + if (ac10x->data_protocol) { + ac108_multi_write(I2S_LRCK_CTRL2, ac108_samp_res[samp_res].real_val - 1, ac10x); + /*encoding mode, the max LRCK period value < 32,so the 2-High bit is zero*/ + ac108_multi_update_bits(I2S_LRCK_CTRL1, 0x03 << 0, 0x00, ac10x); + } else { + /*TDM mode or normal mode*/ + ac108_multi_update_bits(I2S_LRCK_CTRL1, 0x03 << 0, 0x00, ac10x); + } + + } else { + unsigned div; + + /*TDM mode or normal mode*/ + div = ac108_samp_res[samp_res].real_val * channels - 1; + ac108_multi_write(I2S_LRCK_CTRL2, (div & 0xFF), ac10x); + ac108_multi_update_bits(I2S_LRCK_CTRL1, 0x03 << 0, (div >> 8) << 0, ac10x); + } + + /** + * 0x35: + * TX Encoding mode will add 4bits to mark channel number + * TODO: need a chat to explain this + */ + ac108_multi_update_bits(I2S_FMT_CTRL2, 0x07 << SAMPLE_RESOLUTION | 0x07 << SLOT_WIDTH_SEL, + ac108_samp_res[samp_res].reg_val << SAMPLE_RESOLUTION + | ac108_samp_res[samp_res].reg_val << SLOT_WIDTH_SEL, ac10x); + + /** + * 0x60: + * ADC Sample Rate synchronised with I2S1 clock zone + */ + ac108_multi_update_bits(ADC_SPRC, 0x0f << ADC_FS_I2S1, ac108_sample_rate[rate].reg_val << ADC_FS_I2S1, ac10x); + ac108_multi_write(HPF_EN, 0x0F, ac10x); + + if (ac10x->i2c101 && _MASTER_MULTI_CODEC == _MASTER_AC101) { + ac108_config_pll(ac10x, ac108_sample_rate[rate].real_val, ac108_samp_res[samp_res].real_val * channels); + } else { + ac108_config_pll(ac10x, ac108_sample_rate[rate].real_val, 0); + } + + /* + * master mode only + */ + bclkdiv = ac10x->mclk / (ac108_sample_rate[rate].real_val * channels * ac108_samp_res[samp_res].real_val); + for (i = 0; i < ARRAY_SIZE(ac108_bclkdivs) - 1; i++) { + if (ac108_bclkdivs[i] >= bclkdiv) { + break; + } + } + ac108_multi_update_bits(I2S_BCLK_CTRL, 0x0F << BCLKDIV, i << BCLKDIV, ac10x); + + /* + * slots allocation for each chip + */ + ac108_multi_chips_slots(ac10x, channels); + + /*0x21: Module clock enable*/ + ac108_multi_write(MOD_CLK_EN, 1 << I2S | 1 << ADC_DIGITAL | 1 << MIC_OFFSET_CALIBRATION | 1 << ADC_ANALOG, ac10x); + /*0x22: Module reset de-asserted*/ + ac108_multi_write(MOD_RST_CTRL, 1 << I2S | 1 << ADC_DIGITAL | 1 << MIC_OFFSET_CALIBRATION | 1 << ADC_ANALOG, ac10x); + + + dev_dbg(dai->dev, "%s() stream=%s ---\n", __func__, + snd_pcm_stream_str(substream)); + + return 0; +} + +static int ac108_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir) { + + struct ac10x_priv *ac10x = snd_soc_dai_get_drvdata(dai); + + if (freq != 24000000 || clk_id != SYSCLK_SRC_PLL) + dev_warn(dai->dev, "ac108_set_sysclk freq = %d clk = %d\n", freq, clk_id); + + freq = 24000000; + clk_id = SYSCLK_SRC_PLL; + + pr_info("%s :%d\n", __FUNCTION__, freq); + + switch (clk_id) { + case SYSCLK_SRC_MCLK: + ac108_multi_update_bits(SYSCLK_CTRL, 0x1 << SYSCLK_SRC, SYSCLK_SRC_MCLK << SYSCLK_SRC, ac10x); + break; + case SYSCLK_SRC_PLL: + ac108_multi_update_bits(SYSCLK_CTRL, 0x1 << SYSCLK_SRC, SYSCLK_SRC_PLL << SYSCLK_SRC, ac10x); + break; + default: + return -EINVAL; + } + ac10x->sysclk = freq; + ac10x->clk_id = clk_id; + + return 0; +} + +/** + * The i2s format management related registers are Reg + * 30h~Reg36h + * 33h,35h will be set in ac108_hw_params, It's BCLK width and + * Sample Resolution. + * @author baozhu (17-6-20) + * + * @param dai + * @param fmt + * + * @return int + */ +static int ac108_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) { + unsigned char tx_offset, lrck_polarity, brck_polarity; + struct ac10x_priv *ac10x = dev_get_drvdata(dai->dev); + + dev_dbg(dai->dev, "%s\n", __FUNCTION__); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: /*AC108 Master*/ + if (! ac10x->i2c101 || _MASTER_MULTI_CODEC == _MASTER_AC108) { + dev_dbg(dai->dev, "AC108 set to work as Master\n"); + /** + * 0x30:chip is master mode ,BCLK & LRCK output + */ + ac108_multi_update_bits(I2S_CTRL, 0x03 << LRCK_IOEN | 0x03 << SDO1_EN | 0x1 << TXEN | 0x1 << GEN, + 0x00 << LRCK_IOEN | 0x03 << SDO1_EN | 0x1 << TXEN | 0x1 << GEN, ac10x); + /* multi_chips: only one chip set as Master, and the others also need to set as Slave */ + ac10x_update_bits(I2S_CTRL, 0x3 << LRCK_IOEN, 0x01 << BCLK_IOEN, ac10x->i2cmap[_MASTER_INDEX]); + break; + } else { + /* TODO: Both cpu_dai and codec_dai(AC108) be set as slave in DTS */ + dev_dbg(dai->dev, "used as slave when AC101 is master\n"); + } + fallthrough; + case SND_SOC_DAIFMT_CBS_CFS: /*AC108 Slave*/ + dev_dbg(dai->dev, "AC108 set to work as Slave\n"); + /** + * 0x30:chip is slave mode, BCLK & LRCK input,enable SDO1_EN and + * SDO2_EN, Transmitter Block Enable, Globe Enable + */ + ac108_multi_update_bits(I2S_CTRL, 0x03 << LRCK_IOEN | 0x03 << SDO1_EN | 0x1 << TXEN | 0x1 << GEN, + 0x00 << LRCK_IOEN | 0x03 << SDO1_EN | 0x0 << TXEN | 0x0 << GEN, ac10x); + break; + default: + pr_err("AC108 Master/Slave mode config error:%u\n\n", (fmt & SND_SOC_DAIFMT_MASTER_MASK) >> 12); + return -EINVAL; + } + + /*AC108 config I2S/LJ/RJ/PCM format*/ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + dev_dbg(dai->dev, "AC108 config I2S format\n"); + ac10x->i2s_mode = LEFT_JUSTIFIED_FORMAT; + tx_offset = 1; + break; + case SND_SOC_DAIFMT_RIGHT_J: + dev_dbg(dai->dev, "AC108 config RIGHT-JUSTIFIED format\n"); + ac10x->i2s_mode = RIGHT_JUSTIFIED_FORMAT; + tx_offset = 0; + break; + case SND_SOC_DAIFMT_LEFT_J: + dev_dbg(dai->dev, "AC108 config LEFT-JUSTIFIED format\n"); + ac10x->i2s_mode = LEFT_JUSTIFIED_FORMAT; + tx_offset = 0; + break; + case SND_SOC_DAIFMT_DSP_A: + dev_dbg(dai->dev, "AC108 config PCM-A format\n"); + ac10x->i2s_mode = PCM_FORMAT; + tx_offset = 1; + break; + case SND_SOC_DAIFMT_DSP_B: + dev_dbg(dai->dev, "AC108 config PCM-B format\n"); + ac10x->i2s_mode = PCM_FORMAT; + tx_offset = 0; + break; + default: + pr_err("AC108 I2S format config error:%u\n\n", fmt & SND_SOC_DAIFMT_FORMAT_MASK); + return -EINVAL; + } + + /*AC108 config BCLK&LRCK polarity*/ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + dev_dbg(dai->dev, "AC108 config BCLK&LRCK polarity: BCLK_normal,LRCK_normal\n"); + brck_polarity = BCLK_NORMAL_DRIVE_N_SAMPLE_P; + lrck_polarity = LRCK_LEFT_HIGH_RIGHT_LOW; + break; + case SND_SOC_DAIFMT_NB_IF: + dev_dbg(dai->dev, "AC108 config BCLK&LRCK polarity: BCLK_normal,LRCK_invert\n"); + brck_polarity = BCLK_NORMAL_DRIVE_N_SAMPLE_P; + lrck_polarity = LRCK_LEFT_LOW_RIGHT_HIGH; + break; + case SND_SOC_DAIFMT_IB_NF: + dev_dbg(dai->dev, "AC108 config BCLK&LRCK polarity: BCLK_invert,LRCK_normal\n"); + brck_polarity = BCLK_INVERT_DRIVE_P_SAMPLE_N; + lrck_polarity = LRCK_LEFT_HIGH_RIGHT_LOW; + break; + case SND_SOC_DAIFMT_IB_IF: + dev_dbg(dai->dev, "AC108 config BCLK&LRCK polarity: BCLK_invert,LRCK_invert\n"); + brck_polarity = BCLK_INVERT_DRIVE_P_SAMPLE_N; + lrck_polarity = LRCK_LEFT_LOW_RIGHT_HIGH; + break; + default: + pr_err("AC108 config BCLK/LRCLK polarity error:%u\n\n", (fmt & SND_SOC_DAIFMT_INV_MASK) >> 8); + return -EINVAL; + } + + ac108_configure_power(ac10x); + + /** + *0x31: 0: normal mode, negative edge drive and positive edge sample + 1: invert mode, positive edge drive and negative edge sample + */ + ac108_multi_update_bits(I2S_BCLK_CTRL, 0x01 << BCLK_POLARITY, brck_polarity << BCLK_POLARITY, ac10x); + /** + * 0x32: same as 0x31 + */ + ac108_multi_update_bits(I2S_LRCK_CTRL1, 0x01 << LRCK_POLARITY, lrck_polarity << LRCK_POLARITY, ac10x); + /** + * 0x34:Encoding Mode Selection,Mode + * Selection,data is offset by 1 BCLKs to LRCK + * normal mode for the last half cycle of BCLK in the slot ? + * turn to hi-z state (TDM) when not transferring slot ? + */ + ac108_multi_update_bits(I2S_FMT_CTRL1, 0x01 << ENCD_SEL | 0x03 << MODE_SEL | 0x01 << TX2_OFFSET | + 0x01 << TX1_OFFSET | 0x01 << TX_SLOT_HIZ | 0x01 << TX_STATE, + ac10x->data_protocol << ENCD_SEL | + ac10x->i2s_mode << MODE_SEL | + tx_offset << TX2_OFFSET | + tx_offset << TX1_OFFSET | + 0x00 << TX_SLOT_HIZ | + 0x01 << TX_STATE, ac10x); + + /** + * 0x60: + * MSB / LSB First Select: This driver only support MSB First Select . + * OUT2_MUTE,OUT1_MUTE shoule be set in widget. + * LRCK = 1 BCLK width + * Linear PCM + * + * TODO:pcm mode, bit[0:1] and bit[2] is special + */ + ac108_multi_update_bits(I2S_FMT_CTRL3, 0x01 << TX_MLS | 0x03 << SEXT | 0x01 << LRCK_WIDTH | 0x03 << TX_PDM, + 0x00 << TX_MLS | 0x03 << SEXT | 0x00 << LRCK_WIDTH | 0x00 << TX_PDM, ac10x); + + ac108_multi_write(HPF_EN, 0x00, ac10x); + + if (ac10x->i2c101) { + return ac101_set_dai_fmt(dai, fmt); + } + return 0; +} + +/* + * due to miss channels order in cpu_dai, we meed defer the clock starting. + */ +static int ac108_set_clock(int y_start_n_stop, struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) { + u8 reg; + int ret = 0; + + dev_dbg(ac10x->codec->dev, "%s() L%d cmd:%d\n", __func__, __LINE__, y_start_n_stop); + + /* spin_lock move to machine trigger */ + + if (y_start_n_stop && ac10x->i2c101 && _MASTER_MULTI_CODEC == _MASTER_AC101) { + ac101_trigger(substream, cmd, dai); + } + if (y_start_n_stop && ac10x->sysclk_en == 0) { + /* enable lrck clock */ + ac10x_read(I2S_CTRL, ®, ac10x->i2cmap[_MASTER_INDEX]); + if (reg & (0x01 << BCLK_IOEN)) { + ret = ret || ac10x_update_bits(I2S_CTRL, 0x03 << LRCK_IOEN, 0x03 << LRCK_IOEN, ac10x->i2cmap[_MASTER_INDEX]); + } + + /*0x10: PLL Common voltage enable, PLL enable */ + ret = ret || ac108_multi_update_bits(PLL_CTRL1, 0x01 << PLL_EN | 0x01 << PLL_COM_EN, + 0x01 << PLL_EN | 0x01 << PLL_COM_EN, ac10x); + /* enable global clock */ + ret = ret || ac108_multi_update_bits(I2S_CTRL, 0x1 << TXEN | 0x1 << GEN, 0x1 << TXEN | 0x1 << GEN, ac10x); + + ac10x->sysclk_en = 1UL; + } else if (!y_start_n_stop && ac10x->sysclk_en != 0) { + /* disable global clock */ + ret = ret || ac108_multi_update_bits(I2S_CTRL, 0x1 << TXEN | 0x1 << GEN, 0x0 << TXEN | 0x0 << GEN, ac10x); + + /*0x10: PLL Common voltage disable, PLL disable */ + ret = ret || ac108_multi_update_bits(PLL_CTRL1, 0x01 << PLL_EN | 0x01 << PLL_COM_EN, + 0x00 << PLL_EN | 0x00 << PLL_COM_EN, ac10x); + + /* disable lrck clock if it's enabled */ + ac10x_read(I2S_CTRL, ®, ac10x->i2cmap[_MASTER_INDEX]); + if (reg & (0x01 << LRCK_IOEN)) { + ret = ret || ac10x_update_bits(I2S_CTRL, 0x03 << LRCK_IOEN, 0x01 << BCLK_IOEN, ac10x->i2cmap[_MASTER_INDEX]); + } + if (!ret) { + ac10x->sysclk_en = 0UL; + } + } + + return ret; +} + +static int ac108_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + dev_dbg(dai->dev, "%s() stream=%s\n", + __func__, + snd_pcm_stream_str(substream)); + + return 0; +} + +static int ac108_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + unsigned long flags; + int ret = 0; + u8 r; + + dev_dbg(dai->dev, "%s() stream=%s cmd=%d\n", + __FUNCTION__, + snd_pcm_stream_str(substream), + cmd); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + spin_lock_irqsave(&ac10x->lock, flags); + /* disable global clock if lrck disabled */ + ac10x_read(I2S_CTRL, &r, ac10x->i2cmap[_MASTER_INDEX]); + if ((r & (0x01 << BCLK_IOEN)) && (r & (0x01 << LRCK_IOEN)) == 0) { + /* disable global clock */ + ac108_multi_update_bits(I2S_CTRL, 0x1 << TXEN | 0x1 << GEN, 0x0 << TXEN | 0x0 << GEN, ac10x); + } + spin_unlock_irqrestore(&ac10x->lock, flags); + + /* delayed clock starting, move to machine trigger() */ + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (ac10x->i2c101 && _MASTER_MULTI_CODEC == _MASTER_AC101) { + ac101_trigger(substream, cmd, dai); + } + break; + default: + ret = -EINVAL; + } + + dev_dbg(dai->dev, "%s() stream=%s cmd=%d; finished %d\n", + __FUNCTION__, + snd_pcm_stream_str(substream), + cmd, ret); + return ret; +} + +int ac108_audio_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai +) { + struct snd_soc_codec *codec = dai->codec; + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + + if (ac10x->i2c101) { + return ac101_audio_startup(substream, dai); + } + return 0; +} + +void ac108_aif_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai +) { + struct snd_soc_codec *codec = dai->codec; + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + /*0x21: Module clock disable */ + ac108_multi_write(MOD_CLK_EN, 0x0, ac10x); + /*0x22: Module reset asserted */ + ac108_multi_write(MOD_RST_CTRL, 0x0, ac10x); + } + + if (ac10x->i2c101) { + ac101_aif_shutdown(substream, dai); + } +} + +int ac108_aif_mute(struct snd_soc_dai *dai, int mute, int direction) { + struct snd_soc_codec *codec = dai->codec; + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + + if (ac10x->i2c101) { + return ac101_aif_mute(dai, mute); + } + return 0; +} + +static const struct snd_soc_dai_ops ac108_dai_ops = { + .startup = ac108_audio_startup, + .shutdown = ac108_aif_shutdown, + + /*DAI clocking configuration*/ + .set_sysclk = ac108_set_sysclk, + + /*ALSA PCM audio operations*/ + .hw_params = ac108_hw_params, + .prepare = ac108_prepare, + .trigger = ac108_trigger, + .mute_stream = ac108_aif_mute, + + /*DAI format configuration*/ + .set_fmt = ac108_set_fmt, + + // .hw_free = ac108_hw_free, + .no_capture_mute = 1, +}; + +static struct snd_soc_dai_driver ac108_dai0 = { + .name = "ac10x-codec0", + #if _USE_CAPTURE + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = AC108_CHANNELS_MAX, + .rates = AC108_RATES, + .formats = AC108_FORMATS, + }, + #endif + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = AC108_CHANNELS_MAX, + .rates = AC108_RATES, + .formats = AC108_FORMATS, + }, + .ops = &ac108_dai_ops, +}; + +static struct snd_soc_dai_driver ac108_dai1 = { + .name = "ac10x-codec1", + #if _USE_CAPTURE + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = AC108_CHANNELS_MAX, + .rates = AC108_RATES, + .formats = AC108_FORMATS, + }, + #endif + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = AC108_CHANNELS_MAX, + .rates = AC108_RATES, + .formats = AC108_FORMATS, + }, + .ops = &ac108_dai_ops, +}; + +static struct snd_soc_dai_driver *ac108_dai[] = { + &ac108_dai0, + &ac108_dai1, +}; + +static int ac108_add_widgets(struct snd_soc_codec *codec) { + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec); + const struct snd_kcontrol_new* snd_kcntl = ac108_snd_controls; + int ctrl_cnt = ARRAY_SIZE(ac108_snd_controls); + + /* only register controls correspond to exist chips */ + if (ac10x->tdm_chips_cnt >= 2) { + snd_kcntl = ac108tdm_snd_controls; + ctrl_cnt = ARRAY_SIZE(ac108tdm_snd_controls); + } + snd_soc_add_codec_controls(codec, snd_kcntl, ctrl_cnt); + + snd_soc_dapm_new_controls(dapm, ac108_dapm_widgets,ARRAY_SIZE(ac108_dapm_widgets)); + snd_soc_dapm_add_routes(dapm, ac108_dapm_routes, ARRAY_SIZE(ac108_dapm_routes)); + + return 0; +} + +static int ac108_codec_probe(struct snd_soc_codec *codec) { + spin_lock_init(&ac10x->lock); + + ac10x->codec = codec; + dev_set_drvdata(codec->dev, ac10x); + ac108_add_widgets(codec); + + if (ac10x->i2c101) { + ac101_codec_probe(codec); + } + + return 0; +} + +static int ac108_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + + dev_dbg(codec->dev, "AC108 level:%d\n", level); + + switch (level) { + case SND_SOC_BIAS_ON: + ac108_multi_update_bits(ANA_ADC1_CTRL1, 0x01 << ADC1_MICBIAS_EN, 0x01 << ADC1_MICBIAS_EN, ac10x); + ac108_multi_update_bits(ANA_ADC2_CTRL1, 0x01 << ADC2_MICBIAS_EN, 0x01 << ADC2_MICBIAS_EN, ac10x); + ac108_multi_update_bits(ANA_ADC3_CTRL1, 0x01 << ADC3_MICBIAS_EN, 0x01 << ADC3_MICBIAS_EN, ac10x); + ac108_multi_update_bits(ANA_ADC4_CTRL1, 0x01 << ADC4_MICBIAS_EN, 0x01 << ADC4_MICBIAS_EN, ac10x); + break; + + case SND_SOC_BIAS_PREPARE: + /* Put the MICBIASes into regulating mode */ + break; + + case SND_SOC_BIAS_STANDBY: + break; + + case SND_SOC_BIAS_OFF: + ac108_multi_update_bits(ANA_ADC1_CTRL1, 0x01 << ADC1_MICBIAS_EN, 0x00 << ADC1_MICBIAS_EN, ac10x); + ac108_multi_update_bits(ANA_ADC2_CTRL1, 0x01 << ADC2_MICBIAS_EN, 0x00 << ADC2_MICBIAS_EN, ac10x); + ac108_multi_update_bits(ANA_ADC3_CTRL1, 0x01 << ADC3_MICBIAS_EN, 0x00 << ADC3_MICBIAS_EN, ac10x); + ac108_multi_update_bits(ANA_ADC4_CTRL1, 0x01 << ADC4_MICBIAS_EN, 0x00 << ADC4_MICBIAS_EN, ac10x); + break; + } + + if (ac10x->i2c101) { + ac101_set_bias_level(codec, level); + } + return 0; +} + +int ac108_codec_remove(struct snd_soc_codec *codec) { + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + + if (! ac10x->i2c101) { + return 0; + } + return ac101_codec_remove(codec); +} +#if __NO_SND_SOC_CODEC_DRV +void ac108_codec_remove_void(struct snd_soc_codec *codec) { + ac108_codec_remove(codec); +} +#define ac108_codec_remove ac108_codec_remove_void +#endif + +int ac108_codec_suspend(struct snd_soc_codec *codec) { + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + int i; + + for (i = 0; i < ac10x->codec_cnt; i++) { + regcache_cache_only(ac10x->i2cmap[i], true); + } + + if (! ac10x->i2c101) { + return 0; + } + return ac101_codec_suspend(codec); +} + +int ac108_codec_resume(struct snd_soc_codec *codec) { + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + int i, ret; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ac10x->codec_cnt; i++) { + regcache_cache_only(ac10x->i2cmap[i], false); + ret = regcache_sync(ac10x->i2cmap[i]); + if (ret != 0) { + dev_err(codec->dev, "Failed to sync i2cmap%d register cache: %d\n", i, ret); + regcache_cache_only(ac10x->i2cmap[i], true); + } + } + + if (! ac10x->i2c101) { + return 0; + } + return ac101_codec_resume(codec); +} + +static struct snd_soc_codec_driver ac10x_soc_codec_driver = { + .probe = ac108_codec_probe, + .remove = ac108_codec_remove, + .suspend = ac108_codec_suspend, + .resume = ac108_codec_resume, + .set_bias_level = ac108_set_bias_level, + .read = ac108_codec_read, + .write = ac108_codec_write, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,17,0) + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +#endif +}; + +static ssize_t ac108_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { + int val = 0, flag = 0; + u8 i = 0, reg, num, value_w, value_r[4]; + + val = simple_strtol(buf, NULL, 16); + flag = (val >> 16) & 0xF; + + if (flag) { + reg = (val >> 8) & 0xFF; + value_w = val & 0xFF; + ac108_multi_write(reg, value_w, ac10x); + printk("Write 0x%02x to REG:0x%02x\n", value_w, reg); + } else { + int k; + + reg = (val >> 8) & 0xFF; + num = val & 0xff; + printk("\nRead: start REG:0x%02x,count:0x%02x\n", reg, num); + + for (k = 0; k < ac10x->codec_cnt; k++) { + regcache_cache_bypass(ac10x->i2cmap[k], true); + } + do { + + memset(value_r, 0, sizeof value_r); + + for (k = 0; k < ac10x->codec_cnt; k++) { + ac10x_read(reg, &value_r[k], ac10x->i2cmap[k]); + } + if (ac10x->codec_cnt >= 2) { + printk("REG[0x%02x]: 0x%02x 0x%02x", reg, value_r[0], value_r[1]); + } else { + printk("REG[0x%02x]: 0x%02x", reg, value_r[0]); + } + reg++; + + if ((++i == num) || (i % 4 == 0)) { + printk("\n"); + } + } while (i < num); + for (k = 0; k < ac10x->codec_cnt; k++) { + regcache_cache_bypass(ac10x->i2cmap[k], false); + } + } + + return count; +} + +static ssize_t ac108_show(struct device *dev, struct device_attribute *attr, char *buf) { +#if 1 + printk("echo flag|reg|val > ac108\n"); + printk("eg read star addres=0x06,count 0x10:echo 0610 >ac108\n"); + printk("eg write value:0xfe to address:0x06 :echo 106fe > ac108\n"); + return 0; +#else + return snprintf(buf, PAGE_SIZE,"echo flag|reg|val > ac108\n" + "eg read star addres=0x06,count 0x10:echo 0610 >ac108\n" + "eg write value:0xfe to address:0x06 :echo 106fe > ac108\n"); +#endif +} + +static DEVICE_ATTR(ac108, 0644, ac108_show, ac108_store); +static struct attribute *ac108_debug_attrs[] = { + &dev_attr_ac108.attr, + NULL, +}; +static struct attribute_group ac108_debug_attr_group = { + .name = "ac108_debug", + .attrs = ac108_debug_attrs, +}; + +static const struct regmap_config ac108_regmap = { + .reg_bits = 8, + .val_bits = 8, + .reg_stride = 1, + .max_register = 0xDF, + .cache_type = REGCACHE_FLAT, +}; +static int ac108_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *i2c_id) { + struct device_node *np = i2c->dev.of_node; + unsigned int val = 0; + int ret = 0, index; + + if (ac10x == NULL) { + ac10x = kzalloc(sizeof(struct ac10x_priv), GFP_KERNEL); + if (ac10x == NULL) { + dev_err(&i2c->dev, "Unable to allocate ac10x private data\n"); + return -ENOMEM; + } + } + + index = (int)i2c_id->driver_data; + if (index == AC101_I2C_ID) { + ac10x->i2c101 = i2c; + i2c_set_clientdata(i2c, ac10x); + ret = ac101_probe(i2c, i2c_id); + if (ret) { + ac10x->i2c101 = NULL; + return ret; + } + goto __ret; + } + + ret = of_property_read_u32(np, "data-protocol", &val); + if (ret) { + pr_err("Please set data-protocol.\n"); + return -EINVAL; + } + ac10x->data_protocol = val; + + if (of_property_read_u32(np, "tdm-chips-count", &val)) val = 1; + ac10x->tdm_chips_cnt = val; + + pr_info(" ac10x i2c_id number: %d\n", index); + pr_info(" ac10x data protocol: %d\n", ac10x->data_protocol); + + ac10x->i2c[index] = i2c; + ac10x->i2cmap[index] = devm_regmap_init_i2c(i2c, &ac108_regmap); + if (IS_ERR(ac10x->i2cmap[index])) { + ret = PTR_ERR(ac10x->i2cmap[index]); + dev_err(&i2c->dev, "Fail to initialize i2cmap%d I/O: %d\n", index, ret); + return ret; + } + + /* + * Writing this register with 0x12 + * will resets all register to their default state. + */ + regcache_cache_only(ac10x->i2cmap[index], false); + ret = regmap_write(ac10x->i2cmap[index], CHIP_RST, CHIP_RST_VAL); + msleep(1); + + /* sync regcache for FLAT type */ + ac10x_fill_regcache(&i2c->dev, ac10x->i2cmap[index]); + + ac10x->codec_cnt++; + pr_info(" ac10x codec count : %d\n", ac10x->codec_cnt); + + ret = sysfs_create_group(&i2c->dev.kobj, &ac108_debug_attr_group); + if (ret) { + pr_err("failed to create attr group\n"); + } + +__ret: + /* It's time to bind codec to i2c[_MASTER_INDEX] when all i2c are ready */ + if ((ac10x->codec_cnt != 0 && ac10x->tdm_chips_cnt < 2) + || (ac10x->i2c[0] && ac10x->i2c[1] && ac10x->i2c101)) { + seeed_voice_card_register_set_clock(SNDRV_PCM_STREAM_CAPTURE, ac108_set_clock); + /* no playback stream */ + if (! ac10x->i2c101) { + memset(&ac108_dai[_MASTER_INDEX]->playback, '\0', sizeof ac108_dai[_MASTER_INDEX]->playback); + } + ret = snd_soc_register_codec(&ac10x->i2c[_MASTER_INDEX]->dev, &ac10x_soc_codec_driver, + ac108_dai[_MASTER_INDEX], 1); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to register ac10x codec: %d\n", ret); + } + } + return ret; +} + +static int ac108_i2c_remove(struct i2c_client *i2c) { + if (ac10x->codec != NULL) { + snd_soc_unregister_codec(&ac10x->i2c[_MASTER_INDEX]->dev); + ac10x->codec = NULL; + } + if (i2c == ac10x->i2c101) { + ac101_remove(ac10x->i2c101); + ac10x->i2c101 = NULL; + goto __ret; + } + + if (i2c == ac10x->i2c[0]) { + ac10x->i2c[0] = NULL; + } + if (i2c == ac10x->i2c[1]) { + ac10x->i2c[1] = NULL; + } + + sysfs_remove_group(&i2c->dev.kobj, &ac108_debug_attr_group); + +__ret: + if (!ac10x->i2c[0] && !ac10x->i2c[1] && !ac10x->i2c101) { + kfree(ac10x); + ac10x = NULL; + } + return 0; +} + +static const struct i2c_device_id ac108_i2c_id[] = { + { "ac108_0", 0 }, + { "ac108_1", 1 }, + { "ac108_2", 2 }, + { "ac108_3", 3 }, + { "ac101", AC101_I2C_ID }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ac108_i2c_id); + +static const struct of_device_id ac108_of_match[] = { + { .compatible = "x-power,ac108_0", }, + { .compatible = "x-power,ac108_1", }, + { .compatible = "x-power,ac108_2", }, + { .compatible = "x-power,ac108_3", }, + { .compatible = "x-power,ac101", }, + { } +}; +MODULE_DEVICE_TABLE(of, ac108_of_match); + +static struct i2c_driver ac108_i2c_driver = { + .driver = { + .name = "ac10x-codec", + .of_match_table = ac108_of_match, + }, + .probe = ac108_i2c_probe, + .remove = ac108_i2c_remove, + .id_table = ac108_i2c_id, +}; + +module_i2c_driver(ac108_i2c_driver); + +MODULE_DESCRIPTION("ASoC AC108 driver"); +MODULE_AUTHOR("Baozhu Zuo"); +MODULE_LICENSE("GPL"); diff --git a/seeed-voicecard/ac108.h b/seeed-voicecard/ac108.h new file mode 100755 index 0000000..8ab57bb --- /dev/null +++ b/seeed-voicecard/ac108.h @@ -0,0 +1,774 @@ +/* + * ac108.h -- ac108 ALSA Soc Audio driver + * + * Author: panjunwen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef _AC108_H +#define _AC108_H + + +/*** AC108 Codec Register Define***/ + +//Chip Reset +#define CHIP_RST 0x00 +#define CHIP_RST_VAL 0x12 + +//Power Control +#define PWR_CTRL1 0x01 +#define PWR_CTRL2 0x02 +#define PWR_CTRL3 0x03 +#define PWR_CTRL4 0x04 +#define PWR_CTRL5 0x05 +#define PWR_CTRL6 0x06 +#define PWR_CTRL7 0x07 +#define PWR_CTRL8 0x08 +#define PWR_CTRL9 0x09 + +//PLL Configure Control +#define PLL_CTRL1 0x10 +#define PLL_CTRL2 0x11 +#define PLL_CTRL3 0x12 +#define PLL_CTRL4 0x13 +#define PLL_CTRL5 0x14 +#define PLL_CTRL6 0x16 +#define PLL_CTRL7 0x17 +#define PLL_LOCK_CTRL 0x18 + +//System Clock Control +#define SYSCLK_CTRL 0x20 +#define MOD_CLK_EN 0x21 +#define MOD_RST_CTRL 0x22 +#define DSM_CLK_CTRL 0x25 + +//I2S Common Control +#define I2S_CTRL 0x30 +#define I2S_BCLK_CTRL 0x31 +#define I2S_LRCK_CTRL1 0x32 +#define I2S_LRCK_CTRL2 0x33 +#define I2S_FMT_CTRL1 0x34 +#define I2S_FMT_CTRL2 0x35 +#define I2S_FMT_CTRL3 0x36 + +//I2S TX1 Control +#define I2S_TX1_CTRL1 0x38 +#define I2S_TX1_CTRL2 0x39 +#define I2S_TX1_CTRL3 0x3A +#define I2S_TX1_CHMP_CTRL1 0x3C +#define I2S_TX1_CHMP_CTRL2 0x3D +#define I2S_TX1_CHMP_CTRL3 0x3E +#define I2S_TX1_CHMP_CTRL4 0x3F + +//I2S TX2 Control +#define I2S_TX2_CTRL1 0x40 +#define I2S_TX2_CTRL2 0x41 +#define I2S_TX2_CTRL3 0x42 +#define I2S_TX2_CHMP_CTRL1 0x44 +#define I2S_TX2_CHMP_CTRL2 0x45 +#define I2S_TX2_CHMP_CTRL3 0x46 +#define I2S_TX2_CHMP_CTRL4 0x47 + +//I2S RX1 Control +#define I2S_RX1_CTRL1 0x50 +#define I2S_RX1_CHMP_CTRL1 0x54 +#define I2S_RX1_CHMP_CTRL2 0x55 +#define I2S_RX1_CHMP_CTRL3 0x56 +#define I2S_RX1_CHMP_CTRL4 0x57 + +//I2S Loopback Debug +#define I2S_LPB_DEBUG 0x58 + +//ADC Common Control +#define ADC_SPRC 0x60 +#define ADC_DIG_EN 0x61 +#define DMIC_EN 0x62 +#define ADC_DSR 0x63 +#define ADC_FIR 0x64 +#define ADC_DDT_CTRL 0x65 + +//HPF Control +#define HPF_EN 0x66 +#define HPF_COEF_REGH1 0x67 +#define HPF_COEF_REGH2 0x68 +#define HPF_COEF_REGL1 0x69 +#define HPF_COEF_REGL2 0x6A +#define HPF_GAIN_REGH1 0x6B +#define HPF_GAIN_REGH2 0x6C +#define HPF_GAIN_REGL1 0x6D +#define HPF_GAIN_REGL2 0x6E + +//ADC Digital Channel Volume Control +#define ADC1_DVOL_CTRL 0x70 +#define ADC2_DVOL_CTRL 0x71 +#define ADC3_DVOL_CTRL 0x72 +#define ADC4_DVOL_CTRL 0x73 + +//ADC Digital Mixer Source and Gain Control +#define ADC1_DMIX_SRC 0x76 +#define ADC2_DMIX_SRC 0x77 +#define ADC3_DMIX_SRC 0x78 +#define ADC4_DMIX_SRC 0x79 + +//ADC Digital Debug Control +#define ADC_DIG_DEBUG 0x7F + +//I2S Pad Drive Control +#define I2S_DAT_PADDRV_CTRL 0x80 +#define I2S_CLK_PADDRV_CTRL 0x81 + +//Analog PGA Control +#define ANA_PGA1_CTRL 0x90 +#define ANA_PGA2_CTRL 0x91 +#define ANA_PGA3_CTRL 0x92 +#define ANA_PGA4_CTRL 0x93 + +//MIC Offset Control +#define MIC_OFFSET_CTRL1 0x96 +#define MIC_OFFSET_CTRL2 0x97 +#define MIC1_OFFSET_STATU1 0x98 +#define MIC1_OFFSET_STATU2 0x99 +#define MIC2_OFFSET_STATU1 0x9A +#define MIC2_OFFSET_STATU2 0x9B +#define MIC3_OFFSET_STATU1 0x9C +#define MIC3_OFFSET_STATU2 0x9D +#define MIC4_OFFSET_STATU1 0x9E +#define MIC4_OFFSET_STATU2 0x9F + +//ADC1 Analog Control +#define ANA_ADC1_CTRL1 0xA0 +#define ANA_ADC1_CTRL2 0xA1 +#define ANA_ADC1_CTRL3 0xA2 +#define ANA_ADC1_CTRL4 0xA3 +#define ANA_ADC1_CTRL5 0xA4 +#define ANA_ADC1_CTRL6 0xA5 +#define ANA_ADC1_CTRL7 0xA6 + +//ADC2 Analog Control +#define ANA_ADC2_CTRL1 0xA7 +#define ANA_ADC2_CTRL2 0xA8 +#define ANA_ADC2_CTRL3 0xA9 +#define ANA_ADC2_CTRL4 0xAA +#define ANA_ADC2_CTRL5 0xAB +#define ANA_ADC2_CTRL6 0xAC +#define ANA_ADC2_CTRL7 0xAD + +//ADC3 Analog Control +#define ANA_ADC3_CTRL1 0xAE +#define ANA_ADC3_CTRL2 0xAF +#define ANA_ADC3_CTRL3 0xB0 +#define ANA_ADC3_CTRL4 0xB1 +#define ANA_ADC3_CTRL5 0xB2 +#define ANA_ADC3_CTRL6 0xB3 +#define ANA_ADC3_CTRL7 0xB4 + +//ADC4 Analog Control +#define ANA_ADC4_CTRL1 0xB5 +#define ANA_ADC4_CTRL2 0xB6 +#define ANA_ADC4_CTRL3 0xB7 +#define ANA_ADC4_CTRL4 0xB8 +#define ANA_ADC4_CTRL5 0xB9 +#define ANA_ADC4_CTRL6 0xBA +#define ANA_ADC4_CTRL7 0xBB + +//GPIO Configure +#define GPIO_CFG1 0xC0 +#define GPIO_CFG2 0xC1 +#define GPIO_DAT 0xC2 +#define GPIO_DRV 0xC3 +#define GPIO_PULL 0xC4 +#define GPIO_INT_CFG 0xC5 +#define GPIO_INT_EN 0xC6 +#define GPIO_INT_STATUS 0xC7 + +//Misc +#define BGTC_DAT 0xD1 +#define BGVC_DAT 0xD2 +#define PRNG_CLK_CTRL 0xDF + + + +/*** AC108 Codec Register Bit Define***/ + +/*PWR_CTRL1*/ +#define CP12_CTRL 4 +#define CP12_SENSE_SELECT 3 + +/*PWR_CTRL2*/ +#define CP12_SENSE_FILT 6 +#define CP12_COMP_FF_EN 3 +#define CP12_FORCE_ENABLE 2 +#define CP12_FORCE_RSTB 1 + +/*PWR_CTRL3*/ +#define LDO33DIG_CTRL 0 + +/*PWR_CTRL6*/ +#define LDO33ANA_2XHDRM 2 +#define LDO33ANA_ENABLE 0 + +/*PWR_CTRL7*/ +#define VREF_SEL 3 +#define VREF_FASTSTART_ENABLE 1 +#define VREF_ENABLE 0 + +/*PWR_CTRL9*/ +#define VREFP_FASTSTART_ENABLE 7 +#define VREFP_RESCTRL 5 +#define VREFP_LPMODE 4 +#define IGEN_TRIM 1 +#define VREFP_ENABLE 0 + + +/*PLL_CTRL1*/ +#define PLL_IBIAS 4 +#define PLL_NDET 3 +#define PLL_LOCKED_STATUS 2 +#define PLL_COM_EN 1 +#define PLL_EN 0 + +/*PLL_CTRL2*/ +#define PLL_PREDIV2 5 +#define PLL_PREDIV1 0 + +/*PLL_CTRL3*/ +#define PLL_LOOPDIV_MSB 0 + +/*PLL_CTRL4*/ +#define PLL_LOOPDIV_LSB 0 + +/*PLL_CTRL5*/ +#define PLL_POSTDIV2 5 +#define PLL_POSTDIV1 0 + +/*PLL_CTRL6*/ +#define PLL_LDO 6 +#define PLL_CP 0 + +/*PLL_CTRL7*/ +#define PLL_CAP 6 +#define PLL_RES 4 +#define PLL_TEST_EN 0 + +/*PLL_LOCK_CTRL*/ +#define LOCK_LEVEL1 2 +#define LOCK_LEVEL2 1 +#define PLL_LOCK_EN 0 + + +/*SYSCLK_CTRL*/ +#define PLLCLK_EN 7 +#define PLLCLK_SRC 4 +#define SYSCLK_SRC 3 +#define SYSCLK_EN 0 + +/*MOD_CLK_EN & MOD_RST_CTRL*/ +#define I2S 7 +#define ADC_DIGITAL 4 +#define MIC_OFFSET_CALIBRATION 1 +#define ADC_ANALOG 0 + +/*DSM_CLK_CTRL*/ +#define MIC_OFFSET_DIV 4 +#define DSM_CLK_SEL 0 + + +/*I2S_CTRL*/ +#define BCLK_IOEN 7 +#define LRCK_IOEN 6 +#define SDO2_EN 5 +#define SDO1_EN 4 +#define TXEN 2 +#define RXEN 1 +#define GEN 0 + +/*I2S_BCLK_CTRL*/ +#define EDGE_TRANSFER 5 +#define BCLK_POLARITY 4 +#define BCLKDIV 0 + +/*I2S_LRCK_CTRL1*/ +#define LRCK_POLARITY 4 +#define LRCK_PERIODH 0 + +/*I2S_LRCK_CTRL2*/ +#define LRCK_PERIODL 0 + +/*I2S_FMT_CTRL1*/ +#define ENCD_SEL 6 +#define MODE_SEL 4 +#define TX2_OFFSET 3 +#define TX1_OFFSET 2 +#define TX_SLOT_HIZ 1 +#define TX_STATE 0 + +/*I2S_FMT_CTRL2*/ +#define SLOT_WIDTH_SEL 4 +#define SAMPLE_RESOLUTION 0 + +/*I2S_FMT_CTRL3*/ +#define TX_MLS 7 +#define SEXT 5 +#define OUT2_MUTE 4 +#define OUT1_MUTE 3 +#define LRCK_WIDTH 2 +#define TX_PDM 0 + + +/*I2S_TX1_CTRL1*/ +#define TX1_CHSEL 0 + +/*I2S_TX1_CTRL2*/ +#define TX1_CH8_EN 7 +#define TX1_CH7_EN 6 +#define TX1_CH6_EN 5 +#define TX1_CH5_EN 4 +#define TX1_CH4_EN 3 +#define TX1_CH3_EN 2 +#define TX1_CH2_EN 1 +#define TX1_CH1_EN 0 + +/*I2S_TX1_CTRL3*/ +#define TX1_CH16_EN 7 +#define TX1_CH15_EN 6 +#define TX1_CH14_EN 5 +#define TX1_CH13_EN 4 +#define TX1_CH12_EN 3 +#define TX1_CH11_EN 2 +#define TX1_CH10_EN 1 +#define TX1_CH9_EN 0 + +/*I2S_TX1_CHMP_CTRL1*/ +#define TX1_CH4_MAP 6 +#define TX1_CH3_MAP 4 +#define TX1_CH2_MAP 2 +#define TX1_CH1_MAP 0 + +/*I2S_TX1_CHMP_CTRL2*/ +#define TX1_CH8_MAP 6 +#define TX1_CH7_MAP 4 +#define TX1_CH6_MAP 2 +#define TX1_CH5_MAP 0 + +/*I2S_TX1_CHMP_CTRL3*/ +#define TX1_CH12_MAP 6 +#define TX1_CH11_MAP 4 +#define TX1_CH10_MAP 2 +#define TX1_CH9_MAP 0 + +/*I2S_TX1_CHMP_CTRL4*/ +#define TX1_CH16_MAP 6 +#define TX1_CH15_MAP 4 +#define TX1_CH14_MAP 2 +#define TX1_CH13_MAP 0 + + +/*I2S_TX2_CTRL1*/ +#define TX2_CHSEL 0 + +/*I2S_TX2_CHMP_CTRL1*/ +#define TX2_CH4_MAP 6 +#define TX2_CH3_MAP 4 +#define TX2_CH2_MAP 2 +#define TX2_CH1_MAP 0 + +/*I2S_TX2_CHMP_CTRL2*/ +#define TX2_CH8_MAP 6 +#define TX2_CH7_MAP 4 +#define TX2_CH6_MAP 2 +#define TX2_CH5_MAP 0 + +/*I2S_TX2_CHMP_CTRL3*/ +#define TX2_CH12_MAP 6 +#define TX2_CH11_MAP 4 +#define TX2_CH10_MAP 2 +#define TX2_CH9_MAP 0 + +/*I2S_TX2_CHMP_CTRL4*/ +#define TX2_CH16_MAP 6 +#define TX2_CH15_MAP 4 +#define TX2_CH14_MAP 2 +#define TX2_CH13_MAP 0 + + +/*I2S_RX1_CTRL1*/ +#define RX1_CHSEL 0 + +/*I2S_RX1_CHMP_CTRL1*/ +#define RX1_CH4_MAP 6 +#define RX1_CH3_MAP 4 +#define RX1_CH2_MAP 2 +#define RX1_CH1_MAP 0 + +/*I2S_RX1_CHMP_CTRL2*/ +#define RX1_CH8_MAP 6 +#define RX1_CH7_MAP 4 +#define RX1_CH6_MAP 2 +#define RX1_CH5_MAP 0 + +/*I2S_RX1_CHMP_CTRL3*/ +#define RX1_CH12_MAP 6 +#define RX1_CH11_MAP 4 +#define RX1_CH10_MAP 2 +#define RX1_CH9_MAP 0 + +/*I2S_RX1_CHMP_CTRL4*/ +#define RX1_CH16_MAP 6 +#define RX1_CH15_MAP 4 +#define RX1_CH14_MAP 2 +#define RX1_CH13_MAP 0 + + +/*I2S_LPB_DEBUG*/ +#define I2S_LPB_DEBUG_EN 0 + + +/*ADC_SPRC*/ +#define ADC_FS_I2S1 0 + +/*ADC_DIG_EN*/ +#define DG_EN 4 +#define ENAD4 3 +#define ENAD3 2 +#define ENAD2 1 +#define ENAD1 0 + +/*DMIC_EN*/ +#define DMIC2_EN 1 +#define DMIC1_EN 0 + +/*ADC_DSR*/ +#define DIG_ADC4_SRS 6 +#define DIG_ADC3_SRS 4 +#define DIG_ADC2_SRS 2 +#define DIG_ADC1_SRS 0 + +/*ADC_DDT_CTRL*/ +#define ADOUT_DLY_EN 2 +#define ADOUT_DTS 0 + + +/*HPF_EN*/ +#define DIG_ADC4_HPF_EN 3 +#define DIG_ADC3_HPF_EN 2 +#define DIG_ADC2_HPF_EN 1 +#define DIG_ADC1_HPF_EN 0 + + +/*ADC1_DMIX_SRC*/ +#define ADC1_ADC4_DMXL_GC 7 +#define ADC1_ADC3_DMXL_GC 6 +#define ADC1_ADC2_DMXL_GC 5 +#define ADC1_ADC1_DMXL_GC 4 +#define ADC1_ADC4_DMXL_SRC 3 +#define ADC1_ADC3_DMXL_SRC 2 +#define ADC1_ADC2_DMXL_SRC 1 +#define ADC1_ADC1_DMXL_SRC 0 + +/*ADC2_DMIX_SRC*/ +#define ADC2_ADC4_DMXL_GC 7 +#define ADC2_ADC3_DMXL_GC 6 +#define ADC2_ADC2_DMXL_GC 5 +#define ADC2_ADC1_DMXL_GC 4 +#define ADC2_ADC4_DMXL_SRC 3 +#define ADC2_ADC3_DMXL_SRC 2 +#define ADC2_ADC2_DMXL_SRC 1 +#define ADC2_ADC1_DMXL_SRC 0 + +/*ADC3_DMIX_SRC*/ +#define ADC3_ADC4_DMXL_GC 7 +#define ADC3_ADC3_DMXL_GC 6 +#define ADC3_ADC2_DMXL_GC 5 +#define ADC3_ADC1_DMXL_GC 4 +#define ADC3_ADC4_DMXL_SRC 3 +#define ADC3_ADC3_DMXL_SRC 2 +#define ADC3_ADC2_DMXL_SRC 1 +#define ADC3_ADC1_DMXL_SRC 0 + +/*ADC4_DMIX_SRC*/ +#define ADC4_ADC4_DMXL_GC 7 +#define ADC4_ADC3_DMXL_GC 6 +#define ADC4_ADC2_DMXL_GC 5 +#define ADC4_ADC1_DMXL_GC 4 +#define ADC4_ADC4_DMXL_SRC 3 +#define ADC4_ADC3_DMXL_SRC 2 +#define ADC4_ADC2_DMXL_SRC 1 +#define ADC4_ADC1_DMXL_SRC 0 + + +/*ADC_DIG_DEBUG*/ +#define ADC_PTN_SEL 0 + + +/*I2S_DAT_PADDRV_CTRL*/ +#define TX2_DAT_DRV 4 +#define TX1_DAT_DRV 0 + +/*I2S_CLK_PADDRV_CTRL*/ +#define LRCK_DRV 4 +#define BCLK_DRV 0 + + +/*ANA_PGA1_CTRL*/ +#define ADC1_ANALOG_PGA 1 +#define ADC1_ANALOG_PGA_STEP 0 + +/*ANA_PGA2_CTRL*/ +#define ADC2_ANALOG_PGA 1 +#define ADC2_ANALOG_PGA_STEP 0 + +/*ANA_PGA3_CTRL*/ +#define ADC3_ANALOG_PGA 1 +#define ADC3_ANALOG_PGA_STEP 0 + +/*ANA_PGA4_CTRL*/ +#define ADC4_ANALOG_PGA 1 +#define ADC4_ANALOG_PGA_STEP 0 + + +/*MIC_OFFSET_CTRL1*/ +#define MIC_OFFSET_CAL_EN4 3 +#define MIC_OFFSET_CAL_EN3 2 +#define MIC_OFFSET_CAL_EN2 1 +#define MIC_OFFSET_CAL_EN1 0 + +/*MIC_OFFSET_CTRL2*/ +#define MIC_OFFSET_CAL_GAIN 3 +#define MIC_OFFSET_CAL_CHANNEL 1 +#define MIC_OFFSET_CAL_EN_ONCE 0 + +/*MIC1_OFFSET_STATU1*/ +#define MIC1_OFFSET_CAL_DONE 7 +#define MIC1_OFFSET_CAL_RUN_STA 6 +#define MIC1_OFFSET_MSB 0 + +/*MIC1_OFFSET_STATU2*/ +#define MIC1_OFFSET_LSB 0 + +/*MIC2_OFFSET_STATU1*/ +#define MIC2_OFFSET_CAL_DONE 7 +#define MIC2_OFFSET_CAL_RUN_STA 6 +#define MIC2_OFFSET_MSB 0 + +/*MIC2_OFFSET_STATU2*/ +#define MIC2_OFFSET_LSB 0 + +/*MIC3_OFFSET_STATU1*/ +#define MIC3_OFFSET_CAL_DONE 7 +#define MIC3_OFFSET_CAL_RUN_STA 6 +#define MIC3_OFFSET_MSB 0 + +/*MIC3_OFFSET_STATU2*/ +#define MIC3_OFFSET_LSB 0 + +/*MIC4_OFFSET_STATU1*/ +#define MIC4_OFFSET_CAL_DONE 7 +#define MIC4_OFFSET_CAL_RUN_STA 6 +#define MIC4_OFFSET_MSB 0 + +/*MIC4_OFFSET_STATU2*/ +#define MIC4_OFFSET_LSB 0 + + +/*ANA_ADC1_CTRL1*/ +#define ADC1_PGA_BYPASS 7 +#define ADC1_PGA_BYP_RCM 6 +#define ADC1_PGA_CTRL_RCM 4 +#define ADC1_PGA_MUTE 3 +#define ADC1_DSM_ENABLE 2 +#define ADC1_PGA_ENABLE 1 +#define ADC1_MICBIAS_EN 0 + +/*ANA_ADC1_CTRL3*/ +#define ADC1_ANA_CAL_EN 5 +#define ADC1_SEL_OUT_EDGE 3 +#define ADC1_DSM_DISABLE 2 +#define ADC1_VREFP_DISABLE 1 +#define ADC1_AAF_DISABLE 0 + +/*ANA_ADC1_CTRL6*/ +#define PGA_CTRL_TC 6 +#define PGA_CTRL_RC 4 +#define PGA_CTRL_I_LIN 2 +#define PGA_CTRL_I_IN 0 + +/*ANA_ADC1_CTRL7*/ +#define PGA_CTRL_HI_Z 7 +#define PGA_CTRL_SHORT_RF 6 +#define PGA_CTRL_VCM_VG 4 +#define PGA_CTRL_VCM_IN 0 + + +/*ANA_ADC2_CTRL1*/ +#define ADC2_PGA_BYPASS 7 +#define ADC2_PGA_BYP_RCM 6 +#define ADC2_PGA_CTRL_RCM 4 +#define ADC2_PGA_MUTE 3 +#define ADC2_DSM_ENABLE 2 +#define ADC2_PGA_ENABLE 1 +#define ADC2_MICBIAS_EN 0 + +/*ANA_ADC2_CTRL3*/ +#define ADC2_ANA_CAL_EN 5 +#define ADC2_SEL_OUT_EDGE 3 +#define ADC2_DSM_DISABLE 2 +#define ADC2_VREFP_DISABLE 1 +#define ADC2_AAF_DISABLE 0 + +/*ANA_ADC2_CTRL6*/ +#define PGA_CTRL_IBOOST 7 +#define PGA_CTRL_IQCTRL 6 +#define PGA_CTRL_OABIAS 4 +#define PGA_CTRL_CMLP_DIS 3 +#define PGA_CTRL_PDB_RIN 2 +#define PGA_CTRL_PEAKDET 0 + +/*ANA_ADC2_CTRL7*/ +#define AAF_LPMODE_EN 7 +#define AAF_STG2_IB_SEL 4 +#define AAFDSM_IB_DIV2 3 +#define AAF_STG1_IB_SEL 0 + + +/*ANA_ADC3_CTRL1*/ +#define ADC3_PGA_BYPASS 7 +#define ADC3_PGA_BYP_RCM 6 +#define ADC3_PGA_CTRL_RCM 4 +#define ADC3_PGA_MUTE 3 +#define ADC3_DSM_ENABLE 2 +#define ADC3_PGA_ENABLE 1 +#define ADC3_MICBIAS_EN 0 + +/*ANA_ADC3_CTRL3*/ +#define ADC3_ANA_CAL_EN 5 +#define ADC3_INVERT_CLK 4 +#define ADC3_SEL_OUT_EDGE 3 +#define ADC3_DSM_DISABLE 2 +#define ADC3_VREFP_DISABLE 1 +#define ADC3_AAF_DISABLE 0 + +/*ANA_ADC3_CTRL7*/ +#define DSM_COMP_IB_SEL 6 +#define DSM_OTA_CTRL 4 +#define DSM_LPMODE 3 +#define DSM_OTA_IB_SEL 0 + + +/*ANA_ADC4_CTRL1*/ +#define ADC4_PGA_BYPASS 7 +#define ADC4_PGA_BYP_RCM 6 +#define ADC4_PGA_CTRL_RCM 4 +#define ADC4_PGA_MUTE 3 +#define ADC4_DSM_ENABLE 2 +#define ADC4_PGA_ENABLE 1 +#define ADC4_MICBIAS_EN 0 + +/*ANA_ADC4_CTRL3*/ +#define ADC4_ANA_CAL_EN 5 +#define ADC4_SEL_OUT_EDGE 3 +#define ADC4_DSM_DISABLE 2 +#define ADC4_VREFP_DISABLE 1 +#define ADC4_AAF_DISABLE 0 + +/*ANA_ADC4_CTRL6*/ +#define DSM_DEMOFF 5 +#define DSM_EN_DITHER 4 +#define DSM_VREFP_LPMODE 2 +#define DSM_VREFP_OUTCTRL 0 + +/*ANA_ADC4_CTRL7*/ +#define CK8M_EN 5 +#define OSC_EN 4 +#define ADC4_CLK_GATING 3 +#define ADC3_CLK_GATING 2 +#define ADC2_CLK_GATING 1 +#define ADC1_CLK_GATING 0 + + +/*GPIO_CFG1*/ +#define GPIO2_SELECT 4 +#define GPIO1_SELECT 0 + +/*GPIO_CFG2*/ +#define GPIO4_SELECT 4 +#define GPIO3_SELECT 0 + +/*GPIO_DAT*///order??? +#define GPIO4_DAT 3 +#define GPIO3_DAT 2 +#define GPIO2_DAT 1 +#define GPIO1_DAT 0 + +/*GPIO_DRV*/ +#define GPIO4_DRV 6 +#define GPIO3_DRV 4 +#define GPIO2_DRV 2 +#define GPIO1_DRV 0 + +/*GPIO_PULL*/ +#define GPIO4_PULL 6 +#define GPIO3_PULL 4 +#define GPIO2_PULL 2 +#define GPIO1_PULL 0 + +/*GPIO_INT_CFG*/ +#define GPIO4_EINT_CFG 6 +#define GPIO3_EINT_CFG 4 +#define GPIO2_EINT_CFG 2 +#define GPIO1_EINT_CFG 0 + +/*GPIO_INT_EN*///order??? +#define GPIO4_EINT_EN 3 +#define GPIO3_EINT_EN 2 +#define GPIO2_EINT_EN 1 +#define GPIO1_EINT_EN 0 + +/*GPIO_INT_STATUS*///order??? +#define GPIO4_EINT_STA 3 +#define GPIO3_EINT_STA 2 +#define GPIO2_EINT_STA 1 +#define GPIO1_EINT_STA 0 + + +/*PRNG_CLK_CTRL*/ +#define PRNG_CLK_EN 1 +#define PRNG_CLK_POS 0 + + + +/*** Some Config Value ***/ + +//[SYSCLK_CTRL]: PLLCLK_SRC +#define PLLCLK_SRC_MCLK 0 +#define PLLCLK_SRC_BCLK 1 +#define PLLCLK_SRC_GPIO2 2 +#define PLLCLK_SRC_GPIO3 3 + +//[SYSCLK_CTRL]: SYSCLK_SRC +#define SYSCLK_SRC_MCLK 0 +#define SYSCLK_SRC_PLL 1 + +//I2S BCLK POLARITY Control +#define BCLK_NORMAL_DRIVE_N_SAMPLE_P 0 +#define BCLK_INVERT_DRIVE_P_SAMPLE_N 1 + +//I2S LRCK POLARITY Control +#define LRCK_LEFT_LOW_RIGHT_HIGH 0 +#define LRCK_LEFT_HIGH_RIGHT_LOW 1 + +//I2S Format Selection +#define PCM_FORMAT 0 +#define LEFT_JUSTIFIED_FORMAT 1 +#define RIGHT_JUSTIFIED_FORMAT 2 + + +//I2S data protocol types + +#define IS_ENCODING_MODE 0 + +#endif + diff --git a/seeed-voicecard/ac108_6mic.state b/seeed-voicecard/ac108_6mic.state new file mode 100644 index 0000000..61e691a --- /dev/null +++ b/seeed-voicecard/ac108_6mic.state @@ -0,0 +1,367 @@ +state.ALSA { + control.1 { + iface MIXER + name 'PCM Playback Volume' + value 400 + comment { + access 'read write' + type INTEGER + count 1 + range '-10239 - 400' + dbmin -9999999 + dbmax 400 + dbvalue.0 400 + } + } + control.2 { + iface MIXER + name 'PCM Playback Switch' + value true + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.3 { + iface MIXER + name 'PCM Playback Route' + value 1 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 2' + } + } + control.4 { + iface PCM + name 'IEC958 Playback Default' + value '0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' + comment { + access 'read write' + type IEC958 + count 1 + } + } + control.5 { + iface PCM + name 'IEC958 Playback Con Mask' + value '0200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' + comment { + access read + type IEC958 + count 1 + } + } + control.6 { + iface PCM + name 'IEC958 Playback PCM Stream' + value '0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' + comment { + access 'read write inactive' + type IEC958 + count 1 + } + } +} +state.seeed8micvoicec { + control.1 { + iface MIXER + name 'CH1 digital volume' + value 208 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 255' + dbmin -11925 + dbmax 7200 + dbvalue.0 3675 + } + } + control.2 { + iface MIXER + name 'CH2 digital volume' + value 208 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 255' + dbmin -11925 + dbmax 7200 + dbvalue.0 3675 + } + } + control.3 { + iface MIXER + name 'CH3 digital volume' + value 208 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 255' + dbmin -11925 + dbmax 7200 + dbvalue.0 3675 + } + } + control.4 { + iface MIXER + name 'CH4 digital volume' + value 208 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 255' + dbmin -11925 + dbmax 7200 + dbvalue.0 3675 + } + } + control.5 { + iface MIXER + name 'ADC1 PGA gain' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 31' + dbmin 0 + dbmax 3100 + dbvalue.0 0 + } + } + control.6 { + iface MIXER + name 'ADC2 PGA gain' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 31' + dbmin 0 + dbmax 3100 + dbvalue.0 0 + } + } + control.7 { + iface MIXER + name 'ADC3 PGA gain' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 31' + dbmin 0 + dbmax 3100 + dbvalue.0 0 + } + } + control.8 { + iface MIXER + name 'ADC4 PGA gain' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 31' + dbmin 0 + dbmax 3100 + dbvalue.0 0 + } + } + control.9 { + iface MIXER + name 'CH5 digital volume' + value 208 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 255' + dbmin -11925 + dbmax 7200 + dbvalue.0 3675 + } + } + control.10 { + iface MIXER + name 'CH6 digital volume' + value 208 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 255' + dbmin -11925 + dbmax 7200 + dbvalue.0 3675 + } + } + control.11 { + iface MIXER + name 'CH7 digital volume' + value 198 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 255' + dbmin -11925 + dbmax 7200 + dbvalue.0 2925 + } + } + control.12 { + iface MIXER + name 'CH8 digital volume' + value 198 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 255' + dbmin -11925 + dbmax 7200 + dbvalue.0 2925 + } + } + control.13 { + iface MIXER + name 'ADC5 PGA gain' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 31' + dbmin 0 + dbmax 3100 + dbvalue.0 0 + } + } + control.14 { + iface MIXER + name 'ADC6 PGA gain' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 31' + dbmin 0 + dbmax 3100 + dbvalue.0 0 + } + } + control.15 { + iface MIXER + name 'ADC7 PGA gain' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 31' + dbmin 0 + dbmax 3100 + dbvalue.0 0 + } + } + control.16 { + iface MIXER + name 'ADC8 PGA gain' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 31' + dbmin 0 + dbmax 3100 + dbvalue.0 0 + } + } + control.17 { + iface MIXER + name 'DAC volume' + value.0 0 + value.1 0 + comment { + access 'read write' + type INTEGER + count 2 + range '0 - 255' + dbmin -11925 + dbmax 7200 + dbvalue.0 -11925 + dbvalue.1 -11925 + } + } + control.18 { + iface MIXER + name 'DAC mixer gain' + value.0 0 + value.1 0 + comment { + access 'read write' + type INTEGER + count 2 + range '0 - 15' + dbmin -600 + dbmax 8400 + dbvalue.0 -600 + dbvalue.1 -600 + } + } + control.19 { + iface MIXER + name 'digital volume' + value 51 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 63' + dbmin -7308 + dbmax 0 + dbvalue.0 -1392 + } + } + control.20 { + iface MIXER + name 'Speaker Playback Volume' + value 25 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 31' + dbmin -4800 + dbmax -150 + dbvalue.0 -1050 + } + } + control.21 { + iface MIXER + name 'Headphone Playback Volume' + value 52 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 63' + dbmin -6300 + dbmax 0 + dbvalue.0 -1100 + } + } +} diff --git a/seeed-voicecard/ac108_asound.state b/seeed-voicecard/ac108_asound.state new file mode 100644 index 0000000..103bfb4 --- /dev/null +++ b/seeed-voicecard/ac108_asound.state @@ -0,0 +1,181 @@ +state.ALSA { + control.1 { + iface MIXER + name 'PCM Playback Volume' + value 400 + comment { + access 'read write' + type INTEGER + count 1 + range '-10239 - 400' + dbmin -9999999 + dbmax 400 + dbvalue.0 400 + } + } + control.2 { + iface MIXER + name 'PCM Playback Switch' + value true + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.3 { + iface MIXER + name 'PCM Playback Route' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 2' + } + } + control.4 { + iface PCM + name 'IEC958 Playback Default' + value '0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' + comment { + access 'read write' + type IEC958 + count 1 + } + } + control.5 { + iface PCM + name 'IEC958 Playback Con Mask' + value '0200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' + comment { + access read + type IEC958 + count 1 + } + } + control.6 { + iface PCM + name 'IEC958 Playback PCM Stream' + value '0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' + comment { + access 'read write inactive' + type IEC958 + count 1 + } + } +} +state.seeed4micvoicec { + control.1 { + iface MIXER + name 'CH1 digital volume' + value 222 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 255' + dbmin -11925 + dbmax 7200 + dbvalue.0 4725 + } + } + control.2 { + iface MIXER + name 'CH2 digital volume' + value 222 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 255' + dbmin -11925 + dbmax 7200 + dbvalue.0 4725 + } + } + control.3 { + iface MIXER + name 'CH3 digital volume' + value 222 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 255' + dbmin -11925 + dbmax 7200 + dbvalue.0 4725 + } + } + control.4 { + iface MIXER + name 'CH4 digital volume' + value 222 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 255' + dbmin -11925 + dbmax 7200 + dbvalue.0 4725 + } + } + control.5 { + iface MIXER + name 'ADC1 PGA gain' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 31' + dbmin 0 + dbmax 3100 + dbvalue.0 0 + } + } + control.6 { + iface MIXER + name 'ADC2 PGA gain' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 31' + dbmin 0 + dbmax 3100 + dbvalue.0 0 + } + } + control.7 { + iface MIXER + name 'ADC3 PGA gain' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 31' + dbmin 0 + dbmax 3100 + dbvalue.0 0 + } + } + control.8 { + iface MIXER + name 'ADC4 PGA gain' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 31' + dbmin 0 + dbmax 3100 + dbvalue.0 0 + } + } +} diff --git a/seeed-voicecard/ac108_plugin/Makefile b/seeed-voicecard/ac108_plugin/Makefile new file mode 100644 index 0000000..2a32ae9 --- /dev/null +++ b/seeed-voicecard/ac108_plugin/Makefile @@ -0,0 +1,58 @@ + +# Quiet (set to @ for a quite compile) +Q ?= @ +#Q ?= + +# Build Tools +CC := gcc +CFLAGS += -I. -Wall -funroll-loops -ffast-math -fPIC -DPIC -O0 -g +LD := gcc +LDFLAGS += -Wall -shared -lasound + +SND_PCM_OBJECTS = pcm_ac108.o ac108_help.o +SND_PCM_LIBS = +SND_PCM_BIN = libasound_module_pcm_ac108.so + +#SND_CTL_OBJECTS = ctl_ac108.o ladspa_utils.o +#SND_CTL_LIBS = +#SND_CTL_BIN = libasound_module_ctl_ac108.so + +MULTIARCH:=$(shell gcc --print-multiarch) +LIBDIR = lib/$(MULTIARCH) + +.PHONY: all clean dep load_default + +all: Makefile $(SND_PCM_BIN) $(SND_CTL_BIN) + +dep: + @echo DEP $@ + $(Q)for i in *.c; do $(CC) -MM $(CFLAGS) "$${i}" ; done > makefile.dep + +-include makefile.dep + +$(SND_PCM_BIN): $(SND_PCM_OBJECTS) + @echo LD $@ + $(Q)$(LD) $(LDFLAGS) $(SND_PCM_LIBS) $(SND_PCM_OBJECTS) -o $(SND_PCM_BIN) + +#$(SND_CTL_BIN): $(SND_CTL_OBJECTS) +# @echo LD $@ +# $(Q)$(LD) $(LDFLAGS) $(SND_CTL_LIBS) $(SND_CTL_OBJECTS) -o $(SND_CTL_BIN) + +%.o: %.c + @echo GCC $< + $(Q)$(CC) -c $(CFLAGS) $(CPPFLAGS) $< + +clean: + @echo Cleaning... + $(Q)rm -vf *.o *.so + +install: all + @echo Installing... + $(Q)mkdir -p ${DESTDIR}/usr/$(LIBDIR)/alsa-lib/ + $(Q)install -m 644 $(SND_PCM_BIN) ${DESTDIR}/usr/$(LIBDIR)/alsa-lib/ + #$(Q)install -m 644 $(SND_CTL_BIN) ${DESTDIR}/usr/$(LIBDIR)/alsa-lib/ + +uninstall: + @echo Un-installing... + $(Q)rm ${DESTDIR}/usr/lib/alsa-lib/$(SND_PCM_BIN) + #$(Q)rm ${DESTDIR}/usr/lib/alsa-lib/$(SND_CTL_BIN) diff --git a/seeed-voicecard/ac108_plugin/README.md b/seeed-voicecard/ac108_plugin/README.md new file mode 100644 index 0000000..38137b2 --- /dev/null +++ b/seeed-voicecard/ac108_plugin/README.md @@ -0,0 +1,5 @@ +#seeed-4mic-voicecard alsa plugin +``` +sudo apt install libasound2-dev +make && sudo make install +``` \ No newline at end of file diff --git a/seeed-voicecard/ac108_plugin/ac108_help.c b/seeed-voicecard/ac108_plugin/ac108_help.c new file mode 100644 index 0000000..cb5fd81 --- /dev/null +++ b/seeed-voicecard/ac108_plugin/ac108_help.c @@ -0,0 +1,77 @@ +#include "ac108_help.h" + +void generate_sine(const snd_pcm_channel_area_t *areas, + snd_pcm_uframes_t offset, + int count, double *_phase) +{ + snd_pcm_format_t format = SND_PCM_FORMAT_S32; /* sample format */ + unsigned int rate = 16000; /* stream rate */ + unsigned int channels = 4; /* count of channels */ + unsigned int buffer_time = 500000; /* ring buffer length in us */ + unsigned int period_time = 100000; /* period time in us */ + double freq = 160; /* sinusoidal wave frequency in Hz */ + int verbose = 0; /* verbose flag */ + int resample = 1; /* enable alsa-lib resampling */ + int period_event = 0; /* produce poll event after each period */ + + static double max_phase = 2. * M_PI; + double phase = *_phase; + double step = max_phase*freq/(double)rate; + unsigned char *samples[channels]; + int steps[channels]; + unsigned int chn; + int format_bits = snd_pcm_format_width(format); + unsigned int maxval = (1 << (format_bits - 1)) - 1; + int bps = format_bits / 8; /* bytes per sample */ + int phys_bps = snd_pcm_format_physical_width(format) / 8; + int big_endian = snd_pcm_format_big_endian(format) == 1; + int to_unsigned = snd_pcm_format_unsigned(format) == 1; + int is_float = (format == SND_PCM_FORMAT_FLOAT_LE || + format == SND_PCM_FORMAT_FLOAT_BE); + + /* verify and prepare the contents of areas */ + for (chn = 0; chn < channels; chn++) { + if ((areas[chn].first % 8) != 0) { + printf("areas[%i].first == %i, aborting...\n", chn, areas[chn].first); + exit(EXIT_FAILURE); + } + samples[chn] = /*(signed short *)*/(((unsigned char *)areas[chn].addr) + (areas[chn].first / 8)); + if ((areas[chn].step % 16) != 0) { + printf("areas[%i].step == %i, aborting...\n", chn, areas[chn].step); + exit(EXIT_FAILURE); + } + steps[chn] = areas[chn].step / 8; + samples[chn] += offset * steps[chn]; + } + /* fill the channel areas */ + while (count-- > 0) { + union { + float f; + int i; + } fval; + int res, i; + if (is_float) { + fval.f = sin(phase); + res = fval.i; + } else + res = sin(phase) * maxval; + if (to_unsigned) + res ^= 1U << (format_bits - 1); + for (chn = 0; chn < channels; chn++) { + /* Generate data in native endian format */ + if (big_endian) { + for (i = 0; i < bps; i++) + *(samples[chn] + phys_bps - 1 - i) = (res >> i * 8) & 0xff; + } else { + for (i = 0; i < bps; i++) + *(samples[chn] + i) = (res >> i * 8) & 0xff; + } + samples[chn] += steps[chn]; + } + phase += step; + if (phase >= max_phase) + phase -= max_phase; + } + *_phase = phase; +} + diff --git a/seeed-voicecard/ac108_plugin/ac108_help.h b/seeed-voicecard/ac108_plugin/ac108_help.h new file mode 100644 index 0000000..97adb84 --- /dev/null +++ b/seeed-voicecard/ac108_plugin/ac108_help.h @@ -0,0 +1,11 @@ +#include +#include +#include +#include +#include +#include +#include + +void generate_sine(const snd_pcm_channel_area_t *areas, + snd_pcm_uframes_t offset, + int count, double *_phase); diff --git a/seeed-voicecard/ac108_plugin/pcm_ac108.c b/seeed-voicecard/ac108_plugin/pcm_ac108.c new file mode 100644 index 0000000..9ade62a --- /dev/null +++ b/seeed-voicecard/ac108_plugin/pcm_ac108.c @@ -0,0 +1,536 @@ +//https://github.com/HazouPH/android_device_motorola_smi-plus/blob/48029b4afc307c73181b108a5b0155b9f20856ca/smi-modules/alsa-lib_module_voice/pcm_voice.c +#include +#include +#include +#include +#include +#include +#include "ac108_help.h" +#include + +#define ARRAY_SIZE(ary) (sizeof(ary)/sizeof(ary[0])) +#define AC108_FRAME_SIZE 40960 +struct ac108_t { + snd_pcm_ioplug_t io; + snd_pcm_t *pcm; + snd_pcm_hw_params_t *hw_params; + unsigned int last_size; + unsigned int ptr; + unsigned int latency; // Delay in usec + unsigned int bufferSize; // Size of sample buffer +}; +static unsigned char capture_buf[AC108_FRAME_SIZE]; +/* set up the fixed parameters of pcm PCM hw_parmas */ +static int ac108_slave_hw_params_half(struct ac108_t *capture, unsigned int rate,snd_pcm_format_t format) { + int err; + snd_pcm_uframes_t bufferSize = capture->bufferSize; + unsigned int latency = capture->latency; + + unsigned int buffer_time = 0; + unsigned int period_time = 0; + if ((err = snd_pcm_hw_params_malloc(&capture->hw_params)) < 0) return err; + + if ((err = snd_pcm_hw_params_any(capture->pcm, capture->hw_params)) < 0) { + SNDERR("Cannot get pcm hw_params"); + goto out; + } + if ((err = snd_pcm_hw_params_set_access(capture->pcm, capture->hw_params, + SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { + SNDERR("Cannot set pcm access RW_INTERLEAVED"); + goto out; + } + if ((err = snd_pcm_hw_params_set_channels(capture->pcm, capture->hw_params, 2)) < 0) { + SNDERR("Cannot set pcm channels 2"); + goto out; + } + if ((err = snd_pcm_hw_params_set_format(capture->pcm, capture->hw_params, + format)) < 0) { + SNDERR("Cannot set pcm format"); + goto out; + } + if ((err = snd_pcm_hw_params_set_rate(capture->pcm, capture->hw_params, rate, 0)) < 0) { + SNDERR("Cannot set pcm rate %d", rate); + goto out; + } + + err = snd_pcm_hw_params_get_buffer_time_max(capture->hw_params, + &buffer_time, 0); + if (buffer_time > 80000) + buffer_time = 80000; + period_time = buffer_time / 4; + + err = snd_pcm_hw_params_set_period_time_near(capture->pcm, capture->hw_params, + &period_time, 0); + if (err < 0) { + SNDERR("Unable to set_period_time_near"); + goto out; + } + err = snd_pcm_hw_params_set_buffer_time_near(capture->pcm, capture->hw_params, + &buffer_time, 0); + if (err < 0) { + SNDERR("Unable to set_buffer_time_near"); + goto out; + } + + capture->bufferSize = bufferSize; + capture->latency = latency; + + return 0; + +out: + free(capture->hw_params); + capture->hw_params = NULL; + return err; +} + +/* + * start and stop callbacks - just trigger pcm PCM + */ +static int ac108_start(snd_pcm_ioplug_t *io) { + struct ac108_t *capture = io->private_data; + if(!capture->pcm) { + SNDERR( "pcm is lost\n"); + } + + return snd_pcm_start(capture->pcm); +} + +static int ac108_stop(snd_pcm_ioplug_t *io) { + struct ac108_t *capture = io->private_data; + + return snd_pcm_drop(capture->pcm); +} +/* + * pointer callback + * + * Calculate the current position from the delay of pcm PCM + */ +static snd_pcm_sframes_t ac108_pointer(snd_pcm_ioplug_t *io) { + + struct ac108_t *capture = io->private_data; + int size; + + assert(capture); + + + size = snd_pcm_avail(capture->pcm); + if (size < 0) + return size; + + size = size/2; + + if (size > capture->last_size) { + capture->ptr += size - capture->last_size; + capture->ptr %= io->buffer_size; + } + + //fprintf(stderr, "%s :%d %d %d %d %d %d\n", __func__,capture->ptr ,capture->last_size,size, io->buffer_size,io->appl_ptr, io->hw_ptr); + capture->last_size = size; + + return capture->ptr; +} + +/* + * transfer callback + */ +static snd_pcm_sframes_t ac108_transfer(snd_pcm_ioplug_t *io, + const snd_pcm_channel_area_t *dst_areas, + snd_pcm_uframes_t dst_offset, + snd_pcm_uframes_t size) { + struct ac108_t *capture = io->private_data; + int chn; + unsigned char *dst_samples[io->channels]; + int dst_steps[io->channels]; + int bps = snd_pcm_format_width(io->format) / 8; /* bytes per sample */ + int i; + int count = 0; + int err = 0; + unsigned char *src_buf; + unsigned char src_data[4][4]; + + + memset(capture_buf,0,AC108_FRAME_SIZE); + + if(snd_pcm_avail(capture->pcm) > size*2){ + if ((err = snd_pcm_readi (capture->pcm, capture_buf, size*2)) != size*2) { + SNDERR("read from audio interface failed %ld %d %s!\n",size,err,snd_strerror (err)); + exit(EXIT_FAILURE); + size = 0 ; + } + }else{ + size = 0; + } +#if 1 + /* verify and prepare the contents of areas */ + for (chn = 0; chn < io->channels; chn++) { + if ((dst_areas[chn].first % 8) != 0) { + SNDERR("dst_areas[%i].first == %i, aborting...\n", chn, dst_areas[chn].first); + exit(EXIT_FAILURE); + } + dst_samples[chn] = /*(signed short *)*/(((unsigned char *)dst_areas[chn].addr) + (dst_areas[chn].first / 8)); + if ((dst_areas[chn].step % 16) != 0) { + SNDERR("dst_areas[%i].step == %i, aborting...\n", chn, dst_areas[chn].step); + exit(EXIT_FAILURE); + } + dst_steps[chn] = dst_areas[chn].step / 8; + dst_samples[chn] += dst_offset * dst_steps[chn]; + } +#endif + // for(i = 0; i < size*2*bps;i++){ + // fprintf(stderr,"%x ",capture_buf[i]); + // if(i%4 == 0) + // fprintf(stderr,"\n"); + // } + + //generate_sine(dst_areas, dst_offset,size, &count); + src_buf = capture_buf; +#if 1 + while(count < size){ + for(chn = 0; chn < 4; chn++){ + for (i = 0; i < bps; i++){ + src_data[chn][i] = src_buf[i]; + } + src_buf += bps ; + } + + for(chn = 0; chn < io->channels; chn++){ + for (i = 0; i < bps; i++){ + *(dst_samples[chn] + i) = src_data[chn][i]; + //fprintf(stderr,"%x ",*(dst_samples[chn] + i)); + } + //fprintf(stderr,"\n"); + dst_samples[chn] += dst_steps[chn]; + } + count++; + } + +#endif + + capture->last_size -= size; + + return size; +} + +/* + * poll-related callbacks - just pass to pcm PCM + */ +static int ac108_poll_descriptors_count(snd_pcm_ioplug_t *io) { + struct ac108_t *capture = io->private_data; + + return snd_pcm_poll_descriptors_count(capture->pcm); +} + +static int ac108_poll_descriptors(snd_pcm_ioplug_t *io, struct pollfd *pfd, + unsigned int space) { + struct ac108_t *capture = io->private_data; + + return snd_pcm_poll_descriptors(capture->pcm, pfd, space); +} + +static int ac108_poll_revents(snd_pcm_ioplug_t *io, struct pollfd *pfd, + unsigned int nfds, unsigned short *revents) { + struct ac108_t *capture = io->private_data; + + return snd_pcm_poll_descriptors_revents(capture->pcm, pfd, nfds, revents); +} + +/* + * close callback + */ +static int ac108_close(snd_pcm_ioplug_t *io) { + struct ac108_t *capture = io->private_data; + if (capture->pcm) + snd_pcm_close(capture->pcm); + + return 0; +} + +static int setSoftwareParams(struct ac108_t *capture) { + snd_pcm_sw_params_t *softwareParams; + int err; + + snd_pcm_uframes_t bufferSize = 0; + snd_pcm_uframes_t periodSize = 0; + snd_pcm_uframes_t startThreshold, stopThreshold; + snd_pcm_sw_params_alloca(&softwareParams); + + // Get the current software parameters + err = snd_pcm_sw_params_current(capture->pcm, softwareParams); + if (err < 0) { + SNDERR("Unable to get software parameters: %s", snd_strerror(err)); + goto done; + } + + // Configure ALSA to start the transfer when the buffer is almost full. + snd_pcm_get_params(capture->pcm, &bufferSize, &periodSize); + + + startThreshold = 1; + stopThreshold = bufferSize; + + + err = snd_pcm_sw_params_set_start_threshold(capture->pcm, softwareParams, + startThreshold); + if (err < 0) { + SNDERR("Unable to set start threshold to %lu frames: %s", + startThreshold, snd_strerror(err)); + goto done; + } + + err = snd_pcm_sw_params_set_stop_threshold(capture->pcm, softwareParams, + stopThreshold); + if (err < 0) { + SNDERR("Unable to set stop threshold to %lu frames: %s", + stopThreshold, snd_strerror(err)); + goto done; + } + // Allow the transfer to start when at least periodSize samples can be + // processed. + err = snd_pcm_sw_params_set_avail_min(capture->pcm, softwareParams, + periodSize); + if (err < 0) { + SNDERR("Unable to configure available minimum to %lu: %s", + periodSize, snd_strerror(err)); + goto done; + } + + // Commit the software parameters back to the device. + err = snd_pcm_sw_params(capture->pcm, softwareParams); + if (err < 0) + SNDERR("Unable to configure software parameters: %s",snd_strerror(err)); + + + + return 0; +done: + snd_pcm_sw_params_free(softwareParams); + + return err; +} +/* + * hw_params callback + * + * Set up pcm PCM according to the current parameters + */ +static int ac108_hw_params(snd_pcm_ioplug_t *io, snd_pcm_hw_params_t *params) { + struct ac108_t *capture = io->private_data; + snd_pcm_uframes_t period_size; + snd_pcm_uframes_t buffer_size; + int err; + if (!capture->hw_params) { + err = ac108_slave_hw_params_half(capture, 2*io->rate,io->format); + if (err < 0) { + SNDERR("ac108_slave_hw_params_half error\n"); + return err; + } + } + period_size = io->period_size; + if ((err = snd_pcm_hw_params_set_period_size_near(capture->pcm, capture->hw_params, + &period_size, NULL)) < 0) { + SNDERR("Cannot set pcm period size %ld", period_size); + return err; + } + buffer_size = io->buffer_size; + if ((err = snd_pcm_hw_params_set_buffer_size_near(capture->pcm, capture->hw_params, + &buffer_size)) < 0) { + SNDERR("Cannot set pcm buffer size %ld", buffer_size); + return err; + } + if ((err = snd_pcm_hw_params(capture->pcm, capture->hw_params)) < 0) { + SNDERR("Cannot set pcm hw_params"); + return err; + } + setSoftwareParams(capture); + return 0; +} +/* + * hw_free callback + */ +static int ac108_hw_free(snd_pcm_ioplug_t *io) { + struct ac108_t *capture = io->private_data; + free(capture->hw_params); + capture->hw_params = NULL; + + return snd_pcm_hw_free(capture->pcm); + +} + + +static int ac108_prepare(snd_pcm_ioplug_t *io) { + struct ac108_t *capture = io->private_data; + capture->ptr = 0; + capture->last_size =0; + return snd_pcm_prepare(capture->pcm); +} +static int ac108_drain(snd_pcm_ioplug_t *io) { + struct ac108_t *capture = io->private_data; + + return snd_pcm_drain(capture->pcm); +} +#if 0 +static int ac108_sw_params(snd_pcm_ioplug_t *io, snd_pcm_sw_params_t *params) { + return 0; +} +#endif +static int ac108_delay(snd_pcm_ioplug_t * io, snd_pcm_sframes_t * delayp){ + + return 0; +} +/* + * callback table + */ +static snd_pcm_ioplug_callback_t a108_ops = { + .start = ac108_start, + .stop = ac108_stop, + .pointer = ac108_pointer, + .transfer = ac108_transfer, + .poll_descriptors_count = ac108_poll_descriptors_count, + .poll_descriptors = ac108_poll_descriptors, + .poll_revents = ac108_poll_revents, + .close = ac108_close, + .hw_params = ac108_hw_params, + .hw_free = ac108_hw_free, +// .sw_params = ac108_sw_params, + .prepare = ac108_prepare, + .drain = ac108_drain, + .delay = ac108_delay, +}; + + +static int ac108_set_hw_constraint(struct ac108_t *capture) { + static unsigned int accesses[] = { + SND_PCM_ACCESS_RW_INTERLEAVED + }; + unsigned int formats[] = { SND_PCM_FORMAT_S32, + SND_PCM_FORMAT_S16 }; + + unsigned int rates[] = { + 8000, + 16000, + 32000, + 44100, + 48000, + 96000 + }; + int err; + + + err = snd_pcm_ioplug_set_param_list(&capture->io, + SND_PCM_IOPLUG_HW_ACCESS, + ARRAY_SIZE(accesses), + accesses); + if (err < 0){ + SNDERR("ioplug cannot set ac108 hw access"); + return err; + } + + if ((err = snd_pcm_ioplug_set_param_list(&capture->io, SND_PCM_IOPLUG_HW_FORMAT, + ARRAY_SIZE(formats), formats)) < 0 || + (err = snd_pcm_ioplug_set_param_minmax(&capture->io, SND_PCM_IOPLUG_HW_CHANNELS, + 1, 4)) < 0 || + (err = snd_pcm_ioplug_set_param_list(&capture->io, SND_PCM_IOPLUG_HW_RATE, + ARRAY_SIZE(rates), rates)) < 0) + { + SNDERR("ioplug cannot set ac108 format channel rate!"); + return err; + } + err = snd_pcm_ioplug_set_param_minmax(&capture->io,SND_PCM_IOPLUG_HW_BUFFER_BYTES, + 1, 4 * 1024 * 1024); + if (err < 0){ + SNDERR("ioplug cannot set ac108 hw buffer bytes"); + return err; + } + + err = snd_pcm_ioplug_set_param_minmax(&capture->io,SND_PCM_IOPLUG_HW_PERIOD_BYTES, + 128, 2 * 1024 * 1024); + if (err < 0) { + SNDERR("ioplug cannot set ac108 hw period bytes"); + return err; + } + + err = snd_pcm_ioplug_set_param_minmax(&capture->io, SND_PCM_IOPLUG_HW_PERIODS,3, 1024); + if (err < 0) { + SNDERR("ioplug cannot set ac108 hw periods"); + return err; + } + return 0; +} + +/* + * Main entry point + */ +SND_PCM_PLUGIN_DEFINE_FUNC(ac108) { + snd_config_iterator_t i, next; + int err; + const char *pcm_string = NULL; + struct ac108_t *capture; + int channels; + if (stream != SND_PCM_STREAM_CAPTURE) { + SNDERR("a108 is only for capture"); + return -EINVAL; + } + + snd_config_for_each(i, next, conf) { + snd_config_t *n = snd_config_iterator_entry(i); + const char *id; + if (snd_config_get_id(n, &id) < 0) continue; + if (strcmp(id, "comment") == 0 || strcmp(id, "type") == 0 || strcmp(id, "hint") == 0) continue; + + if (strcmp(id, "slavepcm") == 0) { + if (snd_config_get_string(n, &pcm_string) < 0) { + SNDERR("ac108 slavepcm must be a string"); + return -EINVAL; + } + continue; + } + + if (strcmp(id, "channels") == 0) { + long val; + if (snd_config_get_integer(n, &val) < 0) { + SNDERR("Invalid type for %s", id); + return -EINVAL; + } + channels = val; + if (channels != 2 && channels != 4 && channels != 6) { + SNDERR("channels must be 2, 4 or 6"); + return -EINVAL; + } + continue; + } + } + + + capture = calloc(1, sizeof(*capture)); + if (!capture) { + SNDERR("cannot allocate"); + return -ENOMEM; + } + err = snd_pcm_open(&capture->pcm, pcm_string, stream, mode); + if (err < 0) goto error; + + + + //SND_PCM_NONBLOCK + capture->io.version = SND_PCM_IOPLUG_VERSION; + capture->io.name = "AC108 decode Plugin"; + capture->io.mmap_rw = 0; + capture->io.callback = &a108_ops; + capture->io.private_data = capture; + + err = snd_pcm_ioplug_create(&capture->io, name, stream, mode); + if (err < 0) goto error; + + if ((err = ac108_set_hw_constraint(capture)) < 0) { + snd_pcm_ioplug_delete(&capture->io); + return err; + } + *pcmp = capture->io.pcm; + return 0; + +error: + if (capture->pcm) snd_pcm_close(capture->pcm); + free(capture); + return err; +} + +SND_PCM_PLUGIN_SYMBOL(ac108); diff --git a/seeed-voicecard/ac10x.h b/seeed-voicecard/ac10x.h new file mode 100644 index 0000000..a41bacf --- /dev/null +++ b/seeed-voicecard/ac10x.h @@ -0,0 +1,126 @@ +/* + * ac10x.h + * + * (C) Copyright 2017-2018 + * Seeed Technology Co., Ltd. + * + * PeterYang + * + * (C) Copyright 2010-2017 + * Reuuimlla Technology Co., Ltd. + * huangxin + * + * some simple description for this code + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + */ +#ifndef __AC10X_H__ +#define __AC10X_H__ + +#define AC101_I2C_ID 4 +#define _MASTER_AC108 0 +#define _MASTER_AC101 1 +#define _MASTER_MULTI_CODEC _MASTER_AC101 + +/* enable headset detecting & headset button pressing */ +#define CONFIG_AC101_SWITCH_DETECT + +/* obsolete */ +#define CONFIG_AC10X_TRIG_LOCK 0 + + +#ifdef AC101_DEBG + #define AC101_DBG(format,args...) printk("[AC101] %s() L%d " format, __func__, __LINE__, ##args) +#else + #define AC101_DBG(...) +#endif + + +#include "sound-compatible-4.18.h" + +#ifdef CONFIG_AC101_SWITCH_DETECT +enum headphone_mode_u { + HEADPHONE_IDLE, + FOUR_HEADPHONE_PLUGIN, + THREE_HEADPHONE_PLUGIN, +}; +#endif + +struct ac10x_priv { + struct i2c_client *i2c[4]; + struct regmap* i2cmap[4]; + int codec_cnt; + unsigned sysclk; +#define _FREQ_24_576K 24576000 +#define _FREQ_22_579K 22579200 + unsigned mclk; /* master clock or aif_clock/aclk */ + int clk_id; + unsigned char i2s_mode; + unsigned char data_protocol; + struct delayed_work dlywork; + int tdm_chips_cnt; + int sysclk_en; + + /* member for ac101 .begin */ + struct snd_soc_codec *codec; + struct i2c_client *i2c101; + struct regmap* regmap101; + + struct mutex dac_mutex; + u8 dac_enable; + spinlock_t lock; + u8 aif1_clken; + + struct work_struct codec_resume; + struct gpio_desc* gpiod_spk_amp_gate; + + #ifdef CONFIG_AC101_SWITCH_DETECT + struct gpio_desc* gpiod_irq; + long irq; + volatile int irq_cntr; + volatile int pullout_cntr; + volatile int state; + + enum headphone_mode_u mode; + struct work_struct work_switch; + struct work_struct work_clear_irq; + + struct input_dev* inpdev; + #endif + /* member for ac101 .end */ +}; + + +/* AC101 DAI operations */ +int ac101_audio_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *codec_dai); +void ac101_aif_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *codec_dai); +int ac101_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt); +int ac101_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *codec_dai); +int ac101_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai); +int ac101_aif_mute(struct snd_soc_dai *codec_dai, int mute); + +/* codec driver specific */ +int ac101_codec_probe(struct snd_soc_codec *codec); +int ac101_codec_remove(struct snd_soc_codec *codec); +int ac101_codec_suspend(struct snd_soc_codec *codec); +int ac101_codec_resume(struct snd_soc_codec *codec); +int ac101_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level); + +/* i2c device specific */ +int ac101_probe(struct i2c_client *i2c, const struct i2c_device_id *id); +void ac101_shutdown(struct i2c_client *i2c); +int ac101_remove(struct i2c_client *i2c); + +/* seeed voice card export */ +int seeed_voice_card_register_set_clock(int stream, int (*set_clock)(int, struct snd_pcm_substream *, int, struct snd_soc_dai *)); + +int ac10x_fill_regcache(struct device* dev, struct regmap* map); + +#endif//__AC10X_H__ diff --git a/seeed-voicecard/asound_2mic.conf b/seeed-voicecard/asound_2mic.conf new file mode 100644 index 0000000..2e26f0d --- /dev/null +++ b/seeed-voicecard/asound_2mic.conf @@ -0,0 +1,38 @@ +# The IPC key of dmix or dsnoop plugin must be unique +# If 555555 or 666666 is used by other processes, use another one + + +# use samplerate to resample as speexdsp resample is bad +defaults.pcm.rate_converter "samplerate" + +pcm.!default { + type asym + playback.pcm "playback" + capture.pcm "capture" +} + +pcm.playback { + type plug + slave.pcm "dmixed" +} + +pcm.capture { + type plug + slave.pcm "array" +} + +pcm.dmixed { + type dmix + slave.pcm "hw:seeed2micvoicec" + ipc_key 555555 +} + +pcm.array { + type dsnoop + slave { + pcm "hw:seeed2micvoicec" + channels 2 + } + ipc_key 666666 +} + diff --git a/seeed-voicecard/asound_4mic.conf b/seeed-voicecard/asound_4mic.conf new file mode 100644 index 0000000..12d2ab8 --- /dev/null +++ b/seeed-voicecard/asound_4mic.conf @@ -0,0 +1,33 @@ +# The IPC key of dmix or dsnoop plugin must be unique +# If 555555 or 666666 is used by other processes, use another one + +# use samplerate to resample as speexdsp resample is bad +defaults.pcm.rate_converter "samplerate" + +pcm.!default { + type asym + playback.pcm "playback" + capture.pcm "ac108" +} + +pcm.playback { + type plug + slave.pcm "hw:ALSA" +} + +# pcm.dmixed { +# type dmix +# slave.pcm "hw:0,0" +# ipc_key 555555 +# } + +pcm.ac108 { + type plug + slave.pcm "hw:seeed4micvoicec" +} + +# pcm.multiapps { +# type dsnoop +# ac108-slavepcm "hw:1,0" +# ipc_key 666666 +# } diff --git a/seeed-voicecard/asound_6mic.conf b/seeed-voicecard/asound_6mic.conf new file mode 100644 index 0000000..c064c56 --- /dev/null +++ b/seeed-voicecard/asound_6mic.conf @@ -0,0 +1,90 @@ +# The IPC key of dmix or dsnoop plugin must be unique +# If 555555 or 666666 is used by other processes, use another one + +# use samplerate to resample as speexdsp resample is broken +defaults.pcm.rate_converter "samplerate" + +pcm.!default { + type asym + playback.pcm "dmixer" + capture.pcm "ac108" +} + + +pcm.ac108 { + type plug + slave { + rate 48000 + format S32_LE + pcm "hw:seeed8micvoicec" + } +} + +# pcm.multiapps { +# type plug +# slave.pcm { +# type dsnoop +# slave { +# rate 48000 +# format S32_LE +# pcm "hw:seeed8micvoicec" +# } +# ipc_key 666666 +# } +# } + +pcm.dmixer { + type plug + slave { + pcm { + type dmix + ipc_key 555555 + slave { + pcm "hw:seeed8micvoicec" + format S32_LE + channels 8 + } + bindings { + 0 0 + 1 1 + 2 2 + 3 3 + 4 4 + 5 5 + 6 6 + 7 7 + } + } + channels 8 + format S32_LE + rate 48000 + } + ttable.0.0 1 + ttable.1.1 1 + ttable.0.2 1 + ttable.1.3 1 + ttable.0.4 1 + ttable.1.5 1 + ttable.0.6 1 + ttable.1.7 1 +} + +pcm.ac101 { + type plug + slave { + pcm "hw:seeed8micvoicec" + channels 8 + format S32_LE + rate 48000 + } + ttable.0.0 1 + ttable.1.1 1 + ttable.0.2 1 + ttable.1.3 1 + ttable.0.4 1 + ttable.1.5 1 + ttable.0.6 1 + ttable.1.7 1 +} + + diff --git a/seeed-voicecard/builddtbo.sh b/seeed-voicecard/builddtbo.sh new file mode 100755 index 0000000..d6e8009 --- /dev/null +++ b/seeed-voicecard/builddtbo.sh @@ -0,0 +1,10 @@ +#!/bin/sh +#dtoverlay -r seeed-2mic-voicecard +DTC_FLAGS="-b 0 -Wno-unit_address_vs_reg -I dts -O dtb" + +dtc -@ $DTC_FLAGS -o seeed-2mic-voicecard.dtbo seeed-2mic-voicecard-overlay.dts +dtc -@ $DTC_FLAGS -o seeed-4mic-voicecard.dtbo seeed-4mic-voicecard-overlay.dts +dtc -@ $DTC_FLAGS -o seeed-8mic-voicecard.dtbo seeed-8mic-voicecard-overlay.dts + +# cp *.dtbo /boot/overlays +# dtoverlay seeed-2mic-voicecard diff --git a/seeed-voicecard/default.pa b/seeed-voicecard/default.pa new file mode 100644 index 0000000..ed20f40 --- /dev/null +++ b/seeed-voicecard/default.pa @@ -0,0 +1,140 @@ +#!/usr/bin/pulseaudio -nF +# +# This file is part of PulseAudio. +# +# PulseAudio is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# PulseAudio 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 Lesser General Public License +# along with PulseAudio; if not, see . + +# This startup script is used only if PulseAudio is started per-user +# (i.e. not in system mode) + +.fail + +### Automatically restore the volume of streams and devices +load-module module-device-restore +load-module module-stream-restore +load-module module-card-restore + +### Automatically augment property information from .desktop files +### stored in /usr/share/application +load-module module-augment-properties + +### Should be after module-*-restore but before module-*-detect +load-module module-switch-on-port-available + +### Load audio drivers statically +### (it's probably better to not load these drivers manually, but instead +### use module-udev-detect -- see below -- for doing this automatically) +load-module module-alsa-sink device=hw:0,0 +load-module module-alsa-source device=hw:0,0 +#load-module module-oss device="/dev/dsp" sink_name=output source_name=input +#load-module module-oss-mmap device="/dev/dsp" sink_name=output source_name=input +#load-module module-null-sink +#load-module module-pipe-sink + +### Automatically load driver modules depending on the hardware available +#.ifexists module-udev-detect.so +#load-module module-udev-detect +#.else +### Use the static hardware detection module (for systems that lack udev support) +#load-module module-detect +#.endif + +### Automatically connect sink and source if JACK server is present +.ifexists module-jackdbus-detect.so +.nofail +load-module module-jackdbus-detect channels=2 +.fail +.endif + +### Automatically load driver modules for Bluetooth hardware +.ifexists module-bluetooth-policy.so +load-module module-bluetooth-policy +.endif + +.ifexists module-bluetooth-discover.so +load-module module-bluetooth-discover +.endif + +### Load several protocols +.ifexists module-esound-protocol-unix.so +load-module module-esound-protocol-unix +.endif +load-module module-native-protocol-unix + +### Network access (may be configured with paprefs, so leave this commented +### here if you plan to use paprefs) +#load-module module-esound-protocol-tcp +#load-module module-native-protocol-tcp +#load-module module-zeroconf-publish + +### Load the RTP receiver module (also configured via paprefs, see above) +#load-module module-rtp-recv + +### Load the RTP sender module (also configured via paprefs, see above) +#load-module module-null-sink sink_name=rtp format=s16be channels=2 rate=44100 sink_properties="device.description='RTP Multicast Sink'" +#load-module module-rtp-send source=rtp.monitor + +### Load additional modules from GConf settings. This can be configured with the paprefs tool. +### Please keep in mind that the modules configured by paprefs might conflict with manually +### loaded modules. +.ifexists module-gconf.so +.nofail +load-module module-gconf +.fail +.endif + +### Automatically restore the default sink/source when changed by the user +### during runtime +### NOTE: This should be loaded as early as possible so that subsequent modules +### that look up the default sink/source get the right value +load-module module-default-device-restore + +### Automatically move streams to the default sink if the sink they are +### connected to dies, similar for sources +load-module module-rescue-streams + +### Make sure we always have a sink around, even if it is a null sink. +load-module module-always-sink + +### Honour intended role device property +load-module module-intended-roles + +### Automatically suspend sinks/sources that become idle for too long +load-module module-suspend-on-idle + +### If autoexit on idle is enabled we want to make sure we only quit +### when no local session needs us anymore. +.ifexists module-console-kit.so +load-module module-console-kit +.endif +.ifexists module-systemd-login.so +load-module module-systemd-login +.endif + +### Enable positioned event sounds +load-module module-position-event-sounds + +### Cork music/video streams when a phone stream is active +load-module module-role-cork + +### Modules to allow autoloading of filters (such as echo cancellation) +### on demand. module-filter-heuristics tries to determine what filters +### make sense, and module-filter-apply does the heavy-lifting of +### loading modules and rerouting streams. +load-module module-filter-heuristics +load-module module-filter-apply + +### Make some devices default +#set-default-sink output +#set-default-source input diff --git a/seeed-voicecard/dkms.conf b/seeed-voicecard/dkms.conf new file mode 100644 index 0000000..3079ecb --- /dev/null +++ b/seeed-voicecard/dkms.conf @@ -0,0 +1,15 @@ +PACKAGE_NAME="seeed-voicecard" +PACKAGE_VERSION="0.3" +BUILT_MODULE_NAME[0]="snd-soc-wm8960" +BUILT_MODULE_NAME[1]="snd-soc-ac108" +BUILT_MODULE_NAME[2]="snd-soc-seeed-voicecard" +DEST_MODULE_LOCATION[0]="/kernel/sound/soc/codecs" +DEST_MODULE_LOCATION[1]="/kernel/sound/soc/codecs" +DEST_MODULE_LOCATION[2]="/kernel/sound/soc/bcm" +PATCH[0]="back-to-v4.19.diff" +PATCH[1]="back-to-v5.4.diff" +PATCH[2]="back-to-v5.8.diff" +PATCH_MATCH[0]="4\.19\.*" +PATCH_MATCH[1]="5\.4\.*" +PATCH_MATCH[2]="5\.8\.*" +AUTOINSTALL="yes" diff --git a/seeed-voicecard/install.sh b/seeed-voicecard/install.sh new file mode 100755 index 0000000..a8f3279 --- /dev/null +++ b/seeed-voicecard/install.sh @@ -0,0 +1,232 @@ +#!/bin/bash + +# Color +RED='\033[0;31m' +NC='\033[0m' # No Color + +if [[ $EUID -ne 0 ]]; then + echo "This script must be run as root (use sudo)" 1>&2 + exit 1 +fi + +# Check for enough space on /boot volume +boot_line=$(df -h | grep /boot | head -n 1) +if [ "x${boot_line}" = "x" ]; then + echo "Warning: /boot volume not found .." +else + boot_space=$(echo $boot_line | awk '{print $4;}') + free_space=$(echo "${boot_space%?}") + unit="${boot_space: -1}" + if [[ "$unit" = "K" ]]; then + echo "Error: Not enough space left ($boot_space) on /boot" + exit 1 + elif [[ "$unit" = "M" ]]; then + if [ "$free_space" -lt "25" ]; then + echo "Error: Not enough space left ($boot_space) on /boot" + exit 1 + fi + fi +fi + +# +# make sure that we are on something ARM/Raspberry related +# either a bare metal Raspberry or a qemu session with +# Raspberry stuff available +# - check for /boot/overlays +# - dtparam and dtoverlay is available +errorFound=0 +OVERLAYS=/boot/overlays +[ -d /boot/firmware/overlays ] && OVERLAYS=/boot/firmware/overlays + +if [ ! -d $OVERLAYS ] ; then + echo "$OVERLAYS not found or not a directory" 1>&2 + errorFound=1 +fi +# should we also check for alsactl and amixer used in seeed-voicecard? +PATH=$PATH:/opt/vc/bin +for cmd in dtparam dtoverlay ; do + if ! which $cmd &>/dev/null ; then + echo "$cmd not found" 1>&2 + echo "You may need to run ./ubuntu-prerequisite.sh" + errorFound=1 + fi +done +if [ $errorFound = 1 ] ; then + echo "Errors found, exiting." 1>&2 + exit 1 +fi + + +ver="0.3" +uname_r=$(uname -r) + +# we create a dir with this version to ensure that 'dkms remove' won't delete +# the sources during kernel updates +marker="0.0.0" + +_VER_RUN= +function get_kernel_version() { + local ZIMAGE IMG_OFFSET + + _VER_RUN="" + [ -z "$_VER_RUN" ] && { + ZIMAGE=/boot/kernel.img + [ -f /boot/firmware/vmlinuz ] && ZIMAGE=/boot/firmware/vmlinuz + IMG_OFFSET=$(LC_ALL=C grep -abo $'\x1f\x8b\x08\x00' $ZIMAGE | head -n 1 | cut -d ':' -f 1) + _VER_RUN=$(dd if=$ZIMAGE obs=64K ibs=4 skip=$(( IMG_OFFSET / 4)) 2>/dev/null | zcat | grep -a -m1 "Linux version" | strings | awk '{ print $3; }') + } + echo "$_VER_RUN" + return 0 +} + +function check_kernel_headers() { + VER_RUN=$(get_kernel_version) + VER_HDR=$(dpkg -L raspberrypi-kernel-headers | egrep -m1 "/lib/modules/[^-]+/build" | awk -F'/' '{ print $4; }') + [ "X$VER_RUN" == "X$VER_HDR" ] && { + return 0 + } + VER_HDR=$(dpkg -L linux-headers-$VER_RUN | egrep -m1 "/lib/modules/[[:print:]]+/build" | awk -F'/' '{ print $4; }') + [ "X$VER_RUN" == "X$VER_HDR" ] && { + return 0 + } + + # echo RUN=$VER_RUN HDR=$VER_HDR + echo " !!! Your kernel version is $VER_RUN" + echo " Not found *** corresponding *** kernel headers with apt-get." + echo " This may occur if you have ran 'rpi-update'." + echo " Choose *** y *** will revert the kernel to version $VER_HDR then continue." + echo " Choose *** N *** will exit without this driver support, by default." + read -p "Would you like to proceed? (y/N)" -n 1 -r -s + echo + if ! [[ $REPLY =~ ^[Yy]$ ]]; then + exit 1; + fi + + apt-get -y --reinstall install raspberrypi-kernel +} + +# update and install required packages +which apt &>/dev/null +if [[ $? -eq 0 ]]; then + apt update -y + # Raspbian kernel packages + apt-get -y install raspberrypi-kernel-headers raspberrypi-kernel + # Ubuntu kernel packages + apt-get -y install linux-raspi linux-headers-raspi linux-image-raspi + apt-get -y install dkms git i2c-tools libasound2-plugins + # rpi-update checker + check_kernel_headers +fi + +# Arch Linux +which pacman &>/dev/null +if [[ $? -eq 0 ]]; then + pacman -Syu --needed git gcc automake make dkms linux-raspberrypi-headers i2c-tools +fi + +# locate currently installed kernels (may be different to running kernel if +# it's just been updated) +base_ver=$(get_kernel_version) +base_ver=${base_ver%%[-+]*} +#kernels="${base_ver}+ ${base_ver}-v7+ ${base_ver}-v7l+" +kernels=$(uname -r) +kernel_base_ver=${kernels%%[-+]*} + +if [[ "$base_ver" != "$kernel_base_ver" ]] ; then + echo "------------------------------------------------------" + echo -e " ${RED}WARNING${NC} Your loaded kernel version is $kernel_base_ver" + echo " Not matching the updated version $base_ver." + echo " Kernel was updated, but new kernel was not loaded yet" + echo -e " Please ${RED}reboot${NC} your machine AND THEN run this script ${RED}again" + exit 1; +fi + +function install_module { + local _i + + src=$1 + mod=$2 + + if [[ -d /var/lib/dkms/$mod/$ver/$marker ]]; then + rmdir /var/lib/dkms/$mod/$ver/$marker + fi + + if [[ -e /usr/src/$mod-$ver || -e /var/lib/dkms/$mod/$ver ]]; then + dkms remove --force -m $mod -v $ver --all + rm -rf /usr/src/$mod-$ver + fi + + mkdir -p /usr/src/$mod-$ver + cp -a $src/* /usr/src/$mod-$ver/ + + dkms add -m $mod -v $ver + for _i in $kernels; do + dkms build -k $_i -m $mod -v $ver && { + dkms install --force -k $_i -m $mod -v $ver + } + done + + mkdir -p /var/lib/dkms/$mod/$ver/$marker +} + +install_module "./" "seeed-voicecard" + +# install dtbos +cp seeed-2mic-voicecard.dtbo $OVERLAYS +cp seeed-4mic-voicecard.dtbo $OVERLAYS +cp seeed-8mic-voicecard.dtbo $OVERLAYS + +#install alsa plugins +# no need this plugin now +# install -D ac108_plugin/libasound_module_pcm_ac108.so /usr/lib/arm-linux-gnueabihf/alsa-lib/ +rm -f /usr/lib/arm-linux-gnueabihf/alsa-lib/libasound_module_pcm_ac108.so + +#set kernel modules +grep -q "^snd-soc-seeed-voicecard$" /etc/modules || \ + echo "snd-soc-seeed-voicecard" >> /etc/modules +grep -q "^snd-soc-ac108$" /etc/modules || \ + echo "snd-soc-ac108" >> /etc/modules +grep -q "^snd-soc-wm8960$" /etc/modules || \ + echo "snd-soc-wm8960" >> /etc/modules + +#set dtoverlays +CONFIG=/boot/config.txt +[ -f /boot/firmware/usercfg.txt ] && CONFIG=/boot/firmware/usercfg.txt + +sed -i -e 's:#dtparam=i2c_arm=on:dtparam=i2c_arm=on:g' $CONFIG || true +grep -q "^dtoverlay=i2s-mmap$" $CONFIG || \ + echo "dtoverlay=i2s-mmap" >> $CONFIG + + +grep -q "^dtparam=i2s=on$" $CONFIG || \ + echo "dtparam=i2s=on" >> $CONFIG + +#install config files +mkdir /etc/voicecard || true +cp *.conf /etc/voicecard +cp *.state /etc/voicecard + +#create git repo +git_email=$(git config --global --get user.email) +git_name=$(git config --global --get user.name) +if [ "x${git_email}" == "x" ] || [ "x${git_name}" == "x" ] ; then + echo "setup git config" + git config --global user.email "respeaker@seeed.cc" + git config --global user.name "respeaker" +fi +echo "git init" +git --git-dir=/etc/voicecard/.git init +echo "git add --all" +git --git-dir=/etc/voicecard/.git --work-tree=/etc/voicecard/ add --all +echo "git commit -m \"origin configures\"" +git --git-dir=/etc/voicecard/.git --work-tree=/etc/voicecard/ commit -m "origin configures" + +cp seeed-voicecard /usr/bin/ +cp seeed-voicecard.service /lib/systemd/system/ +systemctl enable seeed-voicecard.service +systemctl start seeed-voicecard + +echo "------------------------------------------------------" +echo "Please reboot your raspberry pi to apply all settings" +echo "Enjoy!" +echo "------------------------------------------------------" diff --git a/seeed-voicecard/install_arm64.sh b/seeed-voicecard/install_arm64.sh new file mode 100755 index 0000000..45597e9 --- /dev/null +++ b/seeed-voicecard/install_arm64.sh @@ -0,0 +1,236 @@ +#!/bin/bash + +# Color +RED='\033[0;31m' +NC='\033[0m' # No Color + +if [[ $EUID -ne 0 ]]; then + echo "This script must be run as root (use sudo)" 1>&2 + exit 1 +fi + +# Check for enough space on /boot volume +boot_line=$(df -h | grep /boot | head -n 1) +if [ "x${boot_line}" = "x" ]; then + echo "Warning: /boot volume not found .." +else + boot_space=$(echo $boot_line | awk '{print $4;}') + free_space=$(echo "${boot_space%?}") + unit="${boot_space: -1}" + if [[ "$unit" = "K" ]]; then + echo "Error: Not enough space left ($boot_space) on /boot" + exit 1 + elif [[ "$unit" = "M" ]]; then + if [ "$free_space" -lt "25" ]; then + echo "Error: Not enough space left ($boot_space) on /boot" + exit 1 + fi + fi +fi + +# +# make sure that we are on something ARM/Raspberry related +# either a bare metal Raspberry or a qemu session with +# Raspberry stuff available +# - check for /boot/overlays +# - dtparam and dtoverlay is available +errorFound=0 +OVERLAYS=/boot/overlays +[ -d /boot/firmware/overlays ] && OVERLAYS=/boot/firmware/overlays + +if [ ! -d $OVERLAYS ] ; then + echo "$OVERLAYS not found or not a directory" 1>&2 + errorFound=1 +fi +# should we also check for alsactl and amixer used in seeed-voicecard? +PATH=$PATH:/opt/vc/bin +for cmd in dtparam dtoverlay ; do + if ! which $cmd &>/dev/null ; then + echo "$cmd not found" 1>&2 + echo "You may need to run ./ubuntu-prerequisite.sh" + errorFound=1 + fi +done +if [ $errorFound = 1 ] ; then + echo "Errors found, exiting." 1>&2 + exit 1 +fi + + +ver="0.3" +uname_r=$(uname -r) + +# we create a dir with this version to ensure that 'dkms remove' won't delete +# the sources during kernel updates +marker="0.0.0" + +_VER_RUN= +function get_kernel_version() { + local ZIMAGE IMG_OFFSET + + _VER_RUN="" + [ -z "$_VER_RUN" ] && { + ZIMAGE=/boot/kernel8.img + [ -f /boot/firmware/vmlinuz ] && ZIMAGE=/boot/firmware/vmlinuz + IMG_OFFSET=$(LC_ALL=C grep -abo $'\x1f\x8b\x08\x00' $ZIMAGE | head -n 1 | cut -d ':' -f 1) + _VER_RUN=$(dd if=$ZIMAGE obs=64K ibs=4 skip=$(( IMG_OFFSET / 4)) 2>/dev/null | zcat | grep -a -m1 "Linux version" | strings | awk '{ print $3; }') + } + echo "$_VER_RUN" + return 0 +} + +function check_kernel_headers() { + VER_RUN=$(get_kernel_version) + VER_HDR=$(dpkg -L raspberrypi-kernel-headers | egrep -m1 "/lib/modules/[[:print:]]+/build" | awk -F'/' '{ print $4; }') + echo $VER_RUN + echo $VER_HDR + [ "X$VER_RUN" == "X$VER_HDR" ] && { + return 0 + } + VER_HDR=$(dpkg -L linux-headers-$VER_RUN | egrep -m1 "/lib/modules/[[:print:]]+/build" | awk -F'/' '{ print $4; }') + echo $VER_RUN + echo $VER_HDR + [ "X$VER_RUN" == "X$VER_HDR" ] && { + return 0 + } + + # echo RUN=$VER_RUN HDR=$VER_HDR + echo " !!! Your kernel version is $VER_RUN" + echo " Not found *** corresponding *** kernel headers with apt-get." + echo " This may occur if you have ran 'rpi-update'." + echo " Choose *** y *** will revert the kernel to version $VER_HDR then continue." + echo " Choose *** N *** will exit without this driver support, by default." + read -p "Would you like to proceed? (y/N)" -n 1 -r -s + echo + if ! [[ $REPLY =~ ^[Yy]$ ]]; then + exit 1; + fi + + apt-get -y --reinstall install raspberrypi-kernel +} + +# update and install required packages +which apt &>/dev/null +if [[ $? -eq 0 ]]; then + #apt update -y + # Raspbian kernel packages + apt-get -y install raspberrypi-kernel-headers raspberrypi-kernel + # Ubuntu kernel packages + apt-get -y install linux-raspi linux-headers-raspi linux-image-raspi + apt-get -y install dkms git i2c-tools libasound2-plugins + # rpi-update checker + check_kernel_headers +fi + +# Arch Linux +which pacman &>/dev/null +if [[ $? -eq 0 ]]; then + pacman -Syu --needed git gcc automake make dkms linux-raspberrypi-headers i2c-tools +fi + +# locate currently installed kernels (may be different to running kernel if +# it's just been updated) +base_ver=$(get_kernel_version) +base_ver=${base_ver%%[-+]*} +#kernels="${base_ver}+ ${base_ver}-v7+ ${base_ver}-v7l+" +kernels=$(uname -r) +kernel_base_ver=${kernels%%[-+]*} + +if [[ "$base_ver" != "$kernel_base_ver" ]] ; then + echo "------------------------------------------------------" + echo -e " ${RED}WARNING${NC} Your loaded kernel version is $kernel_base_ver" + echo " Not matching the updated version $base_ver." + echo " Kernel was updated, but new kernel was not loaded yet" + echo -e " Please ${RED}reboot${NC} your machine AND THEN run this script ${RED}again" + exit 1; +fi + +function install_module { + local _i + + src=$1 + mod=$2 + + if [[ -d /var/lib/dkms/$mod/$ver/$marker ]]; then + rmdir /var/lib/dkms/$mod/$ver/$marker + fi + + if [[ -e /usr/src/$mod-$ver || -e /var/lib/dkms/$mod/$ver ]]; then + dkms remove --force -m $mod -v $ver --all + rm -rf /usr/src/$mod-$ver + fi + + mkdir -p /usr/src/$mod-$ver + cp -a $src/* /usr/src/$mod-$ver/ + + dkms add -m $mod -v $ver + for _i in $kernels; do + dkms build -k $_i -m $mod -v $ver && { + dkms install --force -k $_i -m $mod -v $ver + } + done + + mkdir -p /var/lib/dkms/$mod/$ver/$marker +} + +install_module "./" "seeed-voicecard" + +# install dtbos +cp seeed-2mic-voicecard.dtbo $OVERLAYS +cp seeed-4mic-voicecard.dtbo $OVERLAYS +cp seeed-8mic-voicecard.dtbo $OVERLAYS + +#install alsa plugins +# no need this plugin now +# install -D ac108_plugin/libasound_module_pcm_ac108.so /usr/lib/arm-linux-gnueabihf/alsa-lib/ +rm -f /usr/lib/arm-linux-gnueabihf/alsa-lib/libasound_module_pcm_ac108.so + +#set kernel modules +grep -q "^snd-soc-seeed-voicecard$" /etc/modules || \ + echo "snd-soc-seeed-voicecard" >> /etc/modules +grep -q "^snd-soc-ac108$" /etc/modules || \ + echo "snd-soc-ac108" >> /etc/modules +grep -q "^snd-soc-wm8960$" /etc/modules || \ + echo "snd-soc-wm8960" >> /etc/modules + +#set dtoverlays +CONFIG=/boot/config.txt +[ -f /boot/firmware/usercfg.txt ] && CONFIG=/boot/firmware/usercfg.txt + +sed -i -e 's:#dtparam=i2c_arm=on:dtparam=i2c_arm=on:g' $CONFIG || true +grep -q "^dtoverlay=i2s-mmap$" $CONFIG || \ + echo "dtoverlay=i2s-mmap" >> $CONFIG + + +grep -q "^dtparam=i2s=on$" $CONFIG || \ + echo "dtparam=i2s=on" >> $CONFIG + +#install config files +mkdir /etc/voicecard || true +cp *.conf /etc/voicecard +cp *.state /etc/voicecard + +#create git repo +git_email=$(git config --global --get user.email) +git_name=$(git config --global --get user.name) +if [ "x${git_email}" == "x" ] || [ "x${git_name}" == "x" ] ; then + echo "setup git config" + git config --global user.email "respeaker@seeed.cc" + git config --global user.name "respeaker" +fi +echo "git init" +git --git-dir=/etc/voicecard/.git init +echo "git add --all" +git --git-dir=/etc/voicecard/.git --work-tree=/etc/voicecard/ add --all +echo "git commit -m \"origin configures\"" +git --git-dir=/etc/voicecard/.git --work-tree=/etc/voicecard/ commit -m "origin configures" + +cp seeed-voicecard /usr/bin/ +cp seeed-voicecard.service /lib/systemd/system/ +systemctl enable seeed-voicecard.service +systemctl start seeed-voicecard + +echo "------------------------------------------------------" +echo "Please reboot your raspberry pi to apply all settings" +echo "Enjoy!" +echo "------------------------------------------------------" diff --git a/seeed-voicecard/patches/back-to-v4.19.diff b/seeed-voicecard/patches/back-to-v4.19.diff new file mode 100644 index 0000000..c9c7321 --- /dev/null +++ b/seeed-voicecard/patches/back-to-v4.19.diff @@ -0,0 +1,454 @@ +diff --git a/ac101.c b/ac101.c +index 23837a7..41c15f3 100644 +--- a/ac101.c ++++ b/ac101.c +@@ -955,10 +955,10 @@ void ac101_aif_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai + + AC101_DBG("stream = %s, play: %d, capt: %d, active: %d\n", + snd_pcm_stream_str(substream), +- codec_dai->stream_active[SNDRV_PCM_STREAM_PLAYBACK], codec_dai->stream_active[SNDRV_PCM_STREAM_CAPTURE], +- snd_soc_dai_active(codec_dai)); ++ codec_dai->playback_active, codec_dai->capture_active, ++ codec_dai->active); + +- if (!snd_soc_dai_active(codec_dai)) { ++ if (!codec_dai->active) { + ac10x->aif1_clken = 1; + ac101_aif1clk(codec, SND_SOC_DAPM_POST_PMD, 0); + } else { +@@ -1080,7 +1080,7 @@ int ac101_hw_params(struct snd_pcm_substream *substream, + freq_out = _FREQ_24_576K; + for (i = 0; i < ARRAY_SIZE(codec_aif1_fs); i++) { + if (codec_aif1_fs[i].samp_rate == params_rate(params)) { +- if (codec_dai->stream_active[SNDRV_PCM_STREAM_CAPTURE] && dmic_used && codec_aif1_fs[i].samp_rate == 44100) { ++ if (codec_dai->capture_active && dmic_used && codec_aif1_fs[i].samp_rate == 44100) { + ac101_update_bits(codec, AIF_SR_CTRL, (0xf<dev, "%s() stream=%s play:%d capt:%d +++\n", __func__, + snd_pcm_stream_str(substream), +- dai->stream_active[SNDRV_PCM_STREAM_PLAYBACK], dai->stream_active[SNDRV_PCM_STREAM_CAPTURE]); ++ dai->playback_active, dai->capture_active); + + if (ac10x->i2c101) { + ret = ac101_hw_params(substream, params, dai); +@@ -664,8 +664,8 @@ static int ac108_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_h + } + } + +- if ((substream->stream == SNDRV_PCM_STREAM_CAPTURE && dai->stream_active[SNDRV_PCM_STREAM_PLAYBACK]) +- || (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && dai->stream_active[SNDRV_PCM_STREAM_CAPTURE])) { ++ if ((substream->stream == SNDRV_PCM_STREAM_CAPTURE && dai->playback_active) ++ || (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && dai->capture_active)) { + /* not configure hw_param twice */ + /* return 0; */ + } +@@ -810,9 +810,6 @@ static int ac108_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int fr + + struct ac10x_priv *ac10x = snd_soc_dai_get_drvdata(dai); + +- if (freq != 24000000 || clk_id != SYSCLK_SRC_PLL) +- dev_warn(dai->dev, "ac108_set_sysclk freq = %d clk = %d\n", freq, clk_id); +- + freq = 24000000; + clk_id = SYSCLK_SRC_PLL; + +@@ -1124,7 +1121,7 @@ void ac108_aif_shutdown(struct snd_pcm_substream *substream, + } + } + +-int ac108_aif_mute(struct snd_soc_dai *dai, int mute, int direction) { ++int ac108_aif_mute(struct snd_soc_dai *dai, int mute) { + struct snd_soc_codec *codec = dai->codec; + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + +@@ -1145,13 +1142,12 @@ static const struct snd_soc_dai_ops ac108_dai_ops = { + .hw_params = ac108_hw_params, + .prepare = ac108_prepare, + .trigger = ac108_trigger, +- .mute_stream = ac108_aif_mute, ++ .digital_mute = ac108_aif_mute, + + /*DAI format configuration*/ + .set_fmt = ac108_set_fmt, + + // .hw_free = ac108_hw_free, +- .no_capture_mute = 1, + }; + + static struct snd_soc_dai_driver ac108_dai0 = { +diff --git a/seeed-voicecard.c b/seeed-voicecard.c +index b90af93..af6db74 100644 +--- a/seeed-voicecard.c ++++ b/seeed-voicecard.c +@@ -28,8 +28,6 @@ + #include + #include "ac10x.h" + +-#define LINUX_VERSION_IS_GEQ(x1,x2,x3) (LINUX_VERSION_CODE >= KERNEL_VERSION(x1,x2,x3)) +- + /* + * single codec: + * 0 - allow multi codec +@@ -42,9 +40,6 @@ struct seeed_card_data { + struct seeed_dai_props { + struct asoc_simple_dai cpu_dai; + struct asoc_simple_dai codec_dai; +- struct snd_soc_dai_link_component cpus; /* single cpu */ +- struct snd_soc_dai_link_component codecs; /* single codec */ +- struct snd_soc_dai_link_component platforms; + unsigned int mclk_fs; + } *dai_props; + unsigned int mclk_fs; +@@ -97,16 +92,16 @@ static int seeed_voice_card_startup(struct snd_pcm_substream *substream) + if (ret) + clk_disable_unprepare(dai_props->cpu_dai.clk); + +- if (asoc_rtd_to_cpu(rtd, 0)->driver->playback.channels_min) { +- priv->channels_playback_default = asoc_rtd_to_cpu(rtd, 0)->driver->playback.channels_min; ++ if (rtd->cpu_dai->driver->playback.channels_min) { ++ priv->channels_playback_default = rtd->cpu_dai->driver->playback.channels_min; + } +- if (asoc_rtd_to_cpu(rtd, 0)->driver->capture.channels_min) { +- priv->channels_capture_default = asoc_rtd_to_cpu(rtd, 0)->driver->capture.channels_min; ++ if (rtd->cpu_dai->driver->capture.channels_min) { ++ priv->channels_capture_default = rtd->cpu_dai->driver->capture.channels_min; + } +- asoc_rtd_to_cpu(rtd, 0)->driver->playback.channels_min = priv->channels_playback_override; +- asoc_rtd_to_cpu(rtd, 0)->driver->playback.channels_max = priv->channels_playback_override; +- asoc_rtd_to_cpu(rtd, 0)->driver->capture.channels_min = priv->channels_capture_override; +- asoc_rtd_to_cpu(rtd, 0)->driver->capture.channels_max = priv->channels_capture_override; ++ rtd->cpu_dai->driver->playback.channels_min = priv->channels_playback_override; ++ rtd->cpu_dai->driver->playback.channels_max = priv->channels_playback_override; ++ rtd->cpu_dai->driver->capture.channels_min = priv->channels_capture_override; ++ rtd->cpu_dai->driver->capture.channels_max = priv->channels_capture_override; + + return ret; + } +@@ -118,10 +113,10 @@ static void seeed_voice_card_shutdown(struct snd_pcm_substream *substream) + struct seeed_dai_props *dai_props = + seeed_priv_to_props(priv, rtd->num); + +- asoc_rtd_to_cpu(rtd, 0)->driver->playback.channels_min = priv->channels_playback_default; +- asoc_rtd_to_cpu(rtd, 0)->driver->playback.channels_max = priv->channels_playback_default; +- asoc_rtd_to_cpu(rtd, 0)->driver->capture.channels_min = priv->channels_capture_default; +- asoc_rtd_to_cpu(rtd, 0)->driver->capture.channels_max = priv->channels_capture_default; ++ rtd->cpu_dai->driver->playback.channels_min = priv->channels_playback_default; ++ rtd->cpu_dai->driver->playback.channels_max = priv->channels_playback_default; ++ rtd->cpu_dai->driver->capture.channels_min = priv->channels_capture_default; ++ rtd->cpu_dai->driver->capture.channels_max = priv->channels_capture_default; + + clk_disable_unprepare(dai_props->cpu_dai.clk); + +@@ -132,8 +127,8 @@ static int seeed_voice_card_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) + { + struct snd_soc_pcm_runtime *rtd = substream->private_data; +- struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); +- struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); ++ struct snd_soc_dai *codec_dai = rtd->codec_dai; ++ struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct seeed_card_data *priv = snd_soc_card_get_drvdata(rtd->card); + struct seeed_dai_props *dai_props = + seeed_priv_to_props(priv, rtd->num); +@@ -197,7 +192,7 @@ static void work_cb_codec_clk(struct work_struct *work) + static int seeed_voice_card_trigger(struct snd_pcm_substream *substream, int cmd) + { + struct snd_soc_pcm_runtime *rtd = substream->private_data; +- struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0); ++ struct snd_soc_dai *dai = rtd->codec_dai; + struct seeed_card_data *priv = snd_soc_card_get_drvdata(rtd->card); + #if CONFIG_AC10X_TRIG_LOCK + unsigned long flags; +@@ -206,7 +201,7 @@ static int seeed_voice_card_trigger(struct snd_pcm_substream *substream, int cmd + + dev_dbg(rtd->card->dev, "%s() stream=%s cmd=%d play:%d, capt:%d\n", + __FUNCTION__, snd_pcm_stream_str(substream), cmd, +- dai->stream_active[SNDRV_PCM_STREAM_PLAYBACK], dai->stream_active[SNDRV_PCM_STREAM_CAPTURE]); ++ dai->playback_active, dai->capture_active); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: +@@ -228,7 +223,7 @@ static int seeed_voice_card_trigger(struct snd_pcm_substream *substream, int cmd + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + /* capture channel resync, if overrun */ +- if (dai->stream_active[SNDRV_PCM_STREAM_CAPTURE] && substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { ++ if (dai->capture_active && substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + break; + } + +@@ -248,7 +243,7 @@ static int seeed_voice_card_trigger(struct snd_pcm_substream *substream, int cmd + + dev_dbg(rtd->card->dev, "%s() stream=%s cmd=%d play:%d, capt:%d;finished %d\n", + __FUNCTION__, snd_pcm_stream_str(substream), cmd, +- dai->stream_active[SNDRV_PCM_STREAM_PLAYBACK], dai->stream_active[SNDRV_PCM_STREAM_CAPTURE], ret); ++ dai->playback_active, dai->capture_active, ret); + + return ret; + } +@@ -387,8 +382,8 @@ static int asoc_simple_init_dai_link_params(struct snd_soc_pcm_runtime *rtd) + static int seeed_voice_card_dai_init(struct snd_soc_pcm_runtime *rtd) + { + struct seeed_card_data *priv = snd_soc_card_get_drvdata(rtd->card); +- struct snd_soc_dai *codec = asoc_rtd_to_codec(rtd, 0); +- struct snd_soc_dai *cpu = asoc_rtd_to_cpu(rtd, 0); ++ struct snd_soc_dai *codec = rtd->codec_dai; ++ struct snd_soc_dai *cpu = rtd->cpu_dai; + struct seeed_dai_props *dai_props = + seeed_priv_to_props(priv, rtd->num); + int ret; +@@ -453,19 +448,20 @@ static int seeed_voice_card_dai_link_of(struct device_node *node, + goto dai_link_of_err; + } + +- ret = asoc_simple_parse_daifmt(dev, node, codec, ++ ret = asoc_simple_card_parse_daifmt(dev, node, codec, + prefix, &dai_link->dai_fmt); + if (ret < 0) + goto dai_link_of_err; + + of_property_read_u32(node, "mclk-fs", &dai_props->mclk_fs); + +- ret = asoc_simple_parse_cpu(cpu, dai_link, &single_cpu); ++ ret = asoc_simple_card_parse_cpu(cpu, dai_link, ++ DAI, CELL, &single_cpu); + if (ret < 0) + goto dai_link_of_err; + + #if _SINGLE_CODEC +- ret = asoc_simple_parse_codec(codec, dai_link); ++ ret = asoc_simple_card_parse_codec(codec, dai_link, DAI, CELL); + if (ret < 0) + goto dai_link_of_err; + #else +@@ -477,7 +473,7 @@ static int seeed_voice_card_dai_link_of(struct device_node *node, + dev_dbg(dev, "dai_link num_codecs = %d\n", dai_link->num_codecs); + #endif + +- ret = asoc_simple_parse_platform(plat, dai_link); ++ ret = asoc_simple_card_parse_platform(plat, dai_link, DAI, CELL); + if (ret < 0) + goto dai_link_of_err; + +@@ -502,7 +498,7 @@ static int seeed_voice_card_dai_link_of(struct device_node *node, + #if LINUX_VERSION_CODE <= KERNEL_VERSION(4,10,0) + ret = asoc_simple_card_parse_clk_cpu(cpu, dai_link, cpu_dai); + #else +- ret = asoc_simple_parse_clk_cpu(dev, cpu, dai_link, cpu_dai); ++ ret = asoc_simple_card_parse_clk_cpu(dev, cpu, dai_link, cpu_dai); + #endif + if (ret < 0) + goto dai_link_of_err; +@@ -510,16 +506,16 @@ static int seeed_voice_card_dai_link_of(struct device_node *node, + #if LINUX_VERSION_CODE <= KERNEL_VERSION(4,10,0) + ret = asoc_simple_card_parse_clk_codec(codec, dai_link, codec_dai); + #else +- ret = asoc_simple_parse_clk_codec(dev, codec, dai_link, codec_dai); ++ ret = asoc_simple_card_parse_clk_codec(dev, codec, dai_link, codec_dai); + #endif + if (ret < 0) + goto dai_link_of_err; + +- ret = asoc_simple_set_dailink_name(dev, dai_link, ++ ret = asoc_simple_card_set_dailink_name(dev, dai_link, + "%s-%s", +- dai_link->cpus->dai_name, ++ dai_link->cpu_dai_name, + #if _SINGLE_CODEC +- dai_link->codecs->dai_name ++ dai_link->codec_dai_name + #else + dai_link->codecs[0].dai_name + #endif +@@ -533,19 +529,21 @@ static int seeed_voice_card_dai_link_of(struct device_node *node, + dev_dbg(dev, "\tname : %s\n", dai_link->stream_name); + dev_dbg(dev, "\tformat : %04x\n", dai_link->dai_fmt); + dev_dbg(dev, "\tcpu : %s / %d\n", +- dai_link->cpus->dai_name, ++ dai_link->cpu_dai_name, + dai_props->cpu_dai.sysclk); + dev_dbg(dev, "\tcodec : %s / %d\n", + #if _SINGLE_CODEC +- dai_link->codecs->dai_name, ++ dai_link->codec_dai_name, + #else + dai_link->codecs[0].dai_name, + #endif + dai_props->codec_dai.sysclk); + +- asoc_simple_canonicalize_cpu(dai_link, single_cpu); ++ asoc_simple_card_canonicalize_cpu(dai_link, single_cpu); + #if _SINGLE_CODEC +- asoc_simple_canonicalize_platform(dai_link); ++ ret = asoc_simple_card_canonicalize_dailink(dai_link); ++ if (ret < 0) ++ goto dai_link_of_err; + #endif + + dai_link_of_err: +@@ -578,7 +576,7 @@ static int seeed_voice_card_parse_aux_devs(struct device_node *node, + aux_node = of_parse_phandle(node, PREFIX "aux-devs", i); + if (!aux_node) + return -EINVAL; +- priv->snd_card.aux_dev[i].dlc.of_node = aux_node; ++ priv->snd_card.aux_dev[i].codec_of_node = aux_node; + } + + priv->snd_card.num_aux_devs = n; +@@ -638,7 +636,7 @@ static int seeed_voice_card_parse_of(struct device_node *node, + goto card_parse_end; + } + +- ret = asoc_simple_parse_card_name(&priv->snd_card, PREFIX); ++ ret = asoc_simple_card_parse_card_name(&priv->snd_card, PREFIX); + if (ret < 0) + goto card_parse_end; + +@@ -743,7 +741,7 @@ static int seeed_voice_card_probe(struct platform_device *pdev) + struct seeed_dai_props *dai_props; + struct device_node *np = pdev->dev.of_node; + struct device *dev = &pdev->dev; +- int num, ret, i; ++ int num, ret; + + /* Get the number of DAI links */ + if (np && of_get_child_by_name(np, PREFIX "dai-link")) +@@ -761,25 +759,6 @@ static int seeed_voice_card_probe(struct platform_device *pdev) + if (!dai_props || !dai_link) + return -ENOMEM; + +- /* +- * Use snd_soc_dai_link_component instead of legacy style +- * It is codec only. but cpu/platform will be supported in the future. +- * see +- * soc-core.c :: snd_soc_init_multicodec() +- * +- * "platform" might be removed +- * see +- * simple-card-utils.c :: asoc_simple_canonicalize_platform() +- */ +- for (i = 0; i < num; i++) { +- dai_link[i].cpus = &dai_props[i].cpus; +- dai_link[i].num_cpus = 1; +- dai_link[i].codecs = &dai_props[i].codecs; +- dai_link[i].num_codecs = 1; +- dai_link[i].platforms = &dai_props[i].platforms; +- dai_link[i].num_platforms = 1; +- } +- + priv->dai_props = dai_props; + priv->dai_link = dai_link; + +@@ -798,9 +777,6 @@ static int seeed_voice_card_probe(struct platform_device *pdev) + } + } else { + struct seeed_card_info *cinfo; +- struct snd_soc_dai_link_component *cpus; +- struct snd_soc_dai_link_component *codecs; +- struct snd_soc_dai_link_component *platform; + + cinfo = dev->platform_data; + if (!cinfo) { +@@ -817,19 +793,13 @@ static int seeed_voice_card_probe(struct platform_device *pdev) + return -EINVAL; + } + +- cpus = dai_link->cpus; +- cpus->dai_name = cinfo->cpu_dai.name; +- +- codecs = dai_link->codecs; +- codecs->name = cinfo->codec; +- codecs->dai_name = cinfo->codec_dai.name; +- +- platform = dai_link->platforms; +- platform->name = cinfo->platform; +- + priv->snd_card.name = (cinfo->card) ? cinfo->card : cinfo->name; + dai_link->name = cinfo->name; + dai_link->stream_name = cinfo->name; ++ dai_link->platform_name = cinfo->platform; ++ dai_link->codec_name = cinfo->codec; ++ dai_link->cpu_dai_name = cinfo->cpu_dai.name; ++ dai_link->codec_dai_name = cinfo->codec_dai.name; + dai_link->dai_fmt = cinfo->daifmt; + dai_link->init = seeed_voice_card_dai_init; + memcpy(&priv->dai_props->cpu_dai, &cinfo->cpu_dai, +@@ -853,7 +823,7 @@ static int seeed_voice_card_probe(struct platform_device *pdev) + return ret; + + err: +- asoc_simple_clean_reference(&priv->snd_card); ++ asoc_simple_card_clean_reference(&priv->snd_card); + + return ret; + } +@@ -865,7 +835,7 @@ static int seeed_voice_card_remove(struct platform_device *pdev) + + if (cancel_work_sync(&priv->work_codec_clk) != 0) { + } +- return asoc_simple_clean_reference(card); ++ return asoc_simple_card_clean_reference(card); + } + + static const struct of_device_id seeed_voice_of_match[] = { +diff --git a/sound-compatible-4.18.h b/sound-compatible-4.18.h +index 550b3a7..6c1a014 100644 +--- a/sound-compatible-4.18.h ++++ b/sound-compatible-4.18.h +@@ -16,6 +16,10 @@ + #endif + + #if LINUX_VERSION_CODE < KERNEL_VERSION(5,4,0) ++#ifndef __has_attribute ++# define __has_attribute(x) __GCC4_has_attribute_##x ++# define __GCC4_has_attribute___fallthrough__ 0 ++#endif + #if __has_attribute(__fallthrough__) + # define fallthrough __attribute__((__fallthrough__)) + #else +@@ -31,11 +35,7 @@ + #define snd_soc_codec_get_dapm snd_soc_component_get_dapm + #define snd_soc_codec_get_bias_level snd_soc_component_get_bias_level + #define snd_soc_kcontrol_codec snd_soc_kcontrol_component +-#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,9,0) +-#define snd_soc_read snd_soc_component_read +-#else + #define snd_soc_read snd_soc_component_read32 +-#endif + #define snd_soc_register_codec devm_snd_soc_register_component + #define snd_soc_unregister_codec snd_soc_unregister_component + #define snd_soc_update_bits snd_soc_component_update_bits +diff --git a/wm8960.c b/wm8960.c +index 465c6dc..34d4dad 100644 +--- a/wm8960.c ++++ b/wm8960.c +@@ -796,7 +796,7 @@ static int wm8960_hw_free(struct snd_pcm_substream *substream, + return 0; + } + +-static int wm8960_mute(struct snd_soc_dai *dai, int mute, int direction) ++static int wm8960_mute(struct snd_soc_dai *dai, int mute) + { + struct snd_soc_codec *codec = dai->codec; + +@@ -1236,12 +1236,11 @@ static int wm8960_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + static const struct snd_soc_dai_ops wm8960_dai_ops = { + .hw_params = wm8960_hw_params, + .hw_free = wm8960_hw_free, +- .mute_stream = wm8960_mute, ++ .digital_mute = wm8960_mute, + .set_fmt = wm8960_set_dai_fmt, + .set_clkdiv = wm8960_set_dai_clkdiv, + .set_pll = wm8960_set_dai_pll, + .set_sysclk = wm8960_set_dai_sysclk, +- .no_capture_mute = 1, + }; + + static struct snd_soc_dai_driver wm8960_dai = { diff --git a/seeed-voicecard/patches/back-to-v5.4.diff b/seeed-voicecard/patches/back-to-v5.4.diff new file mode 100644 index 0000000..b7d57a8 --- /dev/null +++ b/seeed-voicecard/patches/back-to-v5.4.diff @@ -0,0 +1,245 @@ +diff --git a/ac101.c b/ac101.c +index be9b1d8..343f030 100644 +--- a/ac101.c ++++ b/ac101.c +@@ -955,10 +955,10 @@ void ac101_aif_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai + + AC101_DBG("stream = %s, play: %d, capt: %d, active: %d\n", + snd_pcm_stream_str(substream), +- codec_dai->stream_active[SNDRV_PCM_STREAM_PLAYBACK], codec_dai->stream_active[SNDRV_PCM_STREAM_CAPTURE], +- snd_soc_dai_active(codec_dai)); ++ codec_dai->playback_active, codec_dai->capture_active, ++ codec_dai->active); + +- if (!snd_soc_dai_active(codec_dai)) { ++ if (!codec_dai->active) { + ac10x->aif1_clken = 1; + ac101_aif1clk(codec, SND_SOC_DAPM_POST_PMD, 0); + } else { +@@ -1080,7 +1080,7 @@ int ac101_hw_params(struct snd_pcm_substream *substream, + freq_out = _FREQ_24_576K; + for (i = 0; i < ARRAY_SIZE(codec_aif1_fs); i++) { + if (codec_aif1_fs[i].samp_rate == params_rate(params)) { +- if (codec_dai->stream_active[SNDRV_PCM_STREAM_CAPTURE] && dmic_used && codec_aif1_fs[i].samp_rate == 44100) { ++ if (codec_dai->capture_active && dmic_used && codec_aif1_fs[i].samp_rate == 44100) { + ac101_update_bits(codec, AIF_SR_CTRL, (0xf<dev, "%s() stream=%s play:%d capt:%d +++\n", __func__, + snd_pcm_stream_str(substream), +- dai->stream_active[SNDRV_PCM_STREAM_PLAYBACK], dai->stream_active[SNDRV_PCM_STREAM_CAPTURE]); ++ dai->playback_active, dai->capture_active); + + if (ac10x->i2c101) { + ret = ac101_hw_params(substream, params, dai); +@@ -664,8 +664,8 @@ static int ac108_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_h + } + } + +- if ((substream->stream == SNDRV_PCM_STREAM_CAPTURE && dai->stream_active[SNDRV_PCM_STREAM_PLAYBACK]) +- || (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && dai->stream_active[SNDRV_PCM_STREAM_CAPTURE])) { ++ if ((substream->stream == SNDRV_PCM_STREAM_CAPTURE && dai->playback_active) ++ || (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && dai->capture_active)) { + /* not configure hw_param twice */ + /* return 0; */ + } +@@ -1124,7 +1124,7 @@ void ac108_aif_shutdown(struct snd_pcm_substream *substream, + } + } + +-int ac108_aif_mute(struct snd_soc_dai *dai, int mute, int direction) { ++int ac108_aif_mute(struct snd_soc_dai *dai, int mute) { + struct snd_soc_codec *codec = dai->codec; + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + +@@ -1145,13 +1145,12 @@ static const struct snd_soc_dai_ops ac108_dai_ops = { + .hw_params = ac108_hw_params, + .prepare = ac108_prepare, + .trigger = ac108_trigger, +- .mute_stream = ac108_aif_mute, ++ .digital_mute = ac108_aif_mute, + + /*DAI format configuration*/ + .set_fmt = ac108_set_fmt, + + // .hw_free = ac108_hw_free, +- .no_capture_mute = 1, + }; + + static struct snd_soc_dai_driver ac108_dai0 = { +diff --git a/seeed-voicecard.c b/seeed-voicecard.c +index c6d9048..43535aa 100644 +--- a/seeed-voicecard.c ++++ b/seeed-voicecard.c +@@ -96,16 +96,16 @@ static int seeed_voice_card_startup(struct snd_pcm_substream *substream) + if (ret) + clk_disable_unprepare(dai_props->cpu_dai.clk); + +- if (asoc_rtd_to_cpu(rtd, 0)->driver->playback.channels_min) { +- priv->channels_playback_default = asoc_rtd_to_cpu(rtd, 0)->driver->playback.channels_min; ++ if (rtd->cpu_dai->driver->playback.channels_min) { ++ priv->channels_playback_default = rtd->cpu_dai->driver->playback.channels_min; + } +- if (asoc_rtd_to_cpu(rtd, 0)->driver->capture.channels_min) { +- priv->channels_capture_default = asoc_rtd_to_cpu(rtd, 0)->driver->capture.channels_min; ++ if (rtd->cpu_dai->driver->capture.channels_min) { ++ priv->channels_capture_default = rtd->cpu_dai->driver->capture.channels_min; + } +- asoc_rtd_to_cpu(rtd, 0)->driver->playback.channels_min = priv->channels_playback_override; +- asoc_rtd_to_cpu(rtd, 0)->driver->playback.channels_max = priv->channels_playback_override; +- asoc_rtd_to_cpu(rtd, 0)->driver->capture.channels_min = priv->channels_capture_override; +- asoc_rtd_to_cpu(rtd, 0)->driver->capture.channels_max = priv->channels_capture_override; ++ rtd->cpu_dai->driver->playback.channels_min = priv->channels_playback_override; ++ rtd->cpu_dai->driver->playback.channels_max = priv->channels_playback_override; ++ rtd->cpu_dai->driver->capture.channels_min = priv->channels_capture_override; ++ rtd->cpu_dai->driver->capture.channels_max = priv->channels_capture_override; + + return ret; + } +@@ -117,10 +117,10 @@ static void seeed_voice_card_shutdown(struct snd_pcm_substream *substream) + struct seeed_dai_props *dai_props = + seeed_priv_to_props(priv, rtd->num); + +- asoc_rtd_to_cpu(rtd, 0)->driver->playback.channels_min = priv->channels_playback_default; +- asoc_rtd_to_cpu(rtd, 0)->driver->playback.channels_max = priv->channels_playback_default; +- asoc_rtd_to_cpu(rtd, 0)->driver->capture.channels_min = priv->channels_capture_default; +- asoc_rtd_to_cpu(rtd, 0)->driver->capture.channels_max = priv->channels_capture_default; ++ rtd->cpu_dai->driver->playback.channels_min = priv->channels_playback_default; ++ rtd->cpu_dai->driver->playback.channels_max = priv->channels_playback_default; ++ rtd->cpu_dai->driver->capture.channels_min = priv->channels_capture_default; ++ rtd->cpu_dai->driver->capture.channels_max = priv->channels_capture_default; + + clk_disable_unprepare(dai_props->cpu_dai.clk); + +@@ -131,8 +131,8 @@ static int seeed_voice_card_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) + { + struct snd_soc_pcm_runtime *rtd = substream->private_data; +- struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); +- struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); ++ struct snd_soc_dai *codec_dai = rtd->codec_dai; ++ struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct seeed_card_data *priv = snd_soc_card_get_drvdata(rtd->card); + struct seeed_dai_props *dai_props = + seeed_priv_to_props(priv, rtd->num); +@@ -196,7 +196,7 @@ static void work_cb_codec_clk(struct work_struct *work) + static int seeed_voice_card_trigger(struct snd_pcm_substream *substream, int cmd) + { + struct snd_soc_pcm_runtime *rtd = substream->private_data; +- struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0); ++ struct snd_soc_dai *dai = rtd->codec_dai; + struct seeed_card_data *priv = snd_soc_card_get_drvdata(rtd->card); + #if CONFIG_AC10X_TRIG_LOCK + unsigned long flags; +@@ -205,7 +205,7 @@ static int seeed_voice_card_trigger(struct snd_pcm_substream *substream, int cmd + + dev_dbg(rtd->card->dev, "%s() stream=%s cmd=%d play:%d, capt:%d\n", + __FUNCTION__, snd_pcm_stream_str(substream), cmd, +- dai->stream_active[SNDRV_PCM_STREAM_PLAYBACK], dai->stream_active[SNDRV_PCM_STREAM_CAPTURE]); ++ dai->playback_active, dai->capture_active); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: +@@ -227,7 +227,7 @@ static int seeed_voice_card_trigger(struct snd_pcm_substream *substream, int cmd + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + /* capture channel resync, if overrun */ +- if (dai->stream_active[SNDRV_PCM_STREAM_CAPTURE] && substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { ++ if (dai->capture_active && substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + break; + } + +@@ -252,7 +247,7 @@ static int seeed_voice_card_trigger(struct snd_pcm_substream *substream, int cmd + + dev_dbg(rtd->card->dev, "%s() stream=%s cmd=%d play:%d, capt:%d;finished %d\n", + __FUNCTION__, snd_pcm_stream_str(substream), cmd, +- dai->stream_active[SNDRV_PCM_STREAM_PLAYBACK], dai->stream_active[SNDRV_PCM_STREAM_CAPTURE], ret); ++ dai->playback_active, dai->capture_active, ret); + + return ret; + } +@@ -337,8 +337,8 @@ static int asoc_simple_init_dai(struct snd_soc_dai *dai, + static int seeed_voice_card_dai_init(struct snd_soc_pcm_runtime *rtd) + { + struct seeed_card_data *priv = snd_soc_card_get_drvdata(rtd->card); +- struct snd_soc_dai *codec = asoc_rtd_to_codec(rtd, 0); +- struct snd_soc_dai *cpu = asoc_rtd_to_cpu(rtd, 0); ++ struct snd_soc_dai *codec = rtd->codec_dai; ++ struct snd_soc_dai *cpu = rtd->cpu_dai; + struct seeed_dai_props *dai_props = + seeed_priv_to_props(priv, rtd->num); + int ret; +@@ -636,11 +636,11 @@ static int seeed_voice_card_probe(struct platform_device *pdev) + * Use snd_soc_dai_link_component instead of legacy style + * It is codec only. but cpu/platform will be supported in the future. + * see +- * soc-core.c :: snd_soc_init_multicodec() ++ * soc-core.c :: snd_soc_init_multicodec() + * + * "platform" might be removed + * see +- * simple-card-utils.c :: asoc_simple_canonicalize_platform() ++ * simple-card-utils.c :: asoc_simple_canonicalize_platform() + */ + for (i = 0; i < num; i++) { + dai_link[i].cpus = &dai_props[i].cpus; +diff --git a/sound-compatible-4.18.h b/sound-compatible-4.18.h +index 080325b..eefa7de 100644 +--- a/sound-compatible-4.18.h ++++ b/sound-compatible-4.18.h +@@ -16,6 +16,10 @@ + #endif + + #if LINUX_VERSION_CODE < KERNEL_VERSION(5,4,0) ++#ifndef __has_attribute ++# define __has_attribute(x) __GCC4_has_attribute_##x ++# define __GCC4_has_attribute___fallthrough__ 0 ++#endif + #if __has_attribute(__fallthrough__) + # define fallthrough __attribute__((__fallthrough__)) + #else +@@ -31,11 +35,7 @@ + #define snd_soc_codec_get_dapm snd_soc_component_get_dapm + #define snd_soc_codec_get_bias_level snd_soc_component_get_bias_level + #define snd_soc_kcontrol_codec snd_soc_kcontrol_component +-#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,9,0) +-#define snd_soc_read snd_soc_component_read +-#else + #define snd_soc_read snd_soc_component_read32 +-#endif + #define snd_soc_register_codec devm_snd_soc_register_component + #define snd_soc_unregister_codec snd_soc_unregister_component + #define snd_soc_update_bits snd_soc_component_update_bits +diff --git a/wm8960.c b/wm8960.c +index 465c6dc..34d4dad 100644 +--- a/wm8960.c ++++ b/wm8960.c +@@ -796,7 +796,7 @@ static int wm8960_hw_free(struct snd_pcm_substream *substream, + return 0; + } + +-static int wm8960_mute(struct snd_soc_dai *dai, int mute, int direction) ++static int wm8960_mute(struct snd_soc_dai *dai, int mute) + { + struct snd_soc_codec *codec = dai->codec; + +@@ -1236,12 +1236,11 @@ static int wm8960_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + static const struct snd_soc_dai_ops wm8960_dai_ops = { + .hw_params = wm8960_hw_params, + .hw_free = wm8960_hw_free, +- .mute_stream = wm8960_mute, ++ .digital_mute = wm8960_mute, + .set_fmt = wm8960_set_dai_fmt, + .set_clkdiv = wm8960_set_dai_clkdiv, + .set_pll = wm8960_set_dai_pll, + .set_sysclk = wm8960_set_dai_sysclk, +- .no_capture_mute = 1, + }; + + static struct snd_soc_dai_driver wm8960_dai = { diff --git a/seeed-voicecard/patches/back-to-v5.8.diff b/seeed-voicecard/patches/back-to-v5.8.diff new file mode 100644 index 0000000..8abb836 --- /dev/null +++ b/seeed-voicecard/patches/back-to-v5.8.diff @@ -0,0 +1,71 @@ +diff --git a/ac108.c b/ac108.c +index 4663df0..67edeae 100644 +--- a/ac108.c ++++ b/ac108.c +@@ -1124,7 +1124,7 @@ void ac108_aif_shutdown(struct snd_pcm_substream *substream, + } + } + +-int ac108_aif_mute(struct snd_soc_dai *dai, int mute, int direction) { ++int ac108_aif_mute(struct snd_soc_dai *dai, int mute) { + struct snd_soc_codec *codec = dai->codec; + struct ac10x_priv *ac10x = snd_soc_codec_get_drvdata(codec); + +@@ -1145,13 +1145,12 @@ static const struct snd_soc_dai_ops ac108_dai_ops = { + .hw_params = ac108_hw_params, + .prepare = ac108_prepare, + .trigger = ac108_trigger, +- .mute_stream = ac108_aif_mute, ++ .digital_mute = ac108_aif_mute, + + /*DAI format configuration*/ + .set_fmt = ac108_set_fmt, + + // .hw_free = ac108_hw_free, +- .no_capture_mute = 1, + }; + + static struct snd_soc_dai_driver ac108_dai0 = { +diff --git a/sound-compatible-4.18.h b/sound-compatible-4.18.h +index 080325b..faed848 100644 +--- a/sound-compatible-4.18.h ++++ b/sound-compatible-4.18.h +@@ -31,11 +31,7 @@ + #define snd_soc_codec_get_dapm snd_soc_component_get_dapm + #define snd_soc_codec_get_bias_level snd_soc_component_get_bias_level + #define snd_soc_kcontrol_codec snd_soc_kcontrol_component +-#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,9,0) +-#define snd_soc_read snd_soc_component_read +-#else + #define snd_soc_read snd_soc_component_read32 +-#endif + #define snd_soc_register_codec devm_snd_soc_register_component + #define snd_soc_unregister_codec snd_soc_unregister_component + #define snd_soc_update_bits snd_soc_component_update_bits +diff --git a/wm8960.c b/wm8960.c +index 465c6dc..34d4dad 100644 +--- a/wm8960.c ++++ b/wm8960.c +@@ -796,7 +796,7 @@ static int wm8960_hw_free(struct snd_pcm_substream *substream, + return 0; + } + +-static int wm8960_mute(struct snd_soc_dai *dai, int mute, int direction) ++static int wm8960_mute(struct snd_soc_dai *dai, int mute) + { + struct snd_soc_codec *codec = dai->codec; + +@@ -1236,12 +1236,11 @@ static int wm8960_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + static const struct snd_soc_dai_ops wm8960_dai_ops = { + .hw_params = wm8960_hw_params, + .hw_free = wm8960_hw_free, +- .mute_stream = wm8960_mute, ++ .digital_mute = wm8960_mute, + .set_fmt = wm8960_set_dai_fmt, + .set_clkdiv = wm8960_set_dai_clkdiv, + .set_pll = wm8960_set_dai_pll, + .set_sysclk = wm8960_set_dai_sysclk, +- .no_capture_mute = 1, + }; + + static struct snd_soc_dai_driver wm8960_dai = { diff --git a/seeed-voicecard/pulseaudio/91-seeedvoicecard.rules b/seeed-voicecard/pulseaudio/91-seeedvoicecard.rules new file mode 100644 index 0000000..8b41dd1 --- /dev/null +++ b/seeed-voicecard/pulseaudio/91-seeedvoicecard.rules @@ -0,0 +1,8 @@ +SUBSYSTEM!="sound", GOTO="seeedvoicecard_end" +ACTION!="change", GOTO="seeedvoicecard_end" +KERNEL!="card*", GOTO="seeedvoicecard_end" + +ATTR{id}=="seeed4micvoicec",ENV{PULSE_PROFILE_SET}="seeed-voicecard-4mic.conf" +ATTR{id}=="seeed8micvoicec",ENV{PULSE_PROFILE_SET}="seeed-voicecard-8mic.conf" + +LABEL="seeedvoicecard_end" \ No newline at end of file diff --git a/seeed-voicecard/pulseaudio/README.md b/seeed-voicecard/pulseaudio/README.md new file mode 100644 index 0000000..11e6f45 --- /dev/null +++ b/seeed-voicecard/pulseaudio/README.md @@ -0,0 +1,251 @@ +# PulseAudio Configuration for seeed-voicecard + +Follow this guide if you want to use your seeed-voicecard as a default source/sink of pulseaudio. + +### Prerequisites + +1. Download PulseAudio +``` +sudo apt install -y pulseaudio +``` + +2. PulseAudio Profiles +``` +cd seeed-voicecard/pulseaudio +sudo cp pulse_config_4mic/seeed-voicecard.conf /usr/share/pulseaudio/alsa-mixer/profile-sets/seeed-voicecard-4mic.conf +sudo cp pulse_config_6mic/seeed-voicecard.conf /usr/share/pulseaudio/alsa-mixer/profile-sets/seeed-voicecard-8mic.conf +``` + +3. Add `udev` Rules + +During the system start, when the card "seeed4micvoicec" is detected, the PULSE_PROFILE_SET variable will be set in the udev database, and PulseAudio will be forced to use `seeed-voicecard-4mic.conf`. Similarly, if the card "seeed8micvoicec" is detected, PulseAudio will be forced to use `seeed-voicecard-8mic.conf`. + +``` +sudo cp 91-seeedvoicecard.rules /etc/udev/rules.d/91-seeedvoicecard.rules +``` + +### ReSpeaker 4 Mic Array + + + +1. config `default.pa` and `daemon.conf` +``` +sudo cp pulse_config_4mic/default.pa /etc/pulse/ +sudo cp pulse_config_4mic/daemon.conf /etc/pulse/ +``` + +2. reboot raspberry pi and check +``` +sudo reboot +pulseaudio --start # start pulse at first +pactl info # check the setting + +Server String: /run/user/1000/pulse/native +Library Protocol Version: 32 +Server Protocol Version: 32 +Is Local: yes +Client Index: 18 +Tile Size: 65496 +User Name: pi +Host Name: raspberrypi +Server Name: pulseaudio +Server Version: 10.0 +Default Sample Specification: s16le 4ch 96000Hz +Default Channel Map: front-left,front-center,front-right,rear-center +Default Sink: alsa_output.platform-soc_audio.analog-stereo +Default Source: alsa_input.platform-soc_sound.seeed-source +Cookie: 3b12:70b3 +``` + +### 6-Mics Circular Array Kit and 4-Mics Linear Array + + + +1. config `default.pa` and `daemon.conf` +``` +sudo cp pulse_config_6mic/default.pa /etc/pulse/ +sudo cp pulse_config_6mic/daemon.conf /etc/pulse/ +``` + +2. reboot raspberry pi and check +``` +sudo reboot +pulseaudio --start # start pulse at first +pactl info # check the setting + +# The output should be like this +# You could see the default sink is seeed-2ch and default source is seeed-8ch +pi@raspberrypi:~ $ pactl info +Server String: /run/user/1000/pulse/native +Library Protocol Version: 32 +Server Protocol Version: 32 +Is Local: yes +Client Index: 6 +Tile Size: 65496 +User Name: pi +Host Name: raspberrypi +Server Name: pulseaudio +Server Version: 10.0 +Default Sample Specification: s32le 8ch 96000Hz +Default Channel Map: front-left,front-left-of-center,front-center,front-right,front-right-of-center,rear-center,aux0,aux1 +Default Sink: alsa_output.platform-soc_sound.seeed-2ch +Default Source: alsa_input.platform-soc_sound.seeed-8ch +Cookie: 3523:e5af +``` + +### FAQ + +1. Default Sink/Source not right + + Make sure there is no any other daemon or using the audio device. Then check profile and udev rules. + + `pacmd list-sinks` and `pacmd list-sources` can be used to show the avaiable sinks/sources, after pulseaudio is started. + +2. Can't start PulseAudio + + Check `default.pa` and `daemon.conf` + +3. How to get PulseAudio started automatically + + Normally the PulseAudio server is started automatically. If you want to disable it, you can set `autospawn = no` in `~/.config/pulse/client.conf` or `/etc/pulse/client.conf`. + [Click this for more details](https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/User/Running/). + +4. Why the default sample rate is 96000? What if my audio's sample rate is not the same as the default? + + For the other sample rate audio, PulseAudio will resample it into 96K, which means that if your audio's sample rate is lower than 96K, it will get smoothing. \ No newline at end of file diff --git a/seeed-voicecard/pulseaudio/pulse_config_4mic/daemon.conf b/seeed-voicecard/pulseaudio/pulse_config_4mic/daemon.conf new file mode 100644 index 0000000..d9c5b93 --- /dev/null +++ b/seeed-voicecard/pulseaudio/pulse_config_4mic/daemon.conf @@ -0,0 +1,87 @@ +# This file is part of PulseAudio. +# +# PulseAudio is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# PulseAudio 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 Lesser General Public License +# along with PulseAudio; if not, see . + +## Configuration file for the PulseAudio daemon. See pulse-daemon.conf(5) for +## more information. Default values are commented out. Use either ; or # for +## commenting. + +; daemonize = no +; fail = yes +; allow-module-loading = yes +; allow-exit = yes +; use-pid-file = yes +; system-instance = no +; local-server-type = user +; enable-shm = yes +; enable-memfd = yes +; shm-size-bytes = 0 # setting this 0 will use the system-default, usually 64 MiB +; lock-memory = no +; cpu-limit = no + +; high-priority = yes +; nice-level = -11 + +; realtime-scheduling = yes +; realtime-priority = 5 + +; exit-idle-time = 20 +; scache-idle-time = 20 + +; dl-search-path = (depends on architecture) + +; load-default-script-file = yes +; default-script-file = /etc/pulse/default.pa + +; log-target = auto +; log-level = notice +; log-meta = no +; log-time = no +; log-backtrace = 0 + +; resample-method = speex-float-1 +; enable-remixing = yes +; enable-lfe-remixing = no +; lfe-crossover-freq = 0 + +; flat-volumes = yes + +; rlimit-fsize = -1 +; rlimit-data = -1 +; rlimit-stack = -1 +; rlimit-core = -1 +; rlimit-as = -1 +; rlimit-rss = -1 +; rlimit-nproc = -1 +; rlimit-nofile = 256 +; rlimit-memlock = -1 +; rlimit-locks = -1 +; rlimit-sigpending = -1 +; rlimit-msgqueue = -1 +; rlimit-nice = 31 +; rlimit-rtprio = 9 +; rlimit-rttime = 200000 + +default-sample-format = s16le +default-sample-rate = 96000 +; alternate-sample-rate = 48000 +default-sample-channels = 4 +; default-channel-map = front-left,front-right + +; default-fragments = 4 +; default-fragment-size-msec = 25 + +; enable-deferred-volume = yes +; deferred-volume-safety-margin-usec = 8000 +; deferred-volume-extra-delay-usec = 0 diff --git a/seeed-voicecard/pulseaudio/pulse_config_4mic/default.pa b/seeed-voicecard/pulseaudio/pulse_config_4mic/default.pa new file mode 100644 index 0000000..12915f5 --- /dev/null +++ b/seeed-voicecard/pulseaudio/pulse_config_4mic/default.pa @@ -0,0 +1,144 @@ +#!/usr/bin/pulseaudio -nF +# +# This file is part of PulseAudio. +# +# PulseAudio is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# PulseAudio 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 Lesser General Public License +# along with PulseAudio; if not, see . + +# This startup script is used only if PulseAudio is started per-user +# (i.e. not in system mode) + +.fail + +### Automatically restore the volume of streams and devices +load-module module-device-restore +load-module module-stream-restore +load-module module-card-restore + +### Automatically augment property information from .desktop files +### stored in /usr/share/application +load-module module-augment-properties + +### Should be after module-*-restore but before module-*-detect +load-module module-switch-on-port-available + +### Load audio drivers statically +### (it's probably better to not load these drivers manually, but instead +### use module-udev-detect -- see below -- for doing this automatically) +#load-module module-alsa-sink device="hw:1,0" channels=8 rate=48000 format=s32le +#load-module module-alsa-source device="hw:1,0" channels=8 rate=48000 format=s32le +#load-module module-oss device="/dev/dsp" sink_name=output source_name=input +#load-module module-oss-mmap device="/dev/dsp" sink_name=output source_name=input +#load-module module-null-sink +#load-module module-pipe-sink + +### Automatically load driver modules depending on the hardware available +.ifexists module-udev-detect.so +load-module module-udev-detect +#channels=8 rate=48000 format=s32le +.else +### Use the static hardware detection module (for systems that lack udev support) +load-module module-detect +.endif + +### Automatically connect sink and source if JACK server is present +.ifexists module-jackdbus-detect.so +.nofail +load-module module-jackdbus-detect channels=2 +.fail +.endif + +### Automatically load driver modules for Bluetooth hardware +.ifexists module-bluetooth-policy.so +load-module module-bluetooth-policy +.endif + +.ifexists module-bluetooth-discover.so +load-module module-bluetooth-discover +.endif + +### Load several protocols +.ifexists module-esound-protocol-unix.so +load-module module-esound-protocol-unix +.endif +load-module module-native-protocol-unix + +### Network access (may be configured with paprefs, so leave this commented +### here if you plan to use paprefs) +#load-module module-esound-protocol-tcp +#load-module module-native-protocol-tcp +#load-module module-zeroconf-publish + +### Load the RTP receiver module (also configured via paprefs, see above) +#load-module module-rtp-recv + +### Load the RTP sender module (also configured via paprefs, see above) +#load-module module-null-sink sink_name=rtp format=s16be channels=2 rate=44100 sink_properties="device.description='RTP Multicast Sink'" +#load-module module-rtp-send source=rtp.monitor + +### Load additional modules from GConf settings. This can be configured with the paprefs tool. +### Please keep in mind that the modules configured by paprefs might conflict with manually +### loaded modules. +.ifexists module-gconf.so +.nofail +load-module module-gconf +.fail +.endif + +### Automatically restore the default sink/source when changed by the user +### during runtime +### NOTE: This should be loaded as early as possible so that subsequent modules +### that look up the default sink/source get the right value +load-module module-default-device-restore + +### Automatically move streams to the default sink if the sink they are +### connected to dies, similar for sources +load-module module-rescue-streams + +### Make sure we always have a sink around, even if it is a null sink. +load-module module-always-sink + +### Honour intended role device property +load-module module-intended-roles + +### Automatically suspend sinks/sources that become idle for too long +load-module module-suspend-on-idle + +### If autoexit on idle is enabled we want to make sure we only quit +### when no local session needs us anymore. +.ifexists module-console-kit.so +load-module module-console-kit +.endif +.ifexists module-systemd-login.so +load-module module-systemd-login +.endif + +### Enable positioned event sounds +load-module module-position-event-sounds + +### Cork music/video streams when a phone stream is active +load-module module-role-cork + +### Modules to allow autoloading of filters (such as echo cancellation) +### on demand. module-filter-heuristics tries to determine what filters +### make sense, and module-filter-apply does the heavy-lifting of +### loading modules and rerouting streams. +load-module module-filter-heuristics +load-module module-filter-apply + +### Make some devices default +#set-default-sink output +#set-default-source input +set-default-source alsa_input.platform-soc_sound.seeed-source +set-default-sink alsa_output.platform-soc_sound.seeed-sink + diff --git a/seeed-voicecard/pulseaudio/pulse_config_4mic/seeed-voicecard.conf b/seeed-voicecard/pulseaudio/pulse_config_4mic/seeed-voicecard.conf new file mode 100644 index 0000000..1c35e17 --- /dev/null +++ b/seeed-voicecard/pulseaudio/pulse_config_4mic/seeed-voicecard.conf @@ -0,0 +1,17 @@ +# /usr/share/pulseaudio/alsa-mixer/profile-sets/seeed-voicecard.conf + +[General] +auto-profiles = no +[Mapping seeed-source] +device-strings = hw:%f +channel-map = front-left,front-right,rear-left,rear-right +exact-channels = false +fallback = yes +paths-input = seeed-source +priority = 3 +direction = input + +[Profile input:seeed-source] +input-mappings = seeed-source +priority = 5 +skip-probe = yes diff --git a/seeed-voicecard/pulseaudio/pulse_config_6mic/daemon.conf b/seeed-voicecard/pulseaudio/pulse_config_6mic/daemon.conf new file mode 100644 index 0000000..89a7c9f --- /dev/null +++ b/seeed-voicecard/pulseaudio/pulse_config_6mic/daemon.conf @@ -0,0 +1,87 @@ +# This file is part of PulseAudio. +# +# PulseAudio is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# PulseAudio 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 Lesser General Public License +# along with PulseAudio; if not, see . + +## Configuration file for the PulseAudio daemon. See pulse-daemon.conf(5) for +## more information. Default values are commented out. Use either ; or # for +## commenting. + +; daemonize = no +; fail = yes +; allow-module-loading = yes +; allow-exit = yes +; use-pid-file = yes +; system-instance = no +; local-server-type = user +; enable-shm = yes +; enable-memfd = yes +; shm-size-bytes = 0 # setting this 0 will use the system-default, usually 64 MiB +; lock-memory = no +; cpu-limit = no + +; high-priority = yes +; nice-level = -11 + +; realtime-scheduling = yes +; realtime-priority = 5 + +; exit-idle-time = 20 +; scache-idle-time = 20 + +; dl-search-path = (depends on architecture) + +; load-default-script-file = yes +; default-script-file = /etc/pulse/default.pa + +; log-target = auto +; log-level = notice +; log-meta = no +; log-time = no +; log-backtrace = 0 + +; resample-method = speex-float-1 +; enable-remixing = yes +; enable-lfe-remixing = no +; lfe-crossover-freq = 0 + +; flat-volumes = yes + +; rlimit-fsize = -1 +; rlimit-data = -1 +; rlimit-stack = -1 +; rlimit-core = -1 +; rlimit-as = -1 +; rlimit-rss = -1 +; rlimit-nproc = -1 +; rlimit-nofile = 256 +; rlimit-memlock = -1 +; rlimit-locks = -1 +; rlimit-sigpending = -1 +; rlimit-msgqueue = -1 +; rlimit-nice = 31 +; rlimit-rtprio = 9 +; rlimit-rttime = 200000 + +default-sample-format = s32le +default-sample-rate = 96000 +; alternate-sample-rate = 48000 +default-sample-channels = 8 +; default-channel-map = front-left,front-right + +; default-fragments = 4 +; default-fragment-size-msec = 25 + +; enable-deferred-volume = yes +; deferred-volume-safety-margin-usec = 8000 +; deferred-volume-extra-delay-usec = 0 diff --git a/seeed-voicecard/pulseaudio/pulse_config_6mic/default.pa b/seeed-voicecard/pulseaudio/pulse_config_6mic/default.pa new file mode 100644 index 0000000..aed78bd --- /dev/null +++ b/seeed-voicecard/pulseaudio/pulse_config_6mic/default.pa @@ -0,0 +1,143 @@ +#!/usr/bin/pulseaudio -nF +# +# This file is part of PulseAudio. +# +# PulseAudio is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# PulseAudio 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 Lesser General Public License +# along with PulseAudio; if not, see . + +# This startup script is used only if PulseAudio is started per-user +# (i.e. not in system mode) + +.fail + +### Automatically restore the volume of streams and devices +load-module module-device-restore +load-module module-stream-restore +load-module module-card-restore + +### Automatically augment property information from .desktop files +### stored in /usr/share/application +load-module module-augment-properties + +### Should be after module-*-restore but before module-*-detect +load-module module-switch-on-port-available + +### Load audio drivers statically +### (it's probably better to not load these drivers manually, but instead +### use module-udev-detect -- see below -- for doing this automatically) +#load-module module-alsa-sink device="hw:1,0" channels=8 rate=48000 format=s32le +#load-module module-alsa-source device="hw:1,0" channels=8 rate=48000 format=s32le +#load-module module-oss device="/dev/dsp" sink_name=output source_name=input +#load-module module-oss-mmap device="/dev/dsp" sink_name=output source_name=input +#load-module module-null-sink +#load-module module-pipe-sink + +### Automatically load driver modules depending on the hardware available +.ifexists module-udev-detect.so +load-module module-udev-detect +#channels=8 rate=48000 format=s32le +.else +### Use the static hardware detection module (for systems that lack udev support) +load-module module-detect +.endif + +### Automatically connect sink and source if JACK server is present +.ifexists module-jackdbus-detect.so +.nofail +load-module module-jackdbus-detect channels=2 +.fail +.endif + +### Automatically load driver modules for Bluetooth hardware +.ifexists module-bluetooth-policy.so +load-module module-bluetooth-policy +.endif + +.ifexists module-bluetooth-discover.so +load-module module-bluetooth-discover +.endif + +### Load several protocols +.ifexists module-esound-protocol-unix.so +load-module module-esound-protocol-unix +.endif +load-module module-native-protocol-unix + +### Network access (may be configured with paprefs, so leave this commented +### here if you plan to use paprefs) +#load-module module-esound-protocol-tcp +#load-module module-native-protocol-tcp +#load-module module-zeroconf-publish + +### Load the RTP receiver module (also configured via paprefs, see above) +#load-module module-rtp-recv + +### Load the RTP sender module (also configured via paprefs, see above) +#load-module module-null-sink sink_name=rtp format=s16be channels=2 rate=44100 sink_properties="device.description='RTP Multicast Sink'" +#load-module module-rtp-send source=rtp.monitor + +### Load additional modules from GConf settings. This can be configured with the paprefs tool. +### Please keep in mind that the modules configured by paprefs might conflict with manually +### loaded modules. +.ifexists module-gconf.so +.nofail +load-module module-gconf +.fail +.endif + +### Automatically restore the default sink/source when changed by the user +### during runtime +### NOTE: This should be loaded as early as possible so that subsequent modules +### that look up the default sink/source get the right value +load-module module-default-device-restore + +### Automatically move streams to the default sink if the sink they are +### connected to dies, similar for sources +load-module module-rescue-streams + +### Make sure we always have a sink around, even if it is a null sink. +load-module module-always-sink + +### Honour intended role device property +load-module module-intended-roles + +### Automatically suspend sinks/sources that become idle for too long +load-module module-suspend-on-idle + +### If autoexit on idle is enabled we want to make sure we only quit +### when no local session needs us anymore. +.ifexists module-console-kit.so +load-module module-console-kit +.endif +.ifexists module-systemd-login.so +load-module module-systemd-login +.endif + +### Enable positioned event sounds +load-module module-position-event-sounds + +### Cork music/video streams when a phone stream is active +load-module module-role-cork + +### Modules to allow autoloading of filters (such as echo cancellation) +### on demand. module-filter-heuristics tries to determine what filters +### make sense, and module-filter-apply does the heavy-lifting of +### loading modules and rerouting streams. +load-module module-filter-heuristics +load-module module-filter-apply + +### Make some devices default +#set-default-sink output +#set-default-source input +set-default-source alsa_input.platform-soc_sound.seeed-8ch +set-default-sink alsa_output.platform-soc_sound.seeed-2ch diff --git a/seeed-voicecard/pulseaudio/pulse_config_6mic/seeed-voicecard.conf b/seeed-voicecard/pulseaudio/pulse_config_6mic/seeed-voicecard.conf new file mode 100644 index 0000000..282a646 --- /dev/null +++ b/seeed-voicecard/pulseaudio/pulse_config_6mic/seeed-voicecard.conf @@ -0,0 +1,34 @@ +# /usr/share/pulseaudio/alsa-mixer/profile-sets/seeed-voiced.conf + +[General] +auto-profiles = no +[Mapping seeed-8ch] +device-strings = hw:%f +channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe,side-left,side-right +exact-channels = false +fallback = yes +paths-input = seeed-8ch +priority = 3 +direction = input +[Mapping seeed-2ch] +device-strings = hw:%f +channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe,side-left,side-right +exact-channels = false +exact-channels = false +fallback = yes +paths-output = seeed-2ch +direction = output +priority = 2 +[Profile output:seeed-2ch+input:seeed-8ch] +output-mappings = seeed-2ch +input-mappings = seeed-8ch +priority = 100 +skip-probe = yes +[Profile output:seeed-2ch] +output-mappings = seeed-2ch +priority = 4 +skip-probe = yes +[Profile input:seeed-8ch] +input-mappings = seeed-8ch +priority = 5 +skip-probe = yes \ No newline at end of file diff --git a/seeed-voicecard/pulseaudio/udev_rules_4mic.png b/seeed-voicecard/pulseaudio/udev_rules_4mic.png new file mode 100644 index 0000000..dd585ec Binary files /dev/null and b/seeed-voicecard/pulseaudio/udev_rules_4mic.png differ diff --git a/seeed-voicecard/pulseaudio/udev_rules_6mic.png b/seeed-voicecard/pulseaudio/udev_rules_6mic.png new file mode 100644 index 0000000..ec2d81f Binary files /dev/null and b/seeed-voicecard/pulseaudio/udev_rules_6mic.png differ diff --git a/seeed-voicecard/seeed-2mic-voicecard-overlay.dts b/seeed-voicecard/seeed-2mic-voicecard-overlay.dts new file mode 100644 index 0000000..1bb5d7f --- /dev/null +++ b/seeed-voicecard/seeed-2mic-voicecard-overlay.dts @@ -0,0 +1,86 @@ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2708"; + + fragment@0 { + target = <&i2s>; + __overlay__ { + status = "okay"; + }; + }; + fragment@1 { + target-path="/"; + __overlay__ { + wm8960_mclk: wm8960_mclk { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <12288000>; + }; + + }; + }; + fragment@2 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + wm8960: wm8960{ + compatible = "wlf,wm8960"; + reg = <0x1a>; + #sound-dai-cells = <0>; + AVDD-supply = <&vdd_5v0_reg>; + DVDD-supply = <&vdd_3v3_reg>; + }; + }; + }; + + + fragment@3 { + target = <&sound>; + slave_overlay: __overlay__ { + compatible = "simple-audio-card"; + simple-audio-card,format = "i2s"; + simple-audio-card,name = "seeed-2mic-voicecard"; + status = "okay"; + simple-audio-card,widgets = + "Microphone", "Mic Jack", + "Line", "Line In", + "Line", "Line Out", + "Speaker", "Speaker", + "Headphone", "Headphone Jack"; + simple-audio-card,routing = + "Headphone Jack", "HP_L", + "Headphone Jack", "HP_R", + "Speaker", "SPK_LP", + "Speaker", "SPK_LN", + "LINPUT1", "Mic Jack", + "LINPUT3", "Mic Jack", + "RINPUT1", "Mic Jack", + "RINPUT2", "Mic Jack"; + + + + + simple-audio-card,cpu { + sound-dai = <&i2s>; + }; + dailink0_slave: simple-audio-card,codec { + sound-dai = <&wm8960>; + clocks = <&wm8960_mclk>; + clock-names = "mclk"; + + }; + }; + }; + + __overrides__ { + alsaname = <&slave_overlay>,"simple-audio-card,name"; + compatible = <&wm8960>,"compatible"; + master = <0>,"=2!3"; + }; +}; + diff --git a/seeed-voicecard/seeed-2mic-voicecard.dtbo b/seeed-voicecard/seeed-2mic-voicecard.dtbo new file mode 100644 index 0000000..904c04d Binary files /dev/null and b/seeed-voicecard/seeed-2mic-voicecard.dtbo differ diff --git a/seeed-voicecard/seeed-4mic-voicecard-overlay.dts b/seeed-voicecard/seeed-4mic-voicecard-overlay.dts new file mode 100644 index 0000000..db0b988 --- /dev/null +++ b/seeed-voicecard/seeed-4mic-voicecard-overlay.dts @@ -0,0 +1,75 @@ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2708"; + + fragment@0 { + target = <&i2s>; + __overlay__ { + #sound-dai-cells = <0>; + status = "okay"; + }; + }; + + fragment@1 { + target-path = "/"; + __overlay__ { + ac108_mclk: codec-mclk { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <24000000>; + }; + }; + }; + + fragment@2 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + ac108_a: ac108@3b{ + compatible = "x-power,ac108_0"; + reg = <0x3b>; + #sound-dai-cells = <0>; + data-protocol = <0>; + }; + }; + }; + + + fragment@3 { + target = <&sound>; + + sound_overlay: __overlay__ { + compatible = "seeed-voicecard"; + seeed-voice-card,format = "dsp_a"; + seeed-voice-card,name = "seeed-4mic-voicecard"; + status = "okay"; + + seeed-voice-card,bitclock-master = <&codec_dai>; + seeed-voice-card,frame-master = <&codec_dai>; + seeed-voice-card,channels-playback-override = <4>; + seeed-voice-card,channels-capture-override = <4>; + + cpu_dai: seeed-voice-card,cpu { + sound-dai = <&i2s>; + dai-tdm-slot-num = <2>; + dai-tdm-slot-width = <32>; + dai-tdm-slot-tx-mask = <1 1 0 0>; + dai-tdm-slot-rx-mask = <1 1 0 0>; + }; + codec_dai: seeed-voice-card,codec { + sound-dai = <&ac108_a>; + clocks = <&ac108_mclk>; + }; + }; + }; + + __overrides__ { + card-name = <&sound_overlay>,"seeed-voice-card,name"; + }; +}; + diff --git a/seeed-voicecard/seeed-4mic-voicecard.dtbo b/seeed-voicecard/seeed-4mic-voicecard.dtbo new file mode 100644 index 0000000..09ee566 Binary files /dev/null and b/seeed-voicecard/seeed-4mic-voicecard.dtbo differ diff --git a/seeed-voicecard/seeed-8mic-voicecard-overlay.dts b/seeed-voicecard/seeed-8mic-voicecard-overlay.dts new file mode 100644 index 0000000..56a1433 --- /dev/null +++ b/seeed-voicecard/seeed-8mic-voicecard-overlay.dts @@ -0,0 +1,118 @@ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2708"; + + fragment@0 { + target = <&i2s>; + __overlay__ { + #sound-dai-cells = <0>; + status = "okay"; + }; + }; + + fragment@1 { + target-path = "/"; + __overlay__ { + ac10x_mclk: codec-mclk { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <24000000>; + }; + }; + }; + + fragment@2 { + target = <&gpio>; + __overlay__ { + spk_amp_pins: spk_pins { + brcm,pins = <17 22>; + brcm,function = <1 0>; /* out in */ + brcm,pull = <0 0>; /* - - */ + }; + gpclk0_pins: gpclk0_pins { + brcm,pins = <4>; + brcm,function = <4>; /* alt func 0 */ + brcm,pull = <0>; /* - */ + }; + }; + }; + + fragment@3 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + ac101: ac101@1a{ + compatible = "x-power,ac101"; + pinctrl-names = "default"; + pinctrl-0 = <&spk_amp_pins &gpclk0_pins>; + spk-amp-switch-gpios = <&gpio 17 0>; + switch-irq-gpios = <&gpio 22 0>; + reg = <0x1a>; + #sound-dai-cells = <0>; + }; + + ac108_a: ac108@35{ + compatible = "x-power,ac108_0"; + reg = <0x35>; + #sound-dai-cells = <0>; + data-protocol = <0>; + tdm-chips-count = <2>; + }; + + ac108_b: ac108@3b{ + compatible = "x-power,ac108_1"; + reg = <0x3b>; + #sound-dai-cells = <0>; + data-protocol = <0>; + tdm-chips-count = <2>; + }; + }; + }; + + fragment@4 { + target = <&sound>; + + sound_overlay: __overlay__ { + compatible = "seeed-voicecard"; + seeed-voice-card,name = "seeed-8mic-voicecard"; + seeed-voice-card,channels-playback-override = <8>; + seeed-voice-card,channels-capture-override = <8>; + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + seeed-voice-card,dai-link@0 { + format = "dsp_a"; + bitclock-master = <&codec0_dai>; + frame-master = <&codec0_dai>; + /* bitclock-inversion; */ + /* frame-inversion; */ + reg = <0>; + + cpu { + sound-dai = <&i2s>; + dai-tdm-slot-num = <2>; + dai-tdm-slot-width = <32>; + dai-tdm-slot-tx-mask = <1 1 0 0>; + dai-tdm-slot-rx-mask = <1 1 0 0>; + }; + + codec0_dai: codec { + sound-dai = <&ac108_a>; + clocks = <&ac10x_mclk>; + system-clock-id = <1>; + }; + }; + }; + }; + + __overrides__ { + card-name = <&sound_overlay>,"seeed-voice-card,name"; + }; +}; + diff --git a/seeed-voicecard/seeed-8mic-voicecard.dtbo b/seeed-voicecard/seeed-8mic-voicecard.dtbo new file mode 100644 index 0000000..2dcaa93 Binary files /dev/null and b/seeed-voicecard/seeed-8mic-voicecard.dtbo differ diff --git a/seeed-voicecard/seeed-voicecard b/seeed-voicecard/seeed-voicecard new file mode 100755 index 0000000..55d6b39 --- /dev/null +++ b/seeed-voicecard/seeed-voicecard @@ -0,0 +1,161 @@ +#!/bin/bash +# +# Copyright (c) 2018 Baozhu Zuo +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +set -x +#exec 1>/var/log/$(basename $0).log 2>&1 + +export PATH=$PATH:/opt/vc/bin +OVERLAYS=/boot/overlays +[ -d /boot/firmware/overlays ] && OVERLAYS=/boot/firmware/overlays + +#enable i2c interface +dtparam -d $OVERLAYS i2c_arm=on +modprobe i2c-dev + +#enable spi interface +dtparam -d $OVERLAYS spi=on + +_VER_RUN= +function get_kernel_version() { + local ZIMAGE IMG_OFFSET + + _VER_RUN="" + [ -z "$_VER_RUN" ] && { + ZIMAGE=/boot/kernel.img + IMG_OFFSET=$(LC_ALL=C grep -abo $'\x1f\x8b\x08\x00' $ZIMAGE | head -n 1 | cut -d ':' -f 1) + _VER_RUN=$(dd if=$ZIMAGE obs=64K ibs=4 skip=$(( IMG_OFFSET / 4)) | zcat | grep -a -m1 "Linux version" | strings | awk '{ print $3; }') + } + echo "$_VER_RUN" + return 0 +} + +CONFIG=/boot/config.txt +[ -f /boot/firmware/usercfg.txt ] && CONFIG=/boot/firmware/usercfg.txt + +get_overlay() { + ov=$1 + if grep -q -E "^dtoverlay=$ov" $CONFIG; then + echo 0 + else + echo 1 + fi +} + +do_overlay() { + ov=$1 + RET=$2 + DEFAULT=--defaultno + CURRENT=0 + if [ $(get_overlay $ov) -eq 0 ]; then + DEFAULT= + CURRENT=1 + fi + if [ $RET -eq $CURRENT ]; then + ASK_TO_REBOOT=1 + fi + if [ $RET -eq 0 ]; then + sed $CONFIG -i -e "s/^#dtoverlay=$ov/dtoverlay=$ov/" + if ! grep -q -E "^dtoverlay=$ov" $CONFIG; then + printf "dtoverlay=$ov\n" >> $CONFIG + fi + STATUS=enabled + elif [ $RET -eq 1 ]; then + sed $CONFIG -i -e "s/^dtoverlay=$ov/#dtoverlay=$ov/" + STATUS=disabled + else + return $RET + fi +} + + +is_1a=$(i2cdetect -y 1 0x1a 0x1a | egrep "(1a|UU)" | awk '{print $2}') +is_35=$(i2cdetect -y 1 0x35 0x35 | egrep "(35|UU)" | awk '{print $2}') +is_3b=$(i2cdetect -y 1 0x3b 0x3b | egrep "(3b|UU)" | awk '{print $2}') + +RPI_HATS="seeed-2mic-voicecard seeed-4mic-voicecard seeed-8mic-voicecard" +overlay="" + +if [ "x${is_1a}" != "x" ] && [ "x${is_35}" == "x" ] ; then + echo "install 2mic" + overlay=seeed-2mic-voicecard + asound_conf=/etc/voicecard/asound_2mic.conf + asound_state=/etc/voicecard/wm8960_asound.state +fi + +if [ "x${is_3b}" != "x" ] && [ "x${is_35}" == "x" ] ; then + echo "install 4mic" + overlay=seeed-4mic-voicecard + asound_conf=/etc/voicecard/asound_4mic.conf + asound_state=/etc/voicecard/ac108_asound.state +fi + +if [ "x${is_3b}" != "x" ] && [ "x${is_35}" != "x" ] ; then + echo "install 6mic" + overlay=seeed-8mic-voicecard + asound_conf=/etc/voicecard/asound_6mic.conf + asound_state=/etc/voicecard/ac108_6mic.state +fi + +if [ "$overlay" ]; then + echo Install $overlay ... + + # Remove old configuration + rm /etc/asound.conf + rm /var/lib/alsa/asound.state + + kernel_ver=$(uname -r) # get_kernel_version) + # echo kernel_ver=$kernel_ver + + # TODO: dynamic dtoverlay Bug of v4.19.x + # no DT node phandle inserted. + if [[ "$kernel_ver" =~ ^4\.19.*$ || "$kernel_ver" =~ ^5\.*$ ]]; then + for i in $RPI_HATS; do + if [ "$i" == "$overlay" ]; then + /bin/true #do_overlay $overlay 0 + else + echo Uninstall $i ... + /bin/true #do_overlay $i 1 + fi + done + fi + #make sure the driver loads correctly + dtoverlay -d $OVERLAYS $overlay || true + + + echo "create $overlay asound configure file" + ln -s $asound_conf /etc/asound.conf + echo "create $overlay asound status file" + ln -s $asound_state /var/lib/alsa/asound.state +fi + +alsactl restore + +#Force 3.5mm ('headphone') jack +# The Raspberry Pi 4, released on 24th Jun 2019, has two HDMI ports, +# and can drive two displays with audios for two users simultaneously, +# in a "multiseat" configuration. The earlier single virtual ALSA +# option for re-directing audio playback between headphone jack and HDMI +# via a 'Routing' mixer setting was turned off eventually to allow +# simultaneous usage of all 3 playback devices. +if aplay -l | grep -q "bcm2835 ALSA"; then + amixer cset numid=3 1 || true +fi diff --git a/seeed-voicecard/seeed-voicecard.c b/seeed-voicecard/seeed-voicecard.c new file mode 100644 index 0000000..ebfd780 --- /dev/null +++ b/seeed-voicecard/seeed-voicecard.c @@ -0,0 +1,892 @@ +/* + * SEEED voice card + * + * (C) Copyright 2017-2018 + * Seeed Technology Co., Ltd. + * + * base on ASoC simple sound card support + * + * Copyright (C) 2012 Renesas Solutions Corp. + * Kuninori Morimoto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +/* #undef DEBUG */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ac10x.h" + +#define LINUX_VERSION_IS_GEQ(x1,x2,x3) (LINUX_VERSION_CODE >= KERNEL_VERSION(x1,x2,x3)) + +/* + * single codec: + * 0 - allow multi codec + * 1 - yes + */ +#define _SINGLE_CODEC 1 + +struct seeed_card_data { + struct snd_soc_card snd_card; + struct seeed_dai_props { + struct asoc_simple_dai cpu_dai; + struct asoc_simple_dai codec_dai; + struct snd_soc_dai_link_component cpus; /* single cpu */ + struct snd_soc_dai_link_component codecs; /* single codec */ + struct snd_soc_dai_link_component platforms; + unsigned int mclk_fs; + } *dai_props; + unsigned int mclk_fs; + unsigned channels_playback_default; + unsigned channels_playback_override; + unsigned channels_capture_default; + unsigned channels_capture_override; + struct snd_soc_dai_link *dai_link; + #if CONFIG_AC10X_TRIG_LOCK + spinlock_t lock; + #endif + struct work_struct work_codec_clk; + #define TRY_STOP_MAX 3 + int try_stop; +}; + +struct seeed_card_info { + const char *name; + const char *card; + const char *codec; + const char *platform; + + unsigned int daifmt; + struct asoc_simple_dai cpu_dai; + struct asoc_simple_dai codec_dai; +}; + +#define seeed_priv_to_card(priv) (&(priv)->snd_card) +#define seeed_priv_to_dev(priv) ((priv)->snd_card.dev) +#define seeed_priv_to_link(priv, i) ((priv)->snd_card.dai_link + (i)) +#define seeed_priv_to_props(priv, i) ((priv)->dai_props + (i)) + +#define DAI "sound-dai" +#define CELL "#sound-dai-cells" +#define PREFIX "seeed-voice-card," + +static int seeed_voice_card_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct seeed_card_data *priv = snd_soc_card_get_drvdata(rtd->card); + struct seeed_dai_props *dai_props = + seeed_priv_to_props(priv, rtd->num); + int ret; + + ret = clk_prepare_enable(dai_props->cpu_dai.clk); + if (ret) + return ret; + + ret = clk_prepare_enable(dai_props->codec_dai.clk); + if (ret) + clk_disable_unprepare(dai_props->cpu_dai.clk); + + if (asoc_rtd_to_cpu(rtd, 0)->driver->playback.channels_min) { + priv->channels_playback_default = asoc_rtd_to_cpu(rtd, 0)->driver->playback.channels_min; + } + if (asoc_rtd_to_cpu(rtd, 0)->driver->capture.channels_min) { + priv->channels_capture_default = asoc_rtd_to_cpu(rtd, 0)->driver->capture.channels_min; + } + asoc_rtd_to_cpu(rtd, 0)->driver->playback.channels_min = priv->channels_playback_override; + asoc_rtd_to_cpu(rtd, 0)->driver->playback.channels_max = priv->channels_playback_override; + asoc_rtd_to_cpu(rtd, 0)->driver->capture.channels_min = priv->channels_capture_override; + asoc_rtd_to_cpu(rtd, 0)->driver->capture.channels_max = priv->channels_capture_override; + + return ret; +} + +static void seeed_voice_card_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct seeed_card_data *priv = snd_soc_card_get_drvdata(rtd->card); + struct seeed_dai_props *dai_props = + seeed_priv_to_props(priv, rtd->num); + + asoc_rtd_to_cpu(rtd, 0)->driver->playback.channels_min = priv->channels_playback_default; + asoc_rtd_to_cpu(rtd, 0)->driver->playback.channels_max = priv->channels_playback_default; + asoc_rtd_to_cpu(rtd, 0)->driver->capture.channels_min = priv->channels_capture_default; + asoc_rtd_to_cpu(rtd, 0)->driver->capture.channels_max = priv->channels_capture_default; + + clk_disable_unprepare(dai_props->cpu_dai.clk); + + clk_disable_unprepare(dai_props->codec_dai.clk); +} + +static int seeed_voice_card_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct seeed_card_data *priv = snd_soc_card_get_drvdata(rtd->card); + struct seeed_dai_props *dai_props = + seeed_priv_to_props(priv, rtd->num); + unsigned int mclk, mclk_fs = 0; + int ret = 0; + + if (priv->mclk_fs) + mclk_fs = priv->mclk_fs; + else if (dai_props->mclk_fs) + mclk_fs = dai_props->mclk_fs; + + if (mclk_fs) { + mclk = params_rate(params) * mclk_fs; + ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, + SND_SOC_CLOCK_IN); + if (ret && ret != -ENOTSUPP) + goto err; + + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, mclk, + SND_SOC_CLOCK_OUT); + if (ret && ret != -ENOTSUPP) + goto err; + } + return 0; +err: + return ret; +} + +#define _SET_CLOCK_CNT 2 +static int (* _set_clock[_SET_CLOCK_CNT])(int y_start_n_stop, struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai); + +int seeed_voice_card_register_set_clock(int stream, int (*set_clock)(int, struct snd_pcm_substream *, int, struct snd_soc_dai *)) { + if (! _set_clock[stream]) { + _set_clock[stream] = set_clock; + } + return 0; +} +EXPORT_SYMBOL(seeed_voice_card_register_set_clock); + +/* + * work_cb_codec_clk: clear audio codec inner clock. + */ +static void work_cb_codec_clk(struct work_struct *work) +{ + struct seeed_card_data *priv = container_of(work, struct seeed_card_data, work_codec_clk); + int r = 0; + + if (_set_clock[SNDRV_PCM_STREAM_CAPTURE]) { + r = r || _set_clock[SNDRV_PCM_STREAM_CAPTURE](0, NULL, 0, NULL); /* not using 2nd to 4th arg if 1st == 0 */ + } + if (_set_clock[SNDRV_PCM_STREAM_PLAYBACK]) { + r = r || _set_clock[SNDRV_PCM_STREAM_PLAYBACK](0, NULL, 0, NULL); /* not using 2nd to 4th arg if 1st == 0 */ + } + + if (r && priv->try_stop++ < TRY_STOP_MAX) { + if (0 != schedule_work(&priv->work_codec_clk)) {} + } + return; +} + +static int seeed_voice_card_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0); + struct seeed_card_data *priv = snd_soc_card_get_drvdata(rtd->card); + #if CONFIG_AC10X_TRIG_LOCK + unsigned long flags; + #endif + int ret = 0; + + dev_dbg(rtd->card->dev, "%s() stream=%s cmd=%d play:%d, capt:%d\n", + __FUNCTION__, snd_pcm_stream_str(substream), cmd, + dai->stream_active[SNDRV_PCM_STREAM_PLAYBACK], dai->stream_active[SNDRV_PCM_STREAM_CAPTURE]); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (cancel_work_sync(&priv->work_codec_clk) != 0) {} + #if CONFIG_AC10X_TRIG_LOCK + /* I know it will degrades performance, but I have no choice */ + spin_lock_irqsave(&priv->lock, flags); + #endif + if (_set_clock[SNDRV_PCM_STREAM_CAPTURE]) _set_clock[SNDRV_PCM_STREAM_CAPTURE](1, substream, cmd, dai); + if (_set_clock[SNDRV_PCM_STREAM_PLAYBACK]) _set_clock[SNDRV_PCM_STREAM_PLAYBACK](1, substream, cmd, dai); + #if CONFIG_AC10X_TRIG_LOCK + spin_unlock_irqrestore(&priv->lock, flags); + #endif + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + /* capture channel resync, if overrun */ + if (dai->stream_active[SNDRV_PCM_STREAM_CAPTURE] && substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + break; + } + + /* interrupt environment */ + if (in_irq() || in_nmi() || in_serving_softirq()) { + priv->try_stop = 0; + if (0 != schedule_work(&priv->work_codec_clk)) { + } + } else { + if (_set_clock[SNDRV_PCM_STREAM_CAPTURE]) _set_clock[SNDRV_PCM_STREAM_CAPTURE](0, NULL, 0, NULL); /* not using 2nd to 4th arg if 1st == 0 */ + if (_set_clock[SNDRV_PCM_STREAM_PLAYBACK]) _set_clock[SNDRV_PCM_STREAM_PLAYBACK](0, NULL, 0, NULL); /* not using 2nd to 4th arg if 1st == 0 */ + } + break; + default: + ret = -EINVAL; + } + + dev_dbg(rtd->card->dev, "%s() stream=%s cmd=%d play:%d, capt:%d;finished %d\n", + __FUNCTION__, snd_pcm_stream_str(substream), cmd, + dai->stream_active[SNDRV_PCM_STREAM_PLAYBACK], dai->stream_active[SNDRV_PCM_STREAM_CAPTURE], ret); + + return ret; +} + +static struct snd_soc_ops seeed_voice_card_ops = { + .startup = seeed_voice_card_startup, + .shutdown = seeed_voice_card_shutdown, + .hw_params = seeed_voice_card_hw_params, + .trigger = seeed_voice_card_trigger, +}; + +static int asoc_simple_parse_dai(struct device_node *node, + struct snd_soc_dai_link_component *dlc, + int *is_single_link) +{ + struct of_phandle_args args; + int ret; + + if (!node) + return 0; + + /* + * Get node via "sound-dai = <&phandle port>" + * it will be used as xxx_of_node on soc_bind_dai_link() + */ + ret = of_parse_phandle_with_args(node, DAI, CELL, 0, &args); + if (ret) + return ret; + + /* + * FIXME + * + * Here, dlc->dai_name is pointer to CPU/Codec DAI name. + * If user unbinded CPU or Codec driver, but not for Sound Card, + * dlc->dai_name is keeping unbinded CPU or Codec + * driver's pointer. + * + * If user re-bind CPU or Codec driver again, ALSA SoC will try + * to rebind Card via snd_soc_try_rebind_card(), but because of + * above reason, it might can't bind Sound Card. + * Because Sound Card is pointing to released dai_name pointer. + * + * To avoid this rebind Card issue, + * 1) It needs to alloc memory to keep dai_name eventhough + * CPU or Codec driver was unbinded, or + * 2) user need to rebind Sound Card everytime + * if he unbinded CPU or Codec. + */ + ret = snd_soc_of_get_dai_name(node, &dlc->dai_name); + if (ret < 0) + return ret; + + dlc->of_node = args.np; + + if (is_single_link) + *is_single_link = !args.args_count; + + return 0; +} + +static int asoc_simple_init_dai(struct snd_soc_dai *dai, + struct asoc_simple_dai *simple_dai) +{ + int ret; + + if (!simple_dai) + return 0; + + if (simple_dai->sysclk) { + ret = snd_soc_dai_set_sysclk(dai, 0, simple_dai->sysclk, + simple_dai->clk_direction); + if (ret && ret != -ENOTSUPP) { + dev_err(dai->dev, "simple-card: set_sysclk error\n"); + return ret; + } + } + + if (simple_dai->slots) { + ret = snd_soc_dai_set_bclk_ratio(dai, + simple_dai->slots * + simple_dai->slot_width); + if (ret && ret != -ENOTSUPP) { + dev_err(dai->dev, "simple-card: set_tdm_slot error\n"); + return ret; + } + } + + return 0; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,7,0) +static int asoc_simple_init_dai_link_params(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai_link *dai_link = rtd->dai_link; + struct snd_soc_component *component; + struct snd_soc_pcm_stream *params; + struct snd_pcm_hardware hw; + int i, ret, stream; + + /* Only codecs should have non_legacy_dai_naming set. */ + for_each_rtd_components(rtd, i, component) { + if (!component->driver->non_legacy_dai_naming) + return 0; + } + + /* Assumes the capabilities are the same for all supported streams */ + for (stream = 0; stream < 2; stream++) { + ret = snd_soc_runtime_calc_hw(rtd, &hw, stream); + if (ret == 0) + break; + } + + if (ret < 0) { + dev_err(rtd->dev, "simple-card: no valid dai_link params\n"); + return ret; + } + + params = devm_kzalloc(rtd->dev, sizeof(*params), GFP_KERNEL); + if (!params) + return -ENOMEM; + + params->formats = hw.formats; + params->rates = hw.rates; + params->rate_min = hw.rate_min; + params->rate_max = hw.rate_max; + params->channels_min = hw.channels_min; + params->channels_max = hw.channels_max; + + dai_link->params = params; + dai_link->num_params = 1; + + return 0; +} +#endif + +static int seeed_voice_card_dai_init(struct snd_soc_pcm_runtime *rtd) +{ + struct seeed_card_data *priv = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *codec = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_dai *cpu = asoc_rtd_to_cpu(rtd, 0); + struct seeed_dai_props *dai_props = + seeed_priv_to_props(priv, rtd->num); + int ret; + + ret = asoc_simple_init_dai(codec, &dai_props->codec_dai); + if (ret < 0) + return ret; + + ret = asoc_simple_init_dai(cpu, &dai_props->cpu_dai); + if (ret < 0) + return ret; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,7,0) + ret = asoc_simple_init_dai_link_params(rtd); + if (ret < 0) + return ret; +#endif + + dev_dbg(rtd->card->dev, "codec \"%s\" mapping to cpu \"%s\"\n", codec->name, cpu->name); + return 0; +} + +static int seeed_voice_card_dai_link_of(struct device_node *node, + struct seeed_card_data *priv, + int idx, + bool is_top_level_node) +{ + struct device *dev = seeed_priv_to_dev(priv); + struct snd_soc_dai_link *dai_link = seeed_priv_to_link(priv, idx); + struct seeed_dai_props *dai_props = seeed_priv_to_props(priv, idx); + struct asoc_simple_dai *cpu_dai = &dai_props->cpu_dai; + struct asoc_simple_dai *codec_dai = &dai_props->codec_dai; + struct device_node *cpu = NULL; + struct device_node *plat = NULL; + struct device_node *codec = NULL; + char prop[128]; + char *prefix = ""; + int ret, single_cpu; + + /* For single DAI link & old style of DT node */ + if (is_top_level_node) + prefix = PREFIX; + + snprintf(prop, sizeof(prop), "%scpu", prefix); + cpu = of_get_child_by_name(node, prop); + + if (!cpu) { + ret = -EINVAL; + dev_err(dev, "%s: Can't find %s DT node\n", __func__, prop); + goto dai_link_of_err; + } + + snprintf(prop, sizeof(prop), "%splat", prefix); + plat = of_get_child_by_name(node, prop); + + snprintf(prop, sizeof(prop), "%scodec", prefix); + codec = of_get_child_by_name(node, prop); + + if (!codec) { + ret = -EINVAL; + dev_err(dev, "%s: Can't find %s DT node\n", __func__, prop); + goto dai_link_of_err; + } + + ret = asoc_simple_parse_daifmt(dev, node, codec, + prefix, &dai_link->dai_fmt); + if (ret < 0) + goto dai_link_of_err; + + of_property_read_u32(node, "mclk-fs", &dai_props->mclk_fs); + + ret = asoc_simple_parse_cpu(cpu, dai_link, &single_cpu); + if (ret < 0) + goto dai_link_of_err; + + #if _SINGLE_CODEC + ret = asoc_simple_parse_codec(codec, dai_link); + if (ret < 0) + goto dai_link_of_err; + #else + ret = snd_soc_of_get_dai_link_codecs(dev, codec, dai_link); + if (ret < 0) { + dev_err(dev, "parse codec info error %d\n", ret); + goto dai_link_of_err; + } + dev_dbg(dev, "dai_link num_codecs = %d\n", dai_link->num_codecs); + #endif + + ret = asoc_simple_parse_platform(plat, dai_link); + if (ret < 0) + goto dai_link_of_err; + + ret = snd_soc_of_parse_tdm_slot(cpu, &cpu_dai->tx_slot_mask, + &cpu_dai->rx_slot_mask, + &cpu_dai->slots, + &cpu_dai->slot_width); + dev_dbg(dev, "cpu_dai : slot,width,tx,rx = %d,%d,%d,%d\n", + cpu_dai->slots, cpu_dai->slot_width, + cpu_dai->tx_slot_mask, cpu_dai->rx_slot_mask + ); + if (ret < 0) + goto dai_link_of_err; + + ret = snd_soc_of_parse_tdm_slot(codec, &codec_dai->tx_slot_mask, + &codec_dai->rx_slot_mask, + &codec_dai->slots, + &codec_dai->slot_width); + if (ret < 0) + goto dai_link_of_err; + + #if LINUX_VERSION_CODE <= KERNEL_VERSION(4,10,0) + ret = asoc_simple_card_parse_clk_cpu(cpu, dai_link, cpu_dai); + #else + ret = asoc_simple_parse_clk_cpu(dev, cpu, dai_link, cpu_dai); + #endif + if (ret < 0) + goto dai_link_of_err; + + #if LINUX_VERSION_CODE <= KERNEL_VERSION(4,10,0) + ret = asoc_simple_card_parse_clk_codec(codec, dai_link, codec_dai); + #else + ret = asoc_simple_parse_clk_codec(dev, codec, dai_link, codec_dai); + #endif + if (ret < 0) + goto dai_link_of_err; + + ret = asoc_simple_set_dailink_name(dev, dai_link, + "%s-%s", + dai_link->cpus->dai_name, + #if _SINGLE_CODEC + dai_link->codecs->dai_name + #else + dai_link->codecs[0].dai_name + #endif + ); + if (ret < 0) + goto dai_link_of_err; + + dai_link->ops = &seeed_voice_card_ops; + dai_link->init = seeed_voice_card_dai_init; + + dev_dbg(dev, "\tname : %s\n", dai_link->stream_name); + dev_dbg(dev, "\tformat : %04x\n", dai_link->dai_fmt); + dev_dbg(dev, "\tcpu : %s / %d\n", + dai_link->cpus->dai_name, + dai_props->cpu_dai.sysclk); + dev_dbg(dev, "\tcodec : %s / %d\n", + #if _SINGLE_CODEC + dai_link->codecs->dai_name, + #else + dai_link->codecs[0].dai_name, + #endif + dai_props->codec_dai.sysclk); + + asoc_simple_canonicalize_cpu(dai_link, single_cpu); + #if _SINGLE_CODEC + asoc_simple_canonicalize_platform(dai_link); + #endif + +dai_link_of_err: + of_node_put(cpu); + of_node_put(codec); + + return ret; +} + +static int seeed_voice_card_parse_aux_devs(struct device_node *node, + struct seeed_card_data *priv) +{ + struct device *dev = seeed_priv_to_dev(priv); + struct device_node *aux_node; + int i, n, len; + + if (!of_find_property(node, PREFIX "aux-devs", &len)) + return 0; /* Ok to have no aux-devs */ + + n = len / sizeof(__be32); + if (n <= 0) + return -EINVAL; + + priv->snd_card.aux_dev = devm_kzalloc(dev, + n * sizeof(*priv->snd_card.aux_dev), GFP_KERNEL); + if (!priv->snd_card.aux_dev) + return -ENOMEM; + + for (i = 0; i < n; i++) { + aux_node = of_parse_phandle(node, PREFIX "aux-devs", i); + if (!aux_node) + return -EINVAL; + priv->snd_card.aux_dev[i].dlc.of_node = aux_node; + } + + priv->snd_card.num_aux_devs = n; + return 0; +} + +static int seeed_voice_card_parse_of(struct device_node *node, + struct seeed_card_data *priv) +{ + struct device *dev = seeed_priv_to_dev(priv); + struct device_node *dai_link; + int ret; + + if (!node) + return -EINVAL; + + dai_link = of_get_child_by_name(node, PREFIX "dai-link"); + + /* The off-codec widgets */ + if (of_property_read_bool(node, PREFIX "widgets")) { + ret = snd_soc_of_parse_audio_simple_widgets(&priv->snd_card, + PREFIX "widgets"); + if (ret) + goto card_parse_end; + } + + /* DAPM routes */ + if (of_property_read_bool(node, PREFIX "routing")) { + ret = snd_soc_of_parse_audio_routing(&priv->snd_card, + PREFIX "routing"); + if (ret) + goto card_parse_end; + } + + /* Factor to mclk, used in hw_params() */ + of_property_read_u32(node, PREFIX "mclk-fs", &priv->mclk_fs); + + /* Single/Muti DAI link(s) & New style of DT node */ + if (dai_link) { + struct device_node *np = NULL; + int i = 0; + + for_each_child_of_node(node, np) { + dev_dbg(dev, "\tlink %d:\n", i); + ret = seeed_voice_card_dai_link_of(np, priv, + i, false); + if (ret < 0) { + of_node_put(np); + goto card_parse_end; + } + i++; + } + } else { + /* For single DAI link & old style of DT node */ + ret = seeed_voice_card_dai_link_of(node, priv, 0, true); + if (ret < 0) + goto card_parse_end; + } + + ret = asoc_simple_parse_card_name(&priv->snd_card, PREFIX); + if (ret < 0) + goto card_parse_end; + + ret = seeed_voice_card_parse_aux_devs(node, priv); + + priv->channels_playback_default = 0; + priv->channels_playback_override = 2; + priv->channels_capture_default = 0; + priv->channels_capture_override = 2; + of_property_read_u32(node, PREFIX "channels-playback-default", + &priv->channels_playback_default); + of_property_read_u32(node, PREFIX "channels-playback-override", + &priv->channels_playback_override); + of_property_read_u32(node, PREFIX "channels-capture-default", + &priv->channels_capture_default); + of_property_read_u32(node, PREFIX "channels-capture-override", + &priv->channels_capture_override); + +card_parse_end: + of_node_put(dai_link); + + return ret; +} + +#ifdef DEBUG +inline void seeed_debug_dai(struct seeed_card_data *priv, + char *name, + struct asoc_simple_dai *dai) +{ + struct device *dev = seeed_priv_to_dev(priv); + + if (dai->name) + dev_dbg(dev, "%s dai name = %s\n", + name, dai->name); + if (dai->sysclk) + dev_dbg(dev, "%s sysclk = %d\n", + name, dai->sysclk); + + dev_dbg(dev, "%s direction = %s\n", + name, dai->clk_direction ? "OUT" : "IN"); + + if (dai->slots) + dev_dbg(dev, "%s slots = %d\n", name, dai->slots); + if (dai->slot_width) + dev_dbg(dev, "%s slot width = %d\n", name, dai->slot_width); + if (dai->tx_slot_mask) + dev_dbg(dev, "%s tx slot mask = %d\n", name, dai->tx_slot_mask); + if (dai->rx_slot_mask) + dev_dbg(dev, "%s rx slot mask = %d\n", name, dai->rx_slot_mask); + if (dai->clk) + dev_dbg(dev, "%s clk %luHz\n", name, clk_get_rate(dai->clk)); +} + +inline void seeed_debug_info(struct seeed_card_data *priv) +{ + struct snd_soc_card *card = seeed_priv_to_card(priv); + struct device *dev = seeed_priv_to_dev(priv); + + int i; + + if (card->name) + dev_dbg(dev, "Card Name: %s\n", card->name); + + for (i = 0; i < card->num_links; i++) { + struct seeed_dai_props *props = seeed_priv_to_props(priv, i); + struct snd_soc_dai_link *link = seeed_priv_to_link(priv, i); + + dev_dbg(dev, "DAI%d\n", i); + + seeed_debug_dai(priv, "cpu", &props->cpu_dai); + seeed_debug_dai(priv, "codec", &props->codec_dai); + + if (link->name) + dev_dbg(dev, "dai name = %s\n", link->name); + + dev_dbg(dev, "dai format = %04x\n", link->dai_fmt); + + /* + if (props->adata.convert_rate) + dev_dbg(dev, "convert_rate = %d\n", + props->adata.convert_rate); + if (props->adata.convert_channels) + dev_dbg(dev, "convert_channels = %d\n", + props->adata.convert_channels); + if (props->codec_conf && props->codec_conf->name_prefix) + dev_dbg(dev, "name prefix = %s\n", + props->codec_conf->name_prefix); + */ + if (props->mclk_fs) + dev_dbg(dev, "mclk-fs = %d\n", + props->mclk_fs); + } +} +#else +#define seeed_debug_info(priv) +#endif /* DEBUG */ + +static int seeed_voice_card_probe(struct platform_device *pdev) +{ + struct seeed_card_data *priv; + struct snd_soc_dai_link *dai_link; + struct seeed_dai_props *dai_props; + struct device_node *np = pdev->dev.of_node; + struct device *dev = &pdev->dev; + int num, ret, i; + + /* Get the number of DAI links */ + if (np && of_get_child_by_name(np, PREFIX "dai-link")) + num = of_get_child_count(np); + else + num = 1; + + /* Allocate the private data and the DAI link array */ + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + dai_props = devm_kzalloc(dev, sizeof(*dai_props) * num, GFP_KERNEL); + dai_link = devm_kzalloc(dev, sizeof(*dai_link) * num, GFP_KERNEL); + if (!dai_props || !dai_link) + return -ENOMEM; + + /* + * Use snd_soc_dai_link_component instead of legacy style + * It is codec only. but cpu/platform will be supported in the future. + * see + * soc-core.c :: snd_soc_init_multicodec() + * + * "platform" might be removed + * see + * simple-card-utils.c :: asoc_simple_canonicalize_platform() + */ + for (i = 0; i < num; i++) { + dai_link[i].cpus = &dai_props[i].cpus; + dai_link[i].num_cpus = 1; + dai_link[i].codecs = &dai_props[i].codecs; + dai_link[i].num_codecs = 1; + dai_link[i].platforms = &dai_props[i].platforms; + dai_link[i].num_platforms = 1; + } + + priv->dai_props = dai_props; + priv->dai_link = dai_link; + + /* Init snd_soc_card */ + priv->snd_card.owner = THIS_MODULE; + priv->snd_card.dev = dev; + priv->snd_card.dai_link = priv->dai_link; + priv->snd_card.num_links = num; + + if (np && of_device_is_available(np)) { + ret = seeed_voice_card_parse_of(np, priv); + if (ret < 0) { + if (ret != -EPROBE_DEFER) + dev_err(dev, "parse error %d\n", ret); + goto err; + } + } else { + struct seeed_card_info *cinfo; + struct snd_soc_dai_link_component *cpus; + struct snd_soc_dai_link_component *codecs; + struct snd_soc_dai_link_component *platform; + + cinfo = dev->platform_data; + if (!cinfo) { + dev_err(dev, "no info for seeed-voice-card\n"); + return -EINVAL; + } + + if (!cinfo->name || + !cinfo->codec_dai.name || + !cinfo->codec || + !cinfo->platform || + !cinfo->cpu_dai.name) { + dev_err(dev, "insufficient seeed_voice_card_info settings\n"); + return -EINVAL; + } + + cpus = dai_link->cpus; + cpus->dai_name = cinfo->cpu_dai.name; + + codecs = dai_link->codecs; + codecs->name = cinfo->codec; + codecs->dai_name = cinfo->codec_dai.name; + + platform = dai_link->platforms; + platform->name = cinfo->platform; + + priv->snd_card.name = (cinfo->card) ? cinfo->card : cinfo->name; + dai_link->name = cinfo->name; + dai_link->stream_name = cinfo->name; + dai_link->dai_fmt = cinfo->daifmt; + dai_link->init = seeed_voice_card_dai_init; + memcpy(&priv->dai_props->cpu_dai, &cinfo->cpu_dai, + sizeof(priv->dai_props->cpu_dai)); + memcpy(&priv->dai_props->codec_dai, &cinfo->codec_dai, + sizeof(priv->dai_props->codec_dai)); + } + + snd_soc_card_set_drvdata(&priv->snd_card, priv); + + #if CONFIG_AC10X_TRIG_LOCK + spin_lock_init(&priv->lock); + #endif + + INIT_WORK(&priv->work_codec_clk, work_cb_codec_clk); + + seeed_debug_info(priv); + + ret = devm_snd_soc_register_card(&pdev->dev, &priv->snd_card); + if (ret >= 0) + return ret; + +err: + asoc_simple_clean_reference(&priv->snd_card); + + return ret; +} + +static int seeed_voice_card_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct seeed_card_data *priv = snd_soc_card_get_drvdata(card); + + if (cancel_work_sync(&priv->work_codec_clk) != 0) { + } + return asoc_simple_clean_reference(card); +} + +static const struct of_device_id seeed_voice_of_match[] = { + { .compatible = "seeed-voicecard", }, + {}, +}; +MODULE_DEVICE_TABLE(of, seeed_voice_of_match); + +static struct platform_driver seeed_voice_card = { + .driver = { + .name = "seeed-voicecard", + .pm = &snd_soc_pm_ops, + .of_match_table = seeed_voice_of_match, + }, + .probe = seeed_voice_card_probe, + .remove = seeed_voice_card_remove, +}; + +module_platform_driver(seeed_voice_card); + +MODULE_ALIAS("platform:seeed-voice-card"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("ASoC SEEED Voice Card"); +MODULE_AUTHOR("PeterYang"); diff --git a/seeed-voicecard/seeed-voicecard.service b/seeed-voicecard/seeed-voicecard.service new file mode 100644 index 0000000..298fac6 --- /dev/null +++ b/seeed-voicecard/seeed-voicecard.service @@ -0,0 +1,12 @@ +[Unit] +Description=Seeed Voicecard service +After=alsa-restore.service + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/usr/bin/seeed-voicecard +User=root + +[Install] +WantedBy=sysinit.target diff --git a/seeed-voicecard/sound-compatible-4.18.h b/seeed-voicecard/sound-compatible-4.18.h new file mode 100644 index 0000000..550b3a7 --- /dev/null +++ b/seeed-voicecard/sound-compatible-4.18.h @@ -0,0 +1,47 @@ +/* + * (C) Copyright 2017-2018 + * Seeed Technology Co., Ltd. + * + * PeterYang + */ +#ifndef __SOUND_COMPATIBLE_4_18_H__ +#define __SOUND_COMPATIBLE_4_18_H__ + +#include + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,17,0) +#define __NO_SND_SOC_CODEC_DRV 1 +#else +#define __NO_SND_SOC_CODEC_DRV 0 +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5,4,0) +#if __has_attribute(__fallthrough__) +# define fallthrough __attribute__((__fallthrough__)) +#else +# define fallthrough do {} while (0) /* fallthrough */ +#endif +#endif + +#if __NO_SND_SOC_CODEC_DRV +#define codec component +#define snd_soc_codec snd_soc_component +#define snd_soc_codec_driver snd_soc_component_driver +#define snd_soc_codec_get_drvdata snd_soc_component_get_drvdata +#define snd_soc_codec_get_dapm snd_soc_component_get_dapm +#define snd_soc_codec_get_bias_level snd_soc_component_get_bias_level +#define snd_soc_kcontrol_codec snd_soc_kcontrol_component +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,9,0) +#define snd_soc_read snd_soc_component_read +#else +#define snd_soc_read snd_soc_component_read32 +#endif +#define snd_soc_register_codec devm_snd_soc_register_component +#define snd_soc_unregister_codec snd_soc_unregister_component +#define snd_soc_update_bits snd_soc_component_update_bits +#define snd_soc_write snd_soc_component_write +#define snd_soc_add_codec_controls snd_soc_add_component_controls +#endif + +#endif//__SOUND_COMPATIBLE_4_18_H__ + diff --git a/seeed-voicecard/tools/coherence.py b/seeed-voicecard/tools/coherence.py new file mode 100644 index 0000000..665d228 --- /dev/null +++ b/seeed-voicecard/tools/coherence.py @@ -0,0 +1,44 @@ +""" +Estimate the magnitude squared coherence estimate, + +- requirements + sudo apt install python-numpy python-scipy python-matplotlib +""" + + +import sys +import wave +import numpy as np +from scipy import signal +import matplotlib.pyplot as plt + +if len(sys.argv) < 2: + print('Usage: python {} audio.wav'.format(sys.argv[0])) + sys.exit(1) + +wav = wave.open(sys.argv[1], 'rb') +channels = wav.getnchannels() +frames = wav.readframes(wav.getnframes()) +fs = wav.getframerate() +wav.close() + +print("channels: %d" % channels) +print("rate : %d" % fs) +print("frames : %d" % wav.getnframes()) + +array = np.fromstring(frames, dtype='int16') + +ch0 = array[0::channels] + +fig, ax = plt.subplots() + +for ch in range(1, channels): + f, c = signal.coherence(ch0, array[ch::channels], fs, nperseg=1024) + ax.semilogy(f, c, label="CO 1-%d" % (ch + 1)) + +legend = ax.legend(loc='lower right', shadow=True, fontsize='small') + +plt.xlabel('frequency [Hz]') +plt.ylabel('Coherence') +plt.show() + diff --git a/seeed-voicecard/tools/phase_test.py b/seeed-voicecard/tools/phase_test.py new file mode 100644 index 0000000..a099f2b --- /dev/null +++ b/seeed-voicecard/tools/phase_test.py @@ -0,0 +1,83 @@ +import sys +import wave +import numpy as np + + +if len(sys.argv) != 2: + print('Usage: {} multi.wav'.format(sys.argv[0])) + sys.exit(1) + + +multi = wave.open(sys.argv[1], 'rb') +rate = multi.getframerate() +channels = multi.getnchannels() + +if channels <= 1: + sys.exit(1) + +N = rate + +window = np.hanning(N) + +interp = 4*8 +max_offset = int(rate * 0.1 / 340 * interp) + +def gcc_phat(sig, refsig, fs=1, max_tau=None, interp=16): + ''' + This function computes the offset between the signal sig and the reference signal refsig + using the Generalized Cross Correlation - Phase Transform (GCC-PHAT)method. + ''' + + # make sure the length for the FFT is larger or equal than len(sig) + len(refsig) + n = sig.shape[0] + refsig.shape[0] + + # Generalized Cross Correlation Phase Transform + SIG = np.fft.rfft(sig, n=n) + REFSIG = np.fft.rfft(refsig, n=n) + R = SIG * np.conj(REFSIG) + #R /= np.abs(R) + + cc = np.fft.irfft(R, n=(interp * n)) + + max_shift = int(interp * n / 2) + if max_tau: + max_shift = np.minimum(int(interp * fs * max_tau), max_shift) + + cc = np.concatenate((cc[-max_shift:], cc[:max_shift+1])) + + # find max cross correlation index + shift = np.argmax(np.abs(cc)) - max_shift + + tau = shift / float(interp * fs) + + return tau, cc + + +print(multi.getsampwidth()) + +while True: + data = multi.readframes(N) + + if len(data) != multi.getsampwidth() * N * channels: + print("done") + break + + if multi.getsampwidth() == 2: + data = np.fromstring(data, dtype='int16') + else: + data = np.fromstring(data, dtype='int32') + ref_buf = data[0::channels] + + offsets = [] + for ch in range(1, channels): + sig_buf = data[ch::channels] + tau, _ = gcc_phat(sig_buf * window, ref_buf * window, fs=1, max_tau=max_offset, interp=interp) + # tau, _ = gcc_phat(sig_buf, ref_buf, fs=rate, max_tau=1) + + offsets.append(tau) + + print(offsets) + +print(multi.getframerate()) + +multi.close() diff --git a/seeed-voicecard/ubuntu-prerequisite.sh b/seeed-voicecard/ubuntu-prerequisite.sh new file mode 100755 index 0000000..f5566cc --- /dev/null +++ b/seeed-voicecard/ubuntu-prerequisite.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +# Copyright (c) Hin-Tak Leung 2020 +# +# Overview: +# This script compiles and install the Broadcom VideoCore tools, +# configure the dynamic loader for the non-standard library location, +# and update the loader cache. +# +# A few steps explicitly requires root privilege, which are +# marked with "sudo". The rest is just checking for duplicate/previous +# action. +# +# This derived from my command history on ubuntu 20.04.1 .YMMV + +sudo apt install -y git gcc g++ make alsa-utils cmake + +git clone git://github.com/raspberrypi/userland.git +pushd userland/ + +arch=$(uname -m) +if [[ "$arch" =~ aarch64 ]]; then + ./buildme --aarch64 +else + ./buildme +fi +# ./buildme already includes "sudo make install" at the end + +popd + +# matches Raspbian's location: +if [ ! -f /etc/ld.so.conf.d/00-vmcs.conf ] ; then + echo "/opt/vc/lib" | sudo tee -a /etc/ld.so.conf.d/00-vmcs.conf + sudo ldconfig -v +else + echo "/etc/ld.so.conf.d/00-vmcs.conf exists - no need to update ld.cache!" +fi diff --git a/seeed-voicecard/uninstall.sh b/seeed-voicecard/uninstall.sh new file mode 100755 index 0000000..adbebfc --- /dev/null +++ b/seeed-voicecard/uninstall.sh @@ -0,0 +1,99 @@ +#!/bin/bash + +if [[ $EUID -ne 0 ]]; then + echo "This script must be run as root (use sudo)" 1>&2 + exit 1 +fi + +is_Raspberry=$(cat /proc/device-tree/model | awk '{print $1}') +if [ "x${is_Raspberry}" != "xRaspberry" ] ; then + echo "Sorry, this drivers only works on raspberry pi" + exit 1 +fi + +uname_r=$(uname -r) + +CONFIG=/boot/config.txt +[ -f /boot/firmware/usercfg.txt ] && CONFIG=/boot/firmware/usercfg.txt + +get_overlay() { + ov=$1 + if grep -q -E "^dtoverlay=$ov" $CONFIG; then + echo 0 + else + echo 1 + fi +} + +do_overlay() { + ov=$1 + RET=$2 + DEFAULT=--defaultno + CURRENT=0 + if [ $(get_overlay $ov) -eq 0 ]; then + DEFAULT= + CURRENT=1 + fi + if [ $RET -eq $CURRENT ]; then + ASK_TO_REBOOT=1 + fi + if [ $RET -eq 0 ]; then + sed $CONFIG -i -e "s/^#dtoverlay=$ov/dtoverlay=$ov/" + if ! grep -q -E "^dtoverlay=$ov" $CONFIG; then + printf "dtoverlay=$ov\n" >> $CONFIG + fi + STATUS=enabled + elif [ $RET -eq 1 ]; then + sed $CONFIG -i -e "s/^dtoverlay=$ov/#dtoverlay=$ov/" + STATUS=disabled + else + return $RET + fi +} + +RPI_HATS="seeed-2mic-voicecard seeed-4mic-voicecard seeed-8mic-voicecard" + +PATH=$PATH:/opt/vc/bin +echo "remove dtbos" +for i in $RPI_HATS; do + dtoverlay -r $i +done +OVERLAYS=/boot/overlays +[ -d /boot/firmware/overlays ] && OVERLAYS=/boot/firmware/overlays + +rm ${OVERLAYS}/seeed-2mic-voicecard.dtbo || true +rm ${OVERLAYS}/seeed-4mic-voicecard.dtbo || true +rm ${OVERLAYS}/seeed-8mic-voicecard.dtbo || true + +echo "remove alsa configs" +rm -rf /etc/voicecard/ || true + +echo "disabled seeed-voicecard.service " +systemctl stop seeed-voicecard.service +systemctl disable seeed-voicecard.service + +echo "remove seeed-voicecard" +rm /usr/bin/seeed-voicecard || true +rm /lib/systemd/system/seeed-voicecard.service || true + +echo "remove dkms" +rm -rf /var/lib/dkms/seeed-voicecard || true + +echo "remove kernel modules" +rm /lib/modules/*/kernel/sound/soc/codecs/snd-soc-wm8960.ko || true +rm /lib/modules/*/kernel/sound/soc/codecs/snd-soc-ac108.ko || true +rm /lib/modules/*/kernel/sound/soc/bcm/snd-soc-seeed-voicecard.ko || true +rm /lib/modules/*/updates/dkms/snd-soc-wm8960.ko || true +rm /lib/modules/*/updates/dkms/snd-soc-ac108.ko || true +rm /lib/modules/*/updates/dkms/snd-soc-seeed-voicecard.ko || true + +echo "remove $CONFIG configuration" +for i in $RPI_HATS; do + echo Uninstall $i ... + do_overlay $i 1 +done + +echo "------------------------------------------------------" +echo "Please reboot your raspberry pi to apply all settings" +echo "Thank you!" +echo "------------------------------------------------------" diff --git a/seeed-voicecard/wm8960.c b/seeed-voicecard/wm8960.c new file mode 100644 index 0000000..375d1a7 --- /dev/null +++ b/seeed-voicecard/wm8960.c @@ -0,0 +1,1420 @@ +/* + * wm8960.c -- WM8960 ALSA SoC Audio driver + * + * Copyright 2007-11 Wolfson Microelectronics, plc + * + * Author: Liam Girdwood + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sound-compatible-4.18.h" + +#include "wm8960.h" + +/* R25 - Power 1 */ +#define WM8960_VMID_MASK 0x180 +#define WM8960_VREF 0x40 + +/* R26 - Power 2 */ +#define WM8960_PWR2_LOUT1 0x40 +#define WM8960_PWR2_ROUT1 0x20 +#define WM8960_PWR2_OUT3 0x02 + +/* R28 - Anti-pop 1 */ +#define WM8960_POBCTRL 0x80 +#define WM8960_BUFDCOPEN 0x10 +#define WM8960_BUFIOEN 0x08 +#define WM8960_SOFT_ST 0x04 +#define WM8960_HPSTBY 0x01 + +/* R29 - Anti-pop 2 */ +#define WM8960_DISOP 0x40 +#define WM8960_DRES_MASK 0x30 + +static bool is_pll_freq_available(unsigned int source, unsigned int target); +static int wm8960_set_pll(struct snd_soc_codec *codec, + unsigned int freq_in, unsigned int freq_out); +/* + * wm8960 register cache + * We can't read the WM8960 register space when we are + * using 2 wire for device control, so we cache them instead. + */ +static const struct reg_default wm8960_reg_defaults[] = { + { 0x0, 0x00a7 }, + { 0x1, 0x00a7 }, + { 0x2, 0x0000 }, + { 0x3, 0x0000 }, + { 0x4, 0x0000 }, + { 0x5, 0x0008 }, + { 0x6, 0x0000 }, + { 0x7, 0x000a }, + { 0x8, 0x01c0 }, + { 0x9, 0x0000 }, + { 0xa, 0x00ff }, + { 0xb, 0x00ff }, + + { 0x10, 0x0000 }, + { 0x11, 0x007b }, + { 0x12, 0x0100 }, + { 0x13, 0x0032 }, + { 0x14, 0x0000 }, + { 0x15, 0x00c3 }, + { 0x16, 0x00c3 }, + { 0x17, 0x01c0 }, + { 0x18, 0x0000 }, + { 0x19, 0x0000 }, + { 0x1a, 0x0000 }, + { 0x1b, 0x0000 }, + { 0x1c, 0x0000 }, + { 0x1d, 0x0000 }, + + { 0x20, 0x0100 }, + { 0x21, 0x0100 }, + { 0x22, 0x0050 }, + + { 0x25, 0x0050 }, + { 0x26, 0x0000 }, + { 0x27, 0x0000 }, + { 0x28, 0x0000 }, + { 0x29, 0x0000 }, + { 0x2a, 0x0040 }, + { 0x2b, 0x0000 }, + { 0x2c, 0x0000 }, + { 0x2d, 0x0050 }, + { 0x2e, 0x0050 }, + { 0x2f, 0x0000 }, + { 0x30, 0x0002 }, + { 0x31, 0x0037 }, + + { 0x33, 0x0080 }, + { 0x34, 0x0008 }, + { 0x35, 0x0031 }, + { 0x36, 0x0026 }, + { 0x37, 0x00e9 }, +}; + +static bool wm8960_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WM8960_RESET: + return true; + default: + return false; + } +} + +struct wm8960_priv { + struct clk *mclk; + struct regmap *regmap; + int (*set_bias_level)(struct snd_soc_codec *, + enum snd_soc_bias_level level); + struct snd_soc_dapm_widget *lout1; + struct snd_soc_dapm_widget *rout1; + struct snd_soc_dapm_widget *out3; + bool deemph; + int lrclk; + int bclk; + int sysclk; + int clk_id; + int freq_in; + bool is_stream_in_use[2]; + struct wm8960_data pdata; +}; + +#define wm8960_reset(c) regmap_write(c, WM8960_RESET, 0) + +/* enumerated controls */ +static const char *wm8960_polarity[] = {"No Inversion", "Left Inverted", + "Right Inverted", "Stereo Inversion"}; +static const char *wm8960_3d_upper_cutoff[] = {"High", "Low"}; +static const char *wm8960_3d_lower_cutoff[] = {"Low", "High"}; +static const char *wm8960_alcfunc[] = {"Off", "Right", "Left", "Stereo"}; +static const char *wm8960_alcmode[] = {"ALC", "Limiter"}; +static const char *wm8960_adc_data_output_sel[] = { + "Left Data = Left ADC; Right Data = Right ADC", + "Left Data = Left ADC; Right Data = Left ADC", + "Left Data = Right ADC; Right Data = Right ADC", + "Left Data = Right ADC; Right Data = Left ADC", +}; +static const char *wm8960_dmonomix[] = {"Stereo", "Mono"}; + +static const struct soc_enum wm8960_enum[] = { + SOC_ENUM_SINGLE(WM8960_DACCTL1, 5, 4, wm8960_polarity), + SOC_ENUM_SINGLE(WM8960_DACCTL2, 5, 4, wm8960_polarity), + SOC_ENUM_SINGLE(WM8960_3D, 6, 2, wm8960_3d_upper_cutoff), + SOC_ENUM_SINGLE(WM8960_3D, 5, 2, wm8960_3d_lower_cutoff), + SOC_ENUM_SINGLE(WM8960_ALC1, 7, 4, wm8960_alcfunc), + SOC_ENUM_SINGLE(WM8960_ALC3, 8, 2, wm8960_alcmode), + SOC_ENUM_SINGLE(WM8960_ADDCTL1, 2, 4, wm8960_adc_data_output_sel), + SOC_ENUM_SINGLE(WM8960_ADDCTL1, 4, 2, wm8960_dmonomix), +}; + +static const int deemph_settings[] = { 0, 32000, 44100, 48000 }; + +static int wm8960_set_deemph(struct snd_soc_codec *codec) +{ + struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + int val, i, best; + + /* If we're using deemphasis select the nearest available sample + * rate. + */ + if (wm8960->deemph) { + best = 1; + for (i = 2; i < ARRAY_SIZE(deemph_settings); i++) { + if (abs(deemph_settings[i] - wm8960->lrclk) < + abs(deemph_settings[best] - wm8960->lrclk)) + best = i; + } + + val = best << 1; + } else { + val = 0; + } + + dev_dbg(codec->dev, "Set deemphasis %d\n", val); + + return snd_soc_update_bits(codec, WM8960_DACCTL1, + 0x6, val); +} + +static int wm8960_get_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + + ucontrol->value.integer.value[0] = wm8960->deemph; + return 0; +} + +static int wm8960_put_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + unsigned int deemph = ucontrol->value.integer.value[0]; + + if (deemph > 1) + return -EINVAL; + + wm8960->deemph = deemph; + + return wm8960_set_deemph(codec); +} + +static const DECLARE_TLV_DB_SCALE(adc_tlv, -9750, 50, 1); +static const DECLARE_TLV_DB_SCALE(inpga_tlv, -1725, 75, 0); +static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1); +static const DECLARE_TLV_DB_SCALE(bypass_tlv, -2100, 300, 0); +static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1); +static const DECLARE_TLV_DB_SCALE(lineinboost_tlv, -1500, 300, 1); +static const DECLARE_TLV_DB_RANGE(micboost_tlv, + 0, 1, TLV_DB_SCALE_ITEM(0, 1300, 0), + 2, 3, TLV_DB_SCALE_ITEM(2000, 900, 0), +); + + +static const struct snd_kcontrol_new wm8960_snd_controls[] = { +SOC_DOUBLE_R_TLV("Capture Volume", WM8960_LINVOL, WM8960_RINVOL, + 0, 63, 0, inpga_tlv), +SOC_DOUBLE_R("Capture Volume ZC Switch", WM8960_LINVOL, WM8960_RINVOL, + 6, 1, 0), +SOC_DOUBLE_R("Capture Switch", WM8960_LINVOL, WM8960_RINVOL, + 7, 1, 1), + +SOC_SINGLE_TLV("Left Input Boost Mixer LINPUT3 Volume", + WM8960_INBMIX1, 4, 7, 0, lineinboost_tlv), +SOC_SINGLE_TLV("Left Input Boost Mixer LINPUT2 Volume", + WM8960_INBMIX1, 1, 7, 0, lineinboost_tlv), +SOC_SINGLE_TLV("Right Input Boost Mixer RINPUT3 Volume", + WM8960_INBMIX2, 4, 7, 0, lineinboost_tlv), +SOC_SINGLE_TLV("Right Input Boost Mixer RINPUT2 Volume", + WM8960_INBMIX2, 1, 7, 0, lineinboost_tlv), +SOC_SINGLE_TLV("Right Input Boost Mixer RINPUT1 Volume", + WM8960_RINPATH, 4, 3, 0, micboost_tlv), +SOC_SINGLE_TLV("Left Input Boost Mixer LINPUT1 Volume", + WM8960_LINPATH, 4, 3, 0, micboost_tlv), + +SOC_DOUBLE_R_TLV("Playback Volume", WM8960_LDAC, WM8960_RDAC, + 0, 255, 0, dac_tlv), + +SOC_DOUBLE_R_TLV("Headphone Playback Volume", WM8960_LOUT1, WM8960_ROUT1, + 0, 127, 0, out_tlv), +SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8960_LOUT1, WM8960_ROUT1, + 7, 1, 0), + +SOC_DOUBLE_R_TLV("Speaker Playback Volume", WM8960_LOUT2, WM8960_ROUT2, + 0, 127, 0, out_tlv), +SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8960_LOUT2, WM8960_ROUT2, + 7, 1, 0), +SOC_SINGLE("Speaker DC Volume", WM8960_CLASSD3, 3, 5, 0), +SOC_SINGLE("Speaker AC Volume", WM8960_CLASSD3, 0, 5, 0), + +SOC_SINGLE("PCM Playback -6dB Switch", WM8960_DACCTL1, 7, 1, 0), +SOC_ENUM("ADC Polarity", wm8960_enum[0]), +SOC_SINGLE("ADC High Pass Filter Switch", WM8960_DACCTL1, 0, 1, 0), + +SOC_ENUM("DAC Polarity", wm8960_enum[1]), +SOC_SINGLE_BOOL_EXT("DAC Deemphasis Switch", 0, + wm8960_get_deemph, wm8960_put_deemph), + +SOC_ENUM("3D Filter Upper Cut-Off", wm8960_enum[2]), +SOC_ENUM("3D Filter Lower Cut-Off", wm8960_enum[3]), +SOC_SINGLE("3D Volume", WM8960_3D, 1, 15, 0), +SOC_SINGLE("3D Switch", WM8960_3D, 0, 1, 0), + +SOC_ENUM("ALC Function", wm8960_enum[4]), +SOC_SINGLE("ALC Max Gain", WM8960_ALC1, 4, 7, 0), +SOC_SINGLE("ALC Target", WM8960_ALC1, 0, 15, 1), +SOC_SINGLE("ALC Min Gain", WM8960_ALC2, 4, 7, 0), +SOC_SINGLE("ALC Hold Time", WM8960_ALC2, 0, 15, 0), +SOC_ENUM("ALC Mode", wm8960_enum[5]), +SOC_SINGLE("ALC Decay", WM8960_ALC3, 4, 15, 0), +SOC_SINGLE("ALC Attack", WM8960_ALC3, 0, 15, 0), + +SOC_SINGLE("Noise Gate Threshold", WM8960_NOISEG, 3, 31, 0), +SOC_SINGLE("Noise Gate Switch", WM8960_NOISEG, 0, 1, 0), + +SOC_DOUBLE_R_TLV("ADC PCM Capture Volume", WM8960_LADC, WM8960_RADC, + 0, 255, 0, adc_tlv), + +SOC_SINGLE_TLV("Left Output Mixer Boost Bypass Volume", + WM8960_BYPASS1, 4, 7, 1, bypass_tlv), +SOC_SINGLE_TLV("Left Output Mixer LINPUT3 Volume", + WM8960_LOUTMIX, 4, 7, 1, bypass_tlv), +SOC_SINGLE_TLV("Right Output Mixer Boost Bypass Volume", + WM8960_BYPASS2, 4, 7, 1, bypass_tlv), +SOC_SINGLE_TLV("Right Output Mixer RINPUT3 Volume", + WM8960_ROUTMIX, 4, 7, 1, bypass_tlv), + +SOC_ENUM("ADC Data Output Select", wm8960_enum[6]), +SOC_ENUM("DAC Mono Mix", wm8960_enum[7]), +}; + +static const struct snd_kcontrol_new wm8960_lin_boost[] = { +SOC_DAPM_SINGLE("LINPUT2 Switch", WM8960_LINPATH, 6, 1, 0), +SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LINPATH, 7, 1, 0), +SOC_DAPM_SINGLE("LINPUT1 Switch", WM8960_LINPATH, 8, 1, 0), +}; + +static const struct snd_kcontrol_new wm8960_lin[] = { +SOC_DAPM_SINGLE("Boost Switch", WM8960_LINPATH, 3, 1, 0), +}; + +static const struct snd_kcontrol_new wm8960_rin_boost[] = { +SOC_DAPM_SINGLE("RINPUT2 Switch", WM8960_RINPATH, 6, 1, 0), +SOC_DAPM_SINGLE("RINPUT3 Switch", WM8960_RINPATH, 7, 1, 0), +SOC_DAPM_SINGLE("RINPUT1 Switch", WM8960_RINPATH, 8, 1, 0), +}; + +static const struct snd_kcontrol_new wm8960_rin[] = { +SOC_DAPM_SINGLE("Boost Switch", WM8960_RINPATH, 3, 1, 0), +}; + +static const struct snd_kcontrol_new wm8960_loutput_mixer[] = { +SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_LOUTMIX, 8, 1, 0), +SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LOUTMIX, 7, 1, 0), +SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS1, 7, 1, 0), +}; + +static const struct snd_kcontrol_new wm8960_routput_mixer[] = { +SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_ROUTMIX, 8, 1, 0), +SOC_DAPM_SINGLE("RINPUT3 Switch", WM8960_ROUTMIX, 7, 1, 0), +SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS2, 7, 1, 0), +}; + +static const struct snd_kcontrol_new wm8960_mono_out[] = { +SOC_DAPM_SINGLE("Left Switch", WM8960_MONOMIX1, 7, 1, 0), +SOC_DAPM_SINGLE("Right Switch", WM8960_MONOMIX2, 7, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8960_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("LINPUT1"), +SND_SOC_DAPM_INPUT("RINPUT1"), +SND_SOC_DAPM_INPUT("LINPUT2"), +SND_SOC_DAPM_INPUT("RINPUT2"), +SND_SOC_DAPM_INPUT("LINPUT3"), +SND_SOC_DAPM_INPUT("RINPUT3"), + +SND_SOC_DAPM_SUPPLY("MICB", WM8960_POWER1, 1, 0, NULL, 0), + +SND_SOC_DAPM_MIXER("Left Boost Mixer", WM8960_POWER1, 5, 0, + wm8960_lin_boost, ARRAY_SIZE(wm8960_lin_boost)), +SND_SOC_DAPM_MIXER("Right Boost Mixer", WM8960_POWER1, 4, 0, + wm8960_rin_boost, ARRAY_SIZE(wm8960_rin_boost)), + +SND_SOC_DAPM_MIXER("Left Input Mixer", WM8960_POWER3, 5, 0, + wm8960_lin, ARRAY_SIZE(wm8960_lin)), +SND_SOC_DAPM_MIXER("Right Input Mixer", WM8960_POWER3, 4, 0, + wm8960_rin, ARRAY_SIZE(wm8960_rin)), + +SND_SOC_DAPM_ADC("Left ADC", "Capture", WM8960_POWER1, 3, 0), +SND_SOC_DAPM_ADC("Right ADC", "Capture", WM8960_POWER1, 2, 0), + +SND_SOC_DAPM_DAC("Left DAC", "Playback", WM8960_POWER2, 8, 0), +SND_SOC_DAPM_DAC("Right DAC", "Playback", WM8960_POWER2, 7, 0), + +SND_SOC_DAPM_MIXER("Left Output Mixer", WM8960_POWER3, 3, 0, + &wm8960_loutput_mixer[0], + ARRAY_SIZE(wm8960_loutput_mixer)), +SND_SOC_DAPM_MIXER("Right Output Mixer", WM8960_POWER3, 2, 0, + &wm8960_routput_mixer[0], + ARRAY_SIZE(wm8960_routput_mixer)), + +SND_SOC_DAPM_PGA("LOUT1 PGA", WM8960_POWER2, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA("ROUT1 PGA", WM8960_POWER2, 5, 0, NULL, 0), + +SND_SOC_DAPM_PGA("Left Speaker PGA", WM8960_POWER2, 4, 0, NULL, 0), +SND_SOC_DAPM_PGA("Right Speaker PGA", WM8960_POWER2, 3, 0, NULL, 0), + +SND_SOC_DAPM_PGA("Right Speaker Output", WM8960_CLASSD1, 7, 0, NULL, 0), +SND_SOC_DAPM_PGA("Left Speaker Output", WM8960_CLASSD1, 6, 0, NULL, 0), + +SND_SOC_DAPM_OUTPUT("SPK_LP"), +SND_SOC_DAPM_OUTPUT("SPK_LN"), +SND_SOC_DAPM_OUTPUT("HP_L"), +SND_SOC_DAPM_OUTPUT("HP_R"), +SND_SOC_DAPM_OUTPUT("SPK_RP"), +SND_SOC_DAPM_OUTPUT("SPK_RN"), +SND_SOC_DAPM_OUTPUT("OUT3"), +}; + +static const struct snd_soc_dapm_widget wm8960_dapm_widgets_out3[] = { +SND_SOC_DAPM_MIXER("Mono Output Mixer", WM8960_POWER2, 1, 0, + &wm8960_mono_out[0], + ARRAY_SIZE(wm8960_mono_out)), +}; + +/* Represent OUT3 as a PGA so that it gets turned on with LOUT1/ROUT1 */ +static const struct snd_soc_dapm_widget wm8960_dapm_widgets_capless[] = { +SND_SOC_DAPM_PGA("OUT3 VMID", WM8960_POWER2, 1, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_route audio_paths[] = { + { "Left Boost Mixer", NULL , "MICB"}, + { "Left Boost Mixer", "LINPUT1 Switch", "LINPUT1" }, + { "Left Boost Mixer", "LINPUT2 Switch", "LINPUT2" }, + { "Left Boost Mixer", "LINPUT3 Switch", "LINPUT3" }, + + { "Left Input Mixer", "Boost Switch", "Left Boost Mixer" }, + { "Left Input Mixer", "Boost Switch", "LINPUT1" }, /* Really Boost Switch */ + { "Left Input Mixer", NULL, "LINPUT2" }, + { "Left Input Mixer", NULL, "LINPUT3" }, + + { "Right Boost Mixer", NULL , "MICB"}, + { "Right Boost Mixer", "RINPUT1 Switch", "RINPUT1" }, + { "Right Boost Mixer", "RINPUT2 Switch", "RINPUT2" }, + { "Right Boost Mixer", "RINPUT3 Switch", "RINPUT3" }, + + { "Right Input Mixer", "Boost Switch", "Right Boost Mixer" }, + { "Right Input Mixer", "Boost Switch", "RINPUT1" }, /* Really Boost Switch */ + { "Right Input Mixer", NULL, "RINPUT2" }, + { "Right Input Mixer", NULL, "RINPUT3" }, + + { "Left ADC", NULL, "Left Input Mixer" }, + { "Right ADC", NULL, "Right Input Mixer" }, + + { "Left Output Mixer", "LINPUT3 Switch", "LINPUT3" }, + { "Left Output Mixer", "Boost Bypass Switch", "Left Boost Mixer" }, + { "Left Output Mixer", "PCM Playback Switch", "Left DAC" }, + + { "Right Output Mixer", "RINPUT3 Switch", "RINPUT3" }, + { "Right Output Mixer", "Boost Bypass Switch", "Right Boost Mixer" }, + { "Right Output Mixer", "PCM Playback Switch", "Right DAC" }, + + { "LOUT1 PGA", NULL, "Left Output Mixer" }, + { "ROUT1 PGA", NULL, "Right Output Mixer" }, + + { "HP_L", NULL, "LOUT1 PGA" }, + { "HP_R", NULL, "ROUT1 PGA" }, + + { "Left Speaker PGA", NULL, "Left Output Mixer" }, + { "Right Speaker PGA", NULL, "Right Output Mixer" }, + + { "Left Speaker Output", NULL, "Left Speaker PGA" }, + { "Right Speaker Output", NULL, "Right Speaker PGA" }, + + { "SPK_LN", NULL, "Left Speaker Output" }, + { "SPK_LP", NULL, "Left Speaker Output" }, + { "SPK_RN", NULL, "Right Speaker Output" }, + { "SPK_RP", NULL, "Right Speaker Output" }, +}; + +static const struct snd_soc_dapm_route audio_paths_out3[] = { + { "Mono Output Mixer", "Left Switch", "Left Output Mixer" }, + { "Mono Output Mixer", "Right Switch", "Right Output Mixer" }, + + { "OUT3", NULL, "Mono Output Mixer", } +}; + +static const struct snd_soc_dapm_route audio_paths_capless[] = { + { "HP_L", NULL, "OUT3 VMID" }, + { "HP_R", NULL, "OUT3 VMID" }, + + { "OUT3 VMID", NULL, "Left Output Mixer" }, + { "OUT3 VMID", NULL, "Right Output Mixer" }, +}; + +static int wm8960_add_widgets(struct snd_soc_codec *codec) +{ + struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + struct wm8960_data *pdata = &wm8960->pdata; + struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec); + struct snd_soc_dapm_widget *w; + + snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets, + ARRAY_SIZE(wm8960_dapm_widgets)); + + snd_soc_dapm_add_routes(dapm, audio_paths, ARRAY_SIZE(audio_paths)); + + /* In capless mode OUT3 is used to provide VMID for the + * headphone outputs, otherwise it is used as a mono mixer. + */ + if (pdata && pdata->capless) { + snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets_capless, + ARRAY_SIZE(wm8960_dapm_widgets_capless)); + + snd_soc_dapm_add_routes(dapm, audio_paths_capless, + ARRAY_SIZE(audio_paths_capless)); + } else { + snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets_out3, + ARRAY_SIZE(wm8960_dapm_widgets_out3)); + + snd_soc_dapm_add_routes(dapm, audio_paths_out3, + ARRAY_SIZE(audio_paths_out3)); + } + + /* We need to power up the headphone output stage out of + * sequence for capless mode. To save scanning the widget + * list each time to find the desired power state do so now + * and save the result. + */ + #if __NO_SND_SOC_CODEC_DRV + list_for_each_entry(w, &codec->card->widgets, list) { + #else + list_for_each_entry(w, &codec->component.card->widgets, list) { + #endif + if (w->dapm != dapm) + continue; + if (strcmp(w->name, "LOUT1 PGA") == 0) + wm8960->lout1 = w; + if (strcmp(w->name, "ROUT1 PGA") == 0) + wm8960->rout1 = w; + if (strcmp(w->name, "OUT3 VMID") == 0) + wm8960->out3 = w; + } + + return 0; +} + +static int wm8960_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface |= 0x0040; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= 0x0013; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x0090; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0010; + break; + default: + return -EINVAL; + } + + /* set iface */ + snd_soc_write(codec, WM8960_IFACE1, iface); + return 0; +} + +static struct { + int rate; + unsigned int val; +} alc_rates[] = { + { 48000, 0 }, + { 44100, 0 }, + { 32000, 1 }, + { 22050, 2 }, + { 24000, 2 }, + { 16000, 3 }, + { 11025, 4 }, + { 12000, 4 }, + { 8000, 5 }, +}; + +/* -1 for reserved value */ +static const int sysclk_divs[] = { 1, -1, 2, -1 }; + +/* Multiply 256 for internal 256 div */ +static const int dac_divs[] = { 256, 384, 512, 768, 1024, 1408, 1536 }; + +/* Multiply 10 to eliminate decimials */ +static const int bclk_divs[] = { + 10, 15, 20, 30, 40, 55, 60, 80, 110, + 120, 160, 220, 240, 320, 320, 320 +}; + +static int wm8960_configure_clocking(struct snd_soc_codec *codec) +{ + struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + int sysclk, bclk, lrclk, freq_out, freq_in; + u16 iface1 = snd_soc_read(codec, WM8960_IFACE1); + int i, j, k; + + if (!(iface1 & (1<<6))) { + dev_dbg(codec->dev, + "Codec is slave mode, no need to configure clock\n"); + //return 0; + } + + if (wm8960->clk_id != WM8960_SYSCLK_MCLK && !wm8960->freq_in) { + dev_err(codec->dev, "No MCLK configured\n"); + return -EINVAL; + } + + freq_in = wm8960->freq_in; + bclk = wm8960->bclk; + lrclk = wm8960->lrclk; + + //printk("clk_id %d freq_in: %d bclk: %d lrclk: %d\n",wm8960->clk_id ,freq_in, bclk,lrclk); + + /* + * If it's sysclk auto mode, check if the MCLK can provide sysclk or + * not. If MCLK can provide sysclk, using MCLK to provide sysclk + * directly. Otherwise, auto select a available pll out frequency + * and set PLL. + */ + if (wm8960->clk_id == WM8960_SYSCLK_AUTO) { + /* disable the PLL and using MCLK to provide sysclk */ + wm8960_set_pll(codec, 0, 0); + freq_out = freq_in; + } else if (wm8960->sysclk) { + freq_out = wm8960->sysclk; + } else { + dev_err(codec->dev, "No SYSCLK configured\n"); + return -EINVAL; + } + + if (wm8960->clk_id != WM8960_SYSCLK_PLL) { + /* check if the sysclk frequency is available. */ + for (i = 0; i < ARRAY_SIZE(sysclk_divs); ++i) { + if (sysclk_divs[i] == -1) + continue; + sysclk = freq_out / sysclk_divs[i]; + for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) { + if (sysclk != dac_divs[j] * lrclk) + continue; + for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k) + if (sysclk == bclk * bclk_divs[k] / 10) + break; + if (k != ARRAY_SIZE(bclk_divs)) + break; + } + if (j != ARRAY_SIZE(dac_divs)) + break; + } + + if (i != ARRAY_SIZE(sysclk_divs)) { + goto configure_clock; + } else if (wm8960->clk_id != WM8960_SYSCLK_AUTO) { + dev_err(codec->dev, "failed to configure clock\n"); + return -EINVAL; + } + } + /* get a available pll out frequency and set pll */ + for (i = 0; i < ARRAY_SIZE(sysclk_divs); ++i) { + if (sysclk_divs[i] == -1) + continue; + for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) { + sysclk = lrclk * dac_divs[j]; + freq_out = sysclk * sysclk_divs[i]; + + for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k) { + if (sysclk == bclk * bclk_divs[k] / 10 && + is_pll_freq_available(freq_in, freq_out)) { + wm8960_set_pll(codec, + freq_in, freq_out); + break; + } else { + continue; + } + } + if (k != ARRAY_SIZE(bclk_divs)) + break; + } + if (j != ARRAY_SIZE(dac_divs)) + break; + } + + if (i == ARRAY_SIZE(sysclk_divs)) { + dev_err(codec->dev, "failed to configure clock\n"); + return -EINVAL; + } + +configure_clock: + /* configure sysclk clock */ + snd_soc_update_bits(codec, WM8960_CLOCK1, 3 << 1, i << 1); + + /* configure frame clock */ + snd_soc_update_bits(codec, WM8960_CLOCK1, 0x7 << 3, j << 3); + snd_soc_update_bits(codec, WM8960_CLOCK1, 0x7 << 6, j << 6); + + /* configure bit clock */ + snd_soc_update_bits(codec, WM8960_CLOCK2, 0xf, k); + + return 0; +} + +static int wm8960_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + u16 iface = snd_soc_read(codec, WM8960_IFACE1) & 0xfff3; + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + int i; + + wm8960->bclk = snd_soc_params_to_bclk(params); + if (params_channels(params) == 1) + wm8960->bclk *= 2; + + /* bit size */ + switch (params_width(params)) { + case 16: + break; + case 20: + iface |= 0x0004; + break; + case 24: + iface |= 0x0008; + break; + case 32: + /* right justify mode does not support 32 word length */ + if ((iface & 0x3) != 0) { + iface |= 0x000c; + break; + } + fallthrough; + default: + dev_err(codec->dev, "unsupported width %d\n", + params_width(params)); + return -EINVAL; + } + + wm8960->lrclk = params_rate(params); + /* Update filters for the new rate */ + if (tx) { + wm8960_set_deemph(codec); + } else { + for (i = 0; i < ARRAY_SIZE(alc_rates); i++) + if (alc_rates[i].rate == params_rate(params)) + snd_soc_update_bits(codec, + WM8960_ADDCTL3, 0x7, + alc_rates[i].val); + } + + /* set iface */ + snd_soc_write(codec, WM8960_IFACE1, iface); + + wm8960->is_stream_in_use[tx] = true; + + if (snd_soc_codec_get_bias_level(codec) == SND_SOC_BIAS_ON && + !wm8960->is_stream_in_use[!tx]) + return wm8960_configure_clocking(codec); + + return 0; +} + +static int wm8960_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + + wm8960->is_stream_in_use[tx] = false; + + return 0; +} + +static int wm8960_mute(struct snd_soc_dai *dai, int mute, int direction) +{ + struct snd_soc_codec *codec = dai->codec; + + if (mute) + snd_soc_update_bits(codec, WM8960_DACCTL1, 0x8, 0x8); + else + snd_soc_update_bits(codec, WM8960_DACCTL1, 0x8, 0); + return 0; +} + +static int wm8960_set_bias_level_out3(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + u16 pm2 = snd_soc_read(codec, WM8960_POWER2); + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + switch (snd_soc_codec_get_bias_level(codec)) { + case SND_SOC_BIAS_STANDBY: + if (!IS_ERR(wm8960->mclk)) { + ret = clk_prepare_enable(wm8960->mclk); + if (ret) { + dev_err(codec->dev, + "Failed to enable MCLK: %d\n", + ret); + return ret; + } + } + + ret = wm8960_configure_clocking(codec); + if (ret) + return ret; + + /* Set VMID to 2x50k */ + snd_soc_update_bits(codec, WM8960_POWER1, 0x180, 0x80); + break; + + case SND_SOC_BIAS_ON: + /* + * If it's sysclk auto mode, and the pll is enabled, + * disable the pll + */ + if (wm8960->clk_id == WM8960_SYSCLK_AUTO && (pm2 & 0x1)) + wm8960_set_pll(codec, 0, 0); + + if (!IS_ERR(wm8960->mclk)) + clk_disable_unprepare(wm8960->mclk); + break; + + default: + break; + } + + break; + + case SND_SOC_BIAS_STANDBY: + if (snd_soc_codec_get_bias_level(codec) == SND_SOC_BIAS_OFF) { + regcache_sync(wm8960->regmap); + + /* Enable anti-pop features */ + snd_soc_write(codec, WM8960_APOP1, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN | WM8960_BUFIOEN); + + /* Enable & ramp VMID at 2x50k */ + snd_soc_update_bits(codec, WM8960_POWER1, 0x80, 0x80); + msleep(100); + + /* Enable VREF */ + snd_soc_update_bits(codec, WM8960_POWER1, WM8960_VREF, + WM8960_VREF); + + /* Disable anti-pop features */ + snd_soc_write(codec, WM8960_APOP1, WM8960_BUFIOEN); + } + + /* Set VMID to 2x250k */ + snd_soc_update_bits(codec, WM8960_POWER1, 0x180, 0x100); + break; + + case SND_SOC_BIAS_OFF: + /* Enable anti-pop features */ + snd_soc_write(codec, WM8960_APOP1, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN | WM8960_BUFIOEN); + + /* Disable VMID and VREF, let them discharge */ + snd_soc_write(codec, WM8960_POWER1, 0); + msleep(600); + break; + } + + return 0; +} + +static int wm8960_set_bias_level_capless(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + u16 pm2 = snd_soc_read(codec, WM8960_POWER2); + int reg, ret; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + switch (snd_soc_codec_get_bias_level(codec)) { + case SND_SOC_BIAS_STANDBY: + /* Enable anti pop mode */ + snd_soc_update_bits(codec, WM8960_APOP1, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN); + + /* Enable LOUT1, ROUT1 and OUT3 if they're enabled */ + reg = 0; + if (wm8960->lout1 && wm8960->lout1->power) + reg |= WM8960_PWR2_LOUT1; + if (wm8960->rout1 && wm8960->rout1->power) + reg |= WM8960_PWR2_ROUT1; + if (wm8960->out3 && wm8960->out3->power) + reg |= WM8960_PWR2_OUT3; + snd_soc_update_bits(codec, WM8960_POWER2, + WM8960_PWR2_LOUT1 | + WM8960_PWR2_ROUT1 | + WM8960_PWR2_OUT3, reg); + + /* Enable VMID at 2*50k */ + snd_soc_update_bits(codec, WM8960_POWER1, + WM8960_VMID_MASK, 0x80); + + /* Ramp */ + msleep(100); + + /* Enable VREF */ + snd_soc_update_bits(codec, WM8960_POWER1, + WM8960_VREF, WM8960_VREF); + + msleep(100); + + if (!IS_ERR(wm8960->mclk)) { + ret = clk_prepare_enable(wm8960->mclk); + if (ret) { + dev_err(codec->dev, + "Failed to enable MCLK: %d\n", + ret); + return ret; + } + } + + ret = wm8960_configure_clocking(codec); + if (ret) + return ret; + + break; + + case SND_SOC_BIAS_ON: + /* + * If it's sysclk auto mode, and the pll is enabled, + * disable the pll + */ + if (wm8960->clk_id == WM8960_SYSCLK_AUTO && (pm2 & 0x1)) + wm8960_set_pll(codec, 0, 0); + + if (!IS_ERR(wm8960->mclk)) + clk_disable_unprepare(wm8960->mclk); + + /* Enable anti-pop mode */ + snd_soc_update_bits(codec, WM8960_APOP1, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN); + + /* Disable VMID and VREF */ + snd_soc_update_bits(codec, WM8960_POWER1, + WM8960_VREF | WM8960_VMID_MASK, 0); + break; + + case SND_SOC_BIAS_OFF: + regcache_sync(wm8960->regmap); + break; + default: + break; + } + break; + + case SND_SOC_BIAS_STANDBY: + switch (snd_soc_codec_get_bias_level(codec)) { + case SND_SOC_BIAS_PREPARE: + /* Disable HP discharge */ + snd_soc_update_bits(codec, WM8960_APOP2, + WM8960_DISOP | WM8960_DRES_MASK, + 0); + + /* Disable anti-pop features */ + snd_soc_update_bits(codec, WM8960_APOP1, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN); + break; + + default: + break; + } + break; + + case SND_SOC_BIAS_OFF: + break; + } + + return 0; +} + +/* PLL divisors */ +struct _pll_div { + u32 pre_div:1; + u32 n:4; + u32 k:24; +}; + +static bool is_pll_freq_available(unsigned int source, unsigned int target) +{ + unsigned int Ndiv; + + if (source == 0 || target == 0) + return false; + + /* Scale up target to PLL operating frequency */ + target *= 4; + Ndiv = target / source; + + if (Ndiv < 6) { + source >>= 1; + Ndiv = target / source; + } + + if ((Ndiv < 6) || (Ndiv > 12)) + return false; + + return true; +} + +/* The size in bits of the pll divide multiplied by 10 + * to allow rounding later */ +#define FIXED_PLL_SIZE ((1 << 24) * 10) + +static int pll_factors(unsigned int source, unsigned int target, + struct _pll_div *pll_div) +{ + unsigned long long Kpart; + unsigned int K, Ndiv, Nmod; + + pr_debug("WM8960 PLL: setting %dHz->%dHz\n", source, target); + + /* Scale up target to PLL operating frequency */ + target *= 4; + + Ndiv = target / source; + if (Ndiv < 6) { + source >>= 1; + pll_div->pre_div = 1; + Ndiv = target / source; + } else + pll_div->pre_div = 0; + + if ((Ndiv < 6) || (Ndiv > 12)) { + pr_err("WM8960 PLL: Unsupported N=%d\n", Ndiv); + return -EINVAL; + } + + pll_div->n = Ndiv; + Nmod = target % source; + Kpart = FIXED_PLL_SIZE * (long long)Nmod; + + do_div(Kpart, source); + + K = Kpart & 0xFFFFFFFF; + + /* Check if we need to round */ + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + K /= 10; + + pll_div->k = K; + + pr_debug("WM8960 PLL: N=%x K=%x pre_div=%d\n", + pll_div->n, pll_div->k, pll_div->pre_div); + + return 0; +} + +static int wm8960_set_pll(struct snd_soc_codec *codec, + unsigned int freq_in, unsigned int freq_out) +{ + u16 reg; + static struct _pll_div pll_div; + int ret; + + if (freq_in && freq_out) { + ret = pll_factors(freq_in, freq_out, &pll_div); + if (ret != 0) + return ret; + } + + /* Disable the PLL: even if we are changing the frequency the + * PLL needs to be disabled while we do so. */ + snd_soc_update_bits(codec, WM8960_CLOCK1, 0x1, 0); + snd_soc_update_bits(codec, WM8960_POWER2, 0x1, 0); + + if (!freq_in || !freq_out) + return 0; + + reg = snd_soc_read(codec, WM8960_PLL1) & ~0x3f; + reg |= pll_div.pre_div << 4; + reg |= pll_div.n; + + if (pll_div.k) { + reg |= 0x20; + + snd_soc_write(codec, WM8960_PLL2, (pll_div.k >> 16) & 0xff); + snd_soc_write(codec, WM8960_PLL3, (pll_div.k >> 8) & 0xff); + snd_soc_write(codec, WM8960_PLL4, pll_div.k & 0xff); + } + snd_soc_write(codec, WM8960_PLL1, reg); + + /* Turn it on */ + snd_soc_update_bits(codec, WM8960_POWER2, 0x1, 0x1); + msleep(250); + snd_soc_update_bits(codec, WM8960_CLOCK1, 0x1, 0x1); + + return 0; +} + +static int wm8960_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) +{ + + struct snd_soc_codec *codec = codec_dai->codec; + struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + + wm8960->freq_in = freq_in; + + if (pll_id == WM8960_SYSCLK_AUTO) + return 0; + return wm8960_set_pll(codec, freq_in, freq_out); +} + +static int wm8960_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + + + switch (div_id) { + case WM8960_SYSCLKDIV: + reg = snd_soc_read(codec, WM8960_CLOCK1) & 0x1f9; + snd_soc_write(codec, WM8960_CLOCK1, reg | div); + break; + case WM8960_DACDIV: + reg = snd_soc_read(codec, WM8960_CLOCK1) & 0x1c7; + snd_soc_write(codec, WM8960_CLOCK1, reg | div); + break; + case WM8960_OPCLKDIV: + reg = snd_soc_read(codec, WM8960_PLL1) & 0x03f; + snd_soc_write(codec, WM8960_PLL1, reg | div); + break; + case WM8960_DCLKDIV: + reg = snd_soc_read(codec, WM8960_CLOCK2) & 0x03f; + snd_soc_write(codec, WM8960_CLOCK2, reg | div); + break; + case WM8960_TOCLKSEL: + reg = snd_soc_read(codec, WM8960_ADDCTL1) & 0x1fd; + snd_soc_write(codec, WM8960_ADDCTL1, reg | div); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int wm8960_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + + return wm8960->set_bias_level(codec, level); +} + +static int wm8960_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + + struct snd_soc_codec *codec = dai->codec; + struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + clk_id = WM8960_SYSCLK_PLL; + + switch (clk_id) { + case WM8960_SYSCLK_MCLK: + snd_soc_update_bits(codec, WM8960_CLOCK1, + 0x1, WM8960_SYSCLK_MCLK); + break; + case WM8960_SYSCLK_PLL: + snd_soc_update_bits(codec, WM8960_CLOCK1, + 0x1, WM8960_SYSCLK_PLL); + break; + case WM8960_SYSCLK_AUTO: + break; + default: + return -EINVAL; + } + wm8960->freq_in = 24000000; + wm8960->sysclk = freq; + wm8960->clk_id = clk_id; + + return 0; +} + +#define WM8960_RATES SNDRV_PCM_RATE_8000_48000 + +#define WM8960_FORMATS \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops wm8960_dai_ops = { + .hw_params = wm8960_hw_params, + .hw_free = wm8960_hw_free, + .mute_stream = wm8960_mute, + .set_fmt = wm8960_set_dai_fmt, + .set_clkdiv = wm8960_set_dai_clkdiv, + .set_pll = wm8960_set_dai_pll, + .set_sysclk = wm8960_set_dai_sysclk, + .no_capture_mute = 1, +}; + +static struct snd_soc_dai_driver wm8960_dai = { + .name = "wm8960-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8960_RATES, + .formats = WM8960_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8960_RATES, + .formats = WM8960_FORMATS,}, + .ops = &wm8960_dai_ops, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,12,0) + .symmetric_rate = 1, +#else + .symmetric_rates = 1, +#endif +}; + +static int wm8960_probe(struct snd_soc_codec *codec) +{ + struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + struct wm8960_data *pdata = &wm8960->pdata; + + if (pdata->capless) + wm8960->set_bias_level = wm8960_set_bias_level_capless; + else + wm8960->set_bias_level = wm8960_set_bias_level_out3; + + snd_soc_add_codec_controls(codec, wm8960_snd_controls, + ARRAY_SIZE(wm8960_snd_controls)); + wm8960_add_widgets(codec); + + return 0; +} + +static const struct snd_soc_codec_driver soc_codec_dev_wm8960 = { + .probe = wm8960_probe, + .set_bias_level = wm8960_set_bias_level, + .suspend_bias_off = true, + #if __NO_SND_SOC_CODEC_DRV + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, + #endif +}; + +static const struct regmap_config wm8960_regmap = { + .reg_bits = 7, + .val_bits = 9, + .max_register = WM8960_PLL4, + + .reg_defaults = wm8960_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wm8960_reg_defaults), + .cache_type = REGCACHE_RBTREE, + + .volatile_reg = wm8960_volatile, +}; + +static void wm8960_set_pdata_from_of(struct i2c_client *i2c, + struct wm8960_data *pdata) +{ + const struct device_node *np = i2c->dev.of_node; + + if (of_property_read_bool(np, "wlf,capless")) + pdata->capless = true; + + if (of_property_read_bool(np, "wlf,shared-lrclk")) + pdata->shared_lrclk = true; +} + +static int wm8960_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8960_data *pdata = dev_get_platdata(&i2c->dev); + struct wm8960_priv *wm8960; + int ret; + + wm8960 = devm_kzalloc(&i2c->dev, sizeof(struct wm8960_priv), + GFP_KERNEL); + if (wm8960 == NULL) + return -ENOMEM; + + wm8960->clk_id = WM8960_SYSCLK_PLL; + + wm8960->mclk = devm_clk_get(&i2c->dev, "mclk"); + + if (IS_ERR(wm8960->mclk)) { + if (PTR_ERR(wm8960->mclk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + } + + wm8960->regmap = devm_regmap_init_i2c(i2c, &wm8960_regmap); + if (IS_ERR(wm8960->regmap)) + return PTR_ERR(wm8960->regmap); + + if (pdata) + memcpy(&wm8960->pdata, pdata, sizeof(struct wm8960_data)); + else if (i2c->dev.of_node) + wm8960_set_pdata_from_of(i2c, &wm8960->pdata); + + ret = wm8960_reset(wm8960->regmap); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to issue reset\n"); + return ret; + } + + if (wm8960->pdata.shared_lrclk) { + ret = regmap_update_bits(wm8960->regmap, WM8960_ADDCTL2, + 0x4, 0x4); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to enable LRCM: %d\n", + ret); + return ret; + } + } + + /* Latch the update bits */ + regmap_update_bits(wm8960->regmap, WM8960_LINVOL, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_RINVOL, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_LADC, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_RADC, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_LDAC, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_RDAC, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_LOUT1, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_ROUT1, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_LOUT2, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_ROUT2, 0x100, 0x100); + + i2c_set_clientdata(i2c, wm8960); + + ret = snd_soc_register_codec(&i2c->dev, + &soc_codec_dev_wm8960, &wm8960_dai, 1); + + return ret; +} + +static int wm8960_i2c_remove(struct i2c_client *client) +{ + snd_soc_unregister_codec(&client->dev); + return 0; +} + +static const struct i2c_device_id wm8960_i2c_id[] = { + { "wm8960", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8960_i2c_id); + +static const struct of_device_id wm8960_of_match[] = { + { .compatible = "wlf,wm8960", }, + { } +}; +MODULE_DEVICE_TABLE(of, wm8960_of_match); + +static struct i2c_driver wm8960_i2c_driver = { + .driver = { + .name = "wm8960", + .of_match_table = wm8960_of_match, + }, + .probe = wm8960_i2c_probe, + .remove = wm8960_i2c_remove, + .id_table = wm8960_i2c_id, +}; + +module_i2c_driver(wm8960_i2c_driver); + +MODULE_DESCRIPTION("ASoC WM8960 driver"); +MODULE_AUTHOR("Liam Girdwood"); +MODULE_LICENSE("GPL"); diff --git a/seeed-voicecard/wm8960.h b/seeed-voicecard/wm8960.h new file mode 100644 index 0000000..ab3220d --- /dev/null +++ b/seeed-voicecard/wm8960.h @@ -0,0 +1,114 @@ +/* + * wm8960.h -- WM8960 Soc Audio driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _WM8960_H +#define _WM8960_H + +/* WM8960 register space */ + + +#define WM8960_CACHEREGNUM 56 + +#define WM8960_LINVOL 0x0 +#define WM8960_RINVOL 0x1 +#define WM8960_LOUT1 0x2 +#define WM8960_ROUT1 0x3 +#define WM8960_CLOCK1 0x4 +#define WM8960_DACCTL1 0x5 +#define WM8960_DACCTL2 0x6 +#define WM8960_IFACE1 0x7 +#define WM8960_CLOCK2 0x8 +#define WM8960_IFACE2 0x9 +#define WM8960_LDAC 0xa +#define WM8960_RDAC 0xb + +#define WM8960_RESET 0xf +#define WM8960_3D 0x10 +#define WM8960_ALC1 0x11 +#define WM8960_ALC2 0x12 +#define WM8960_ALC3 0x13 +#define WM8960_NOISEG 0x14 +#define WM8960_LADC 0x15 +#define WM8960_RADC 0x16 +#define WM8960_ADDCTL1 0x17 +#define WM8960_ADDCTL2 0x18 +#define WM8960_POWER1 0x19 +#define WM8960_POWER2 0x1a +#define WM8960_ADDCTL3 0x1b +#define WM8960_APOP1 0x1c +#define WM8960_APOP2 0x1d + +#define WM8960_LINPATH 0x20 +#define WM8960_RINPATH 0x21 +#define WM8960_LOUTMIX 0x22 + +#define WM8960_ROUTMIX 0x25 +#define WM8960_MONOMIX1 0x26 +#define WM8960_MONOMIX2 0x27 +#define WM8960_LOUT2 0x28 +#define WM8960_ROUT2 0x29 +#define WM8960_MONO 0x2a +#define WM8960_INBMIX1 0x2b +#define WM8960_INBMIX2 0x2c +#define WM8960_BYPASS1 0x2d +#define WM8960_BYPASS2 0x2e +#define WM8960_POWER3 0x2f +#define WM8960_ADDCTL4 0x30 +#define WM8960_CLASSD1 0x31 + +#define WM8960_CLASSD3 0x33 +#define WM8960_PLL1 0x34 +#define WM8960_PLL2 0x35 +#define WM8960_PLL3 0x36 +#define WM8960_PLL4 0x37 + + +/* + * WM8960 Clock dividers + */ +#define WM8960_SYSCLKDIV 0 +#define WM8960_DACDIV 1 +#define WM8960_OPCLKDIV 2 +#define WM8960_DCLKDIV 3 +#define WM8960_TOCLKSEL 4 + +#define WM8960_SYSCLK_DIV_1 (0 << 1) +#define WM8960_SYSCLK_DIV_2 (2 << 1) + +#define WM8960_SYSCLK_MCLK (0 << 0) +#define WM8960_SYSCLK_PLL (1 << 0) +#define WM8960_SYSCLK_AUTO (2 << 0) + +#define WM8960_DAC_DIV_1 (0 << 3) +#define WM8960_DAC_DIV_1_5 (1 << 3) +#define WM8960_DAC_DIV_2 (2 << 3) +#define WM8960_DAC_DIV_3 (3 << 3) +#define WM8960_DAC_DIV_4 (4 << 3) +#define WM8960_DAC_DIV_5_5 (5 << 3) +#define WM8960_DAC_DIV_6 (6 << 3) + +#define WM8960_DCLK_DIV_1_5 (0 << 6) +#define WM8960_DCLK_DIV_2 (1 << 6) +#define WM8960_DCLK_DIV_3 (2 << 6) +#define WM8960_DCLK_DIV_4 (3 << 6) +#define WM8960_DCLK_DIV_6 (4 << 6) +#define WM8960_DCLK_DIV_8 (5 << 6) +#define WM8960_DCLK_DIV_12 (6 << 6) +#define WM8960_DCLK_DIV_16 (7 << 6) + +#define WM8960_TOCLK_F19 (0 << 1) +#define WM8960_TOCLK_F21 (1 << 1) + +#define WM8960_OPCLK_DIV_1 (0 << 0) +#define WM8960_OPCLK_DIV_2 (1 << 0) +#define WM8960_OPCLK_DIV_3 (2 << 0) +#define WM8960_OPCLK_DIV_4 (3 << 0) +#define WM8960_OPCLK_DIV_5_5 (4 << 0) +#define WM8960_OPCLK_DIV_6 (5 << 0) + +#endif diff --git a/seeed-voicecard/wm8960_asound.state b/seeed-voicecard/wm8960_asound.state new file mode 100644 index 0000000..079806b --- /dev/null +++ b/seeed-voicecard/wm8960_asound.state @@ -0,0 +1,758 @@ +state.ALSA { + control.1 { + iface MIXER + name 'PCM Playback Volume' + value -1994 + comment { + access 'read write' + type INTEGER + count 1 + range '-10239 - 400' + dbmin -9999999 + dbmax 400 + dbvalue.0 -1994 + } + } + control.2 { + iface MIXER + name 'PCM Playback Switch' + value true + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.3 { + iface MIXER + name 'PCM Playback Route' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 2' + } + } + control.4 { + iface PCM + name 'IEC958 Playback Default' + value '0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' + comment { + access 'read write' + type IEC958 + count 1 + } + } + control.5 { + iface PCM + name 'IEC958 Playback Con Mask' + value '0200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' + comment { + access read + type IEC958 + count 1 + } + } + control.6 { + iface PCM + name 'IEC958 Playback PCM Stream' + value '0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' + comment { + access 'read write inactive' + type IEC958 + count 1 + } + } +} +state.seeed2micvoicec { + control.1 { + iface MIXER + name 'Capture Volume' + value.0 39 + value.1 39 + comment { + access 'read write' + type INTEGER + count 2 + range '0 - 63' + dbmin -1725 + dbmax 3000 + dbvalue.0 1200 + dbvalue.1 1200 + } + } + control.2 { + iface MIXER + name 'Capture Volume ZC Switch' + value.0 0 + value.1 0 + comment { + access 'read write' + type INTEGER + count 2 + range '0 - 1' + } + } + control.3 { + iface MIXER + name 'Capture Switch' + value.0 true + value.1 true + comment { + access 'read write' + type BOOLEAN + count 2 + } + } + control.4 { + iface MIXER + name 'Left Input Boost Mixer LINPUT3 Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 7' + dbmin -9999999 + dbmax 600 + dbvalue.0 -9999999 + } + } + control.5 { + iface MIXER + name 'Left Input Boost Mixer LINPUT2 Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 7' + dbmin -9999999 + dbmax 600 + dbvalue.0 -9999999 + } + } + control.6 { + iface MIXER + name 'Right Input Boost Mixer RINPUT3 Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 7' + dbmin -9999999 + dbmax 600 + dbvalue.0 -9999999 + } + } + control.7 { + iface MIXER + name 'Right Input Boost Mixer RINPUT2 Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 7' + dbmin -9999999 + dbmax 600 + dbvalue.0 -9999999 + } + } + control.8 { + iface MIXER + name 'Right Input Boost Mixer RINPUT1 Volume' + value 3 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 3' + dbmin 0 + dbmax 2900 + dbvalue.0 2900 + } + } + control.9 { + iface MIXER + name 'Left Input Boost Mixer LINPUT1 Volume' + value 3 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 3' + dbmin 0 + dbmax 2900 + dbvalue.0 2900 + } + } + control.10 { + iface MIXER + name 'Playback Volume' + value.0 255 + value.1 255 + comment { + access 'read write' + type INTEGER + count 2 + range '0 - 255' + dbmin -9999999 + dbmax 0 + dbvalue.0 0 + dbvalue.1 0 + } + } + control.11 { + iface MIXER + name 'Headphone Playback Volume' + value.0 127 + value.1 127 + comment { + access 'read write' + type INTEGER + count 2 + range '0 - 127' + dbmin -9999999 + dbmax 600 + dbvalue.0 600 + dbvalue.1 600 + } + } + control.12 { + iface MIXER + name 'Headphone Playback ZC Switch' + value.0 false + value.1 false + comment { + access 'read write' + type BOOLEAN + count 2 + } + } + control.13 { + iface MIXER + name 'Speaker Playback Volume' + value.0 127 + value.1 127 + comment { + access 'read write' + type INTEGER + count 2 + range '0 - 127' + dbmin -9999999 + dbmax 600 + dbvalue.0 600 + dbvalue.1 600 + } + } + control.14 { + iface MIXER + name 'Speaker Playback ZC Switch' + value.0 false + value.1 false + comment { + access 'read write' + type BOOLEAN + count 2 + } + } + control.15 { + iface MIXER + name 'Speaker DC Volume' + value 4 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 5' + } + } + control.16 { + iface MIXER + name 'Speaker AC Volume' + value 5 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 5' + } + } + control.17 { + iface MIXER + name 'PCM Playback -6dB Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.18 { + iface MIXER + name 'ADC Polarity' + value 'No Inversion' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 'No Inversion' + item.1 'Left Inverted' + item.2 'Right Inverted' + item.3 'Stereo Inversion' + } + } + control.19 { + iface MIXER + name 'ADC High Pass Filter Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.20 { + iface MIXER + name 'DAC Polarity' + value 'No Inversion' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 'No Inversion' + item.1 'Left Inverted' + item.2 'Right Inverted' + item.3 'Stereo Inversion' + } + } + control.21 { + iface MIXER + name 'DAC Deemphasis Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.22 { + iface MIXER + name '3D Filter Upper Cut-Off' + value High + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 High + item.1 Low + } + } + control.23 { + iface MIXER + name '3D Filter Lower Cut-Off' + value Low + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Low + item.1 High + } + } + control.24 { + iface MIXER + name '3D Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 15' + } + } + control.25 { + iface MIXER + name '3D Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.26 { + iface MIXER + name 'ALC Function' + value Off + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Off + item.1 Right + item.2 Left + item.3 Stereo + } + } + control.27 { + iface MIXER + name 'ALC Max Gain' + value 7 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 7' + } + } + control.28 { + iface MIXER + name 'ALC Target' + value 4 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 15' + } + } + control.29 { + iface MIXER + name 'ALC Min Gain' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 7' + } + } + control.30 { + iface MIXER + name 'ALC Hold Time' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 15' + } + } + control.31 { + iface MIXER + name 'ALC Mode' + value ALC + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 ALC + item.1 Limiter + } + } + control.32 { + iface MIXER + name 'ALC Decay' + value 3 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 15' + } + } + control.33 { + iface MIXER + name 'ALC Attack' + value 2 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 15' + } + } + control.34 { + iface MIXER + name 'Noise Gate Threshold' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 31' + } + } + control.35 { + iface MIXER + name 'Noise Gate Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.36 { + iface MIXER + name 'ADC PCM Capture Volume' + value.0 195 + value.1 195 + comment { + access 'read write' + type INTEGER + count 2 + range '0 - 255' + dbmin -9999999 + dbmax 3000 + dbvalue.0 0 + dbvalue.1 0 + } + } + control.37 { + iface MIXER + name 'Left Output Mixer Boost Bypass Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 7' + dbmin -2100 + dbmax 0 + dbvalue.0 -2100 + } + } + control.38 { + iface MIXER + name 'Left Output Mixer LINPUT3 Volume' + value 0 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 7' + dbmin -2100 + dbmax 0 + dbvalue.0 -2100 + } + } + control.39 { + iface MIXER + name 'Right Output Mixer Boost Bypass Volume' + value 5 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 7' + dbmin -2100 + dbmax 0 + dbvalue.0 -600 + } + } + control.40 { + iface MIXER + name 'Right Output Mixer RINPUT3 Volume' + value 2 + comment { + access 'read write' + type INTEGER + count 1 + range '0 - 7' + dbmin -2100 + dbmax 0 + dbvalue.0 -1500 + } + } + control.41 { + iface MIXER + name 'ADC Data Output Select' + value 'Left Data = Left ADC; Right Data = Right ADC' + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 'Left Data = Left ADC; Right Data = Right ADC' + item.1 'Left Data = Left ADC; Right Data = Left ADC' + item.2 'Left Data = Right ADC; Right Data = Right ADC' + item.3 'Left Data = Right ADC; Right Data = Left ADC' + } + } + control.42 { + iface MIXER + name 'DAC Mono Mix' + value Stereo + comment { + access 'read write' + type ENUMERATED + count 1 + item.0 Stereo + item.1 Mono + } + } + control.43 { + iface MIXER + name 'Left Boost Mixer LINPUT2 Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.44 { + iface MIXER + name 'Left Boost Mixer LINPUT3 Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.45 { + iface MIXER + name 'Left Boost Mixer LINPUT1 Switch' + value true + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.46 { + iface MIXER + name 'Right Boost Mixer RINPUT2 Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.47 { + iface MIXER + name 'Right Boost Mixer RINPUT3 Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.48 { + iface MIXER + name 'Right Boost Mixer RINPUT1 Switch' + value true + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.49 { + iface MIXER + name 'Left Input Mixer Boost Switch' + value true + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.50 { + iface MIXER + name 'Right Input Mixer Boost Switch' + value true + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.51 { + iface MIXER + name 'Left Output Mixer PCM Playback Switch' + value true + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.52 { + iface MIXER + name 'Left Output Mixer LINPUT3 Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.53 { + iface MIXER + name 'Left Output Mixer Boost Bypass Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.54 { + iface MIXER + name 'Right Output Mixer PCM Playback Switch' + value true + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.55 { + iface MIXER + name 'Right Output Mixer RINPUT3 Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.56 { + iface MIXER + name 'Right Output Mixer Boost Bypass Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.57 { + iface MIXER + name 'Mono Output Mixer Left Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } + control.58 { + iface MIXER + name 'Mono Output Mixer Right Switch' + value false + comment { + access 'read write' + type BOOLEAN + count 1 + } + } +}