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/)!
+
+
+
+### Coherence
+
+Estimate the magnitude squared coherence using Welch’s method.
+
+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
+ }
+ }
+}