diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3a0c8ce
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,63 @@
+# Prerequisites
+*.d
+
+# Object files
+*.o
+*.ko
+*.obj
+*.elf
+
+# Linker output
+*.ilk
+*.map
+*.exp
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Libraries
+*.lib
+*.a
+*.la
+*.lo
+
+# Shared objects (inc. Windows DLLs)
+*.dll
+*.so
+*.so.*
+*.dylib
+
+# Executables
+*.exe
+*.out
+*.app
+*.i*86
+*.x86_64
+*.hex
+*.bin
+
+# Debug files
+*.dSYM/
+*.su
+*.idb
+*.pdb
+
+# Kernel Module Compile Results
+*.mod*
+*.cmd
+.tmp_versions/
+modules.order
+Module.symvers
+Mkfile.old
+dkms.conf
+
+# debug information files
+*.dwo
+
+*.cache
+compile_commands.json
+
+tests/core/test_core
+
+*.pdf
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..f288702
--- /dev/null
+++ b/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.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..1341e78
--- /dev/null
+++ b/README.md
@@ -0,0 +1,32 @@
+# wolfHAL
+
+wolfHAL is a lightweight, OS-agnostic, compiler-agnostic hardware abstraction
+layer for embedded targets written in C. It provides a uniform driver model
+based on vtable dispatch.
+
+## Repository layout
+
+```
+wolfHAL/ Public headers (API surface)
+ platform/ Platform-specific device macros and definitions
+src/ Driver implementations (generic + platform)
+boards/ Example board configurations used for testing and examples
+examples/ Example applications
+tests/ Test framework and test suites
+```
+
+## Further reading
+
+- [Getting Started](docs/getting_started.md) — Integrating wolfHAL into your project
+- [Boards](boards/README.md) — Example board configurations
+- [Examples](examples/README.md) — Example applications
+- [Tests](tests/README.md) — Test framework and test suites
+- [Writing a Driver](docs/writing_a_driver.md) — How to implement a driver for a new platform
+- [Adding a Board](docs/adding_a_board.md) — How to add a new board configuration
+- [Adding a Peripheral](docs/adding_a_peripheral.md) — How to add an external peripheral device
+- [Adding an Example](docs/adding_an_example.md) — How to add a new example application
+- [Adding a Test](docs/adding_a_test.md) — How to add hardware tests
+
+## License
+
+GPLv3 -- see [LICENSE](LICENSE) for details.
diff --git a/boards/README.md b/boards/README.md
new file mode 100644
index 0000000..f8e1887
--- /dev/null
+++ b/boards/README.md
@@ -0,0 +1,78 @@
+# wolfHAL Example Board Definitions
+
+The board definitions in this directory are **examples** for use with
+wolfHAL's tests and sample applications. They are configured for specific
+development boards and are not intended for production use. Users should
+create their own board support packages tailored to their hardware.
+
+Each subdirectory contains a board support package (BSP) for a specific
+development board. A BSP provides everything needed to build wolfHAL for a
+given target: startup code, peripheral initialization, linker script, and
+build configuration.
+
+## Supported Boards
+
+| Board | Platform | CPU | Directory |
+|-------|----------|-----|-----------|
+| Microchip PIC32CZ CA Curiosity Ultra | PIC32CZ | Cortex-M7 | `pic32cz_curiosity_ultra/` |
+| ST NUCLEO-C031C6 | STM32C0 | Cortex-M0+ | `stm32c031_nucleo/` |
+| ST NUCLEO-F091RC | STM32F0 | Cortex-M0 | `stm32f091rc_nucleo/` |
+| ST NUCLEO-F302R8 | STM32F3 | Cortex-M4 | `stm32f302r8_nucleo/` |
+| WeAct BlackPill STM32F411 | STM32F4 | Cortex-M4 | `stm32f411_blackpill/` |
+| ST NUCLEO-H563ZI | STM32H5 | Cortex-M33 | `stm32h563zi_nucleo/` |
+| ST NUCLEO-WB55RG | STM32WB | Cortex-M4 | `stm32wb55xx_nucleo/` |
+| ST NUCLEO-L152RE | STM32L1 | Cortex-M3 | `stm32l152re_nucleo/` |
+| ST NUCLEO-N657X0-Q | STM32N6 | Cortex-M55 | `stm32n657a0_nucleo/` |
+| ST NUCLEO-WBA55CG | STM32WBA | Cortex-M33 | `stm32wba55cg_nucleo/` |
+
+## Board Directory Contents
+
+Each board directory contains:
+
+- **`board.mk`** - Build configuration: toolchain, CPU flags, platform
+ drivers, and linker script. Included by application Makefiles via
+ `include $(BOARD_DIR)/board.mk`.
+- **`board.h`** - Board-level declarations: global peripheral instances,
+ pin definitions, and `Board_Init()`/`Board_Deinit()` prototypes.
+- **`board.c`** - Peripheral instantiation and `Board_Init()` implementation
+ (power, clock, GPIO, UART, flash, timer).
+- **`linker.ld`** - Linker script defining memory regions (flash, RAM).
+- Any additional board-specific source files (e.g. interrupt vector table,
+ architecture-specific startup code).
+
+## board.mk Convention
+
+Board `board.mk` files use a self-referencing pattern so that they can be
+included from any directory:
+
+```makefile
+_BOARD_DIR := $(patsubst %/,%,$(dir $(lastword $(MAKEFILE_LIST))))
+```
+
+`_BOARD_DIR` points to the board's own directory, while the application
+Makefile sets `BOARD_DIR` which may point elsewhere (e.g. a private board
+overlay). This enables private repositories to extend a board by including
+the base `board.mk` and adding additional sources.
+
+### What `BOARD_SOURCE` includes
+
+Board `board.mk` populates `BOARD_SOURCE` with all of the sources
+required to build the wolfHAL tests and sample applications for that board:
+
+- Board files: `board.c` and any additional board-specific source files
+- Platform / SoC drivers: e.g. `pic32cz_*.c`, `stm32wb_*.c`
+- Architecture support: `systick.c` and any related startup / vector code
+- Core wolfHAL modules and common sources: generic drivers such as
+ `gpio.c`, `clock.c`, `uart.c`, and other files under `src/*.c`
+
+In your own projects you may either reuse these defaults by including the
+board `board.mk` as-is, or define your own `BOARD_SOURCE` in your
+application Makefile to select a different set of modules.
+
+## Adding a New Board
+
+1. Create a new directory: `boards/_/`
+2. Add `board.mk` following the `_BOARD_DIR` pattern above
+3. Implement `board.h`, `board.c`, and `linker.ld`
+4. Set `PLATFORM`, `TESTS`, toolchain variables, `CFLAGS`, and `BOARD_SOURCE`
+5. Build with `make BOARD=`
diff --git a/boards/stm32l152re_nucleo/board.c b/boards/stm32l152re_nucleo/board.c
new file mode 100644
index 0000000..fb1b3c3
--- /dev/null
+++ b/boards/stm32l152re_nucleo/board.c
@@ -0,0 +1,225 @@
+/* Example board configuration for the NUCLEO-L152RE dev board */
+
+#include
+#include
+#include "board.h"
+#include
+
+volatile uint32_t g_tick = 0;
+
+void SysTick_Handler(void)
+{
+ g_tick++;
+}
+
+uint32_t Board_GetTick(void)
+{
+ return g_tick;
+}
+
+whal_Timeout g_whalTimeout = {
+ .timeoutTicks = 1000, /* 1s */
+ .GetTick = Board_GetTick,
+};
+
+/* Clock */
+whal_Clock g_whalClock = {
+ .regmap = { WHAL_STM32L152_RCC_REGMAP },
+};
+
+static const whal_Stm32l1_Rcc_PeriphClk g_pwrClock = {WHAL_STM32L152_PWR_CLOCK};
+
+static const whal_Stm32l1_Rcc_PeriphClk g_periphClks[] = {
+ {WHAL_STM32L152_GPIOA_CLOCK},
+ {WHAL_STM32L152_USART2_CLOCK},
+};
+#define PERIPH_CLK_COUNT (sizeof(g_periphClks) / sizeof(g_periphClks[0]))
+
+/* Power */
+whal_Power g_whalPower = {
+ .regmap = { WHAL_STM32L152_PWR_REGMAP },
+};
+
+/* GPIO */
+whal_Gpio g_whalGpio = {
+ .regmap = { WHAL_STM32L152_GPIO_REGMAP },
+
+ .cfg = &(whal_Stm32l1_Gpio_Cfg) {
+ .pinCfg = (whal_Stm32l1_Gpio_PinCfg[PIN_COUNT]) {
+ /* LD2 Green LED on PA5 (NUCLEO-L152RE / UM1724) */
+ [LED_PIN] = WHAL_STM32L1_GPIO_PIN(
+ WHAL_STM32L1_GPIO_PORT_A, 5, WHAL_STM32L1_GPIO_MODE_OUT,
+ WHAL_STM32L1_GPIO_OUTTYPE_PUSHPULL, WHAL_STM32L1_GPIO_SPEED_LOW,
+ WHAL_STM32L1_GPIO_PULL_NONE, 0),
+ /* USART2 TX on PA2, AF7 (ST-Link VCP) */
+ [UART_TX_PIN] = WHAL_STM32L1_GPIO_PIN(
+ WHAL_STM32L1_GPIO_PORT_A, 2, WHAL_STM32L1_GPIO_MODE_ALTFN,
+ WHAL_STM32L1_GPIO_OUTTYPE_PUSHPULL, WHAL_STM32L1_GPIO_SPEED_FAST,
+ WHAL_STM32L1_GPIO_PULL_UP, 7),
+ /* USART2 RX on PA3, AF7 (ST-Link VCP) */
+ [UART_RX_PIN] = WHAL_STM32L1_GPIO_PIN(
+ WHAL_STM32L1_GPIO_PORT_A, 3, WHAL_STM32L1_GPIO_MODE_ALTFN,
+ WHAL_STM32L1_GPIO_OUTTYPE_PUSHPULL, WHAL_STM32L1_GPIO_SPEED_FAST,
+ WHAL_STM32L1_GPIO_PULL_UP, 7),
+ },
+ .pinCount = PIN_COUNT,
+ },
+};
+
+/* UART */
+whal_Uart g_whalUart = {
+ .regmap = { WHAL_STM32L152_USART2_REGMAP },
+
+ .cfg = &(whal_Stm32l1_Uart_Cfg) {
+ .timeout = &g_whalTimeout,
+ .brr = WHAL_STM32L1_UART_BRR(32000000, 115200),
+ },
+};
+
+/* Timer (SysTick) */
+whal_Timer g_whalTimer = {
+ .regmap = { WHAL_CORTEX_M3_SYSTICK_REGMAP },
+ .driver = WHAL_CORTEX_M3_SYSTICK_DRIVER,
+
+ .cfg = &(whal_SysTick_Cfg) {
+ .cyclesPerTick = 32000000 / 1000,
+ .clkSrc = WHAL_SYSTICK_CLKSRC_SYSCLK,
+ .tickInt = WHAL_SYSTICK_TICKINT_ENABLED,
+ },
+};
+
+/* Flash */
+whal_Flash g_whalFlash = {
+ .regmap = { WHAL_STM32L152_FLASH_REGMAP },
+ .driver = WHAL_STM32L152_FLASH_DRIVER,
+
+ .cfg = &(whal_Stm32l1_Flash_Cfg) {
+ .timeout = &g_whalTimeout,
+ .startAddr = 0x08000000,
+ .size = 0x80000, /* 512 KB */
+ },
+};
+
+void Board_WaitMs(size_t ms)
+{
+ uint32_t startCount = g_tick;
+ while ((g_tick - startCount) < ms)
+ ;
+}
+
+whal_Error Board_Init(void)
+{
+ whal_Error err;
+
+ /* Enable PWR peripheral clock so the power driver can program VOS.
+ * Calling Enable on the RCC handle before whal_Clock_Init is safe —
+ * RCC is a board-level driver and only uses regmap.base. */
+ err = whal_Stm32l1_Rcc_EnablePeriphClk(&g_whalClock, &g_pwrClock);
+ if (err)
+ return err;
+
+ /* Switch regulator to range 1 (1.8 V) so the PLL can reach 32 MHz.
+ * Reset default is range 2, which caps SYSCLK at 16 MHz. */
+ err = whal_Stm32l1_Pwr_SetVosRange(&g_whalPower,
+ WHAL_STM32L1_PWR_VOS_RANGE_1,
+ &g_whalTimeout);
+ if (err)
+ return err;
+
+ /* Set flash latency before increasing clock speed.
+ * STM32L1: 0 WS for HCLK <= 16 MHz, 1 WS for 16 < HCLK <= 32 MHz. */
+ err = whal_Stm32l1_Flash_Ext_SetLatency(WHAL_STM32L1_FLASH_LATENCY_1);
+ if (err)
+ return err;
+
+ /* HSI 16 MHz -> PLL (HSI * 4 / 2 = 32 MHz) -> SYSCLK = PLL */
+ err = whal_Stm32l1_Rcc_EnableOsc(&g_whalClock,
+ &(whal_Stm32l1_Rcc_OscCfg){WHAL_STM32L1_RCC_HSI_CFG});
+ if (err)
+ return err;
+
+ err = whal_Stm32l1_Rcc_EnablePll(&g_whalClock, &(whal_Stm32l1_Rcc_PllCfg){
+ .clkSrc = WHAL_STM32L1_RCC_PLLSRC_HSI,
+ .pllmul = WHAL_STM32L1_RCC_PLLMUL_4,
+ .plldiv = WHAL_STM32L1_RCC_PLLDIV_2,
+ });
+ if (err)
+ return err;
+
+ err = whal_Stm32l1_Rcc_SetSysClock(&g_whalClock, WHAL_STM32L1_RCC_SYSCLK_SRC_PLL);
+ if (err)
+ return err;
+
+ for (size_t i = 0; i < PERIPH_CLK_COUNT; i++) {
+ err = whal_Stm32l1_Rcc_EnablePeriphClk(&g_whalClock, &g_periphClks[i]);
+ if (err)
+ return err;
+ }
+
+ err = whal_Gpio_Init(&g_whalGpio);
+ if (err)
+ return err;
+
+ err = whal_Uart_Init(&g_whalUart);
+ if (err)
+ return err;
+
+ err = whal_Flash_Init(&g_whalFlash);
+ if (err)
+ return err;
+
+ err = whal_Timer_Init(&g_whalTimer);
+ if (err)
+ return err;
+
+ err = whal_Timer_Start(&g_whalTimer);
+ if (err)
+ return err;
+
+ return WHAL_SUCCESS;
+}
+
+whal_Error Board_Deinit(void)
+{
+ whal_Error err;
+
+ err = whal_Timer_Stop(&g_whalTimer);
+ if (err)
+ return err;
+
+ err = whal_Timer_Deinit(&g_whalTimer);
+ if (err)
+ return err;
+
+ err = whal_Flash_Deinit(&g_whalFlash);
+ if (err)
+ return err;
+
+ err = whal_Uart_Deinit(&g_whalUart);
+ if (err)
+ return err;
+
+ err = whal_Gpio_Deinit(&g_whalGpio);
+ if (err)
+ return err;
+
+ for (size_t i = PERIPH_CLK_COUNT; i-- > 0; ) {
+ err = whal_Stm32l1_Rcc_DisablePeriphClk(&g_whalClock, &g_periphClks[i]);
+ if (err)
+ return err;
+ }
+
+ err = whal_Stm32l1_Rcc_DisablePeriphClk(&g_whalClock, &g_pwrClock);
+ if (err)
+ return err;
+
+ err = whal_Stm32l1_Rcc_SetSysClock(&g_whalClock, WHAL_STM32L1_RCC_SYSCLK_SRC_MSI);
+ if (err)
+ return err;
+
+ err = whal_Stm32l1_Rcc_DisablePll(&g_whalClock);
+ if (err)
+ return err;
+
+ return WHAL_SUCCESS;
+}
diff --git a/boards/stm32l152re_nucleo/board.h b/boards/stm32l152re_nucleo/board.h
new file mode 100644
index 0000000..c82e44e
--- /dev/null
+++ b/boards/stm32l152re_nucleo/board.h
@@ -0,0 +1,31 @@
+#ifndef BOARD_H
+#define BOARD_H
+
+#include
+#include
+#include
+
+extern whal_Clock g_whalClock;
+extern whal_Gpio g_whalGpio;
+extern whal_Timer g_whalTimer;
+extern whal_Uart g_whalUart;
+extern whal_Flash g_whalFlash;
+extern whal_Power g_whalPower;
+
+extern whal_Timeout g_whalTimeout;
+extern volatile uint32_t g_tick;
+
+enum {
+ LED_PIN,
+ UART_TX_PIN,
+ UART_RX_PIN,
+ PIN_COUNT,
+};
+
+#define BOARD_LED_PIN 0
+
+whal_Error Board_Init(void);
+whal_Error Board_Deinit(void);
+void Board_WaitMs(size_t ms);
+
+#endif /* BOARD_H */
diff --git a/boards/stm32l152re_nucleo/board.mk b/boards/stm32l152re_nucleo/board.mk
new file mode 100644
index 0000000..158ca07
--- /dev/null
+++ b/boards/stm32l152re_nucleo/board.mk
@@ -0,0 +1,40 @@
+_BOARD_DIR := $(patsubst %/,%,$(dir $(lastword $(MAKEFILE_LIST))))
+
+PLATFORM = stm32l1
+TESTS ?= clock gpio uart
+
+GCC = $(GCC_PATH)arm-none-eabi-gcc
+LD = $(GCC_PATH)arm-none-eabi-gcc
+OBJCOPY = $(GCC_PATH)arm-none-eabi-objcopy
+
+CFLAGS += -Wall -Werror $(INCLUDE) -g3 \
+ -ffreestanding -nostdlib \
+ -mcpu=cortex-m3 -mthumb \
+ -DPLATFORM_STM32L1 -MMD -MP \
+ -DWHAL_CFG_STM32L1_GPIO_DIRECT_API_MAPPING \
+ -DWHAL_CFG_STM32L1_UART_DIRECT_API_MAPPING \
+ -DWHAL_CFG_STM32L1_RCC_DIRECT_API_MAPPING
+LDFLAGS = -mcpu=cortex-m3 -mthumb \
+ -ffreestanding -nostartfiles -Wl,--omagic -static
+
+LINKER_SCRIPT ?= $(_BOARD_DIR)/linker.ld
+
+INCLUDE += -I$(_BOARD_DIR)
+
+BOARD_SOURCE = $(_BOARD_DIR)/ivt.c
+BOARD_SOURCE += $(_BOARD_DIR)/board.c
+BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/timer.c)
+BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/flash.c)
+BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/stm32l1_*.c)
+BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/systick.c)
+
+# Flash via openocd: make flash BOARD= IMAGE=
+OPENOCD ?= /opt/openocd/bin/openocd
+OPENOCD_INTERFACE ?= interface/stlink.cfg
+OPENOCD_TARGET ?= target/stm32l1.cfg
+
+.PHONY: flash
+flash:
+ @test -n "$(IMAGE)" || { echo "IMAGE= required" >&2; exit 1; }
+ $(OPENOCD) -f $(OPENOCD_INTERFACE) -f $(OPENOCD_TARGET) \
+ -c "program $(IMAGE) verify reset exit"
diff --git a/boards/stm32l152re_nucleo/ivt.c b/boards/stm32l152re_nucleo/ivt.c
new file mode 100644
index 0000000..adac400
--- /dev/null
+++ b/boards/stm32l152re_nucleo/ivt.c
@@ -0,0 +1,207 @@
+#include
+#include
+
+extern uint32_t _estack[];
+extern uint32_t _sidata[];
+extern uint32_t _sdata[];
+extern uint32_t _edata[];
+extern uint32_t _sbss[];
+extern uint32_t _ebss[];
+
+extern void main();
+
+void __attribute__((naked,noreturn)) Default_Handler()
+{
+ while(1);
+}
+
+void Reset_Handler() __attribute__((weak));
+void NMI_Handler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void HardFault_Handler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void MemManage_Handler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void BusFault_Handler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void UsageFault_Handler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void SVC_Handler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void DebugMon_Handler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void PendSV_Handler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void SysTick_Handler() __attribute__((weak, noreturn, alias("Default_Handler")));
+
+/* STM32L1xx peripheral interrupts (RM0038 Table 51, Cat.4/5/6) */
+void WWDG_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void PVD_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void TAMPER_STAMP_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void RTC_WKUP_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void FLASH_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void RCC_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void EXTI0_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void EXTI1_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void EXTI2_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void EXTI3_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void EXTI4_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void DMA1_Channel1_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void DMA1_Channel2_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void DMA1_Channel3_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void DMA1_Channel4_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void DMA1_Channel5_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void DMA1_Channel6_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void DMA1_Channel7_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void ADC1_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void USB_HP_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void USB_LP_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void DAC_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void COMP_CA_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void EXTI9_5_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void LCD_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void TIM9_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void TIM10_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void TIM11_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void TIM2_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void TIM3_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void TIM4_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void I2C1_EV_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void I2C1_ER_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void I2C2_EV_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void I2C2_ER_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void SPI1_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void SPI2_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void USART1_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void USART2_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void USART3_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void EXTI15_10_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void RTC_Alarm_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void USB_FS_WKUP_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void TIM6_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void TIM7_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void SDIO_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void TIM5_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void SPI3_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void UART4_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void UART5_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void DMA2_Channel1_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void DMA2_Channel2_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void DMA2_Channel3_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void DMA2_Channel4_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void DMA2_Channel5_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void AES_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void COMP_ACQ_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+
+#define RESERVED Default_Handler
+
+void *memcpy(void *dest, const void *src, size_t n)
+{
+ unsigned char *d = dest;
+ const unsigned char *s = src;
+
+ for (size_t i = 0; i < n; i++)
+ d[i] = s[i];
+
+ return dest;
+}
+
+void *memset(void *s, int c, size_t n)
+{
+ unsigned char *p = s;
+ unsigned char v = (unsigned char)c;
+
+ for (size_t i = 0; i < n; i++)
+ p[i] = v;
+
+ return s;
+}
+
+void (* const interrupt_vector_table[])() __attribute__((section(".isr_vector"))) = {
+ (void (*)())_estack,
+ Reset_Handler,
+ NMI_Handler,
+ HardFault_Handler,
+ MemManage_Handler,
+ BusFault_Handler,
+ UsageFault_Handler,
+ RESERVED, /* Reserved */
+ RESERVED, /* Reserved */
+ RESERVED, /* Reserved */
+ RESERVED, /* Reserved */
+ SVC_Handler,
+ DebugMon_Handler,
+ RESERVED, /* Reserved */
+ PendSV_Handler,
+ SysTick_Handler,
+ /* STM32L1xx peripheral interrupts (position 0-56) */
+ WWDG_IRQHandler, /* 0 */
+ PVD_IRQHandler, /* 1 */
+ TAMPER_STAMP_IRQHandler, /* 2 */
+ RTC_WKUP_IRQHandler, /* 3 */
+ FLASH_IRQHandler, /* 4 */
+ RCC_IRQHandler, /* 5 */
+ EXTI0_IRQHandler, /* 6 */
+ EXTI1_IRQHandler, /* 7 */
+ EXTI2_IRQHandler, /* 8 */
+ EXTI3_IRQHandler, /* 9 */
+ EXTI4_IRQHandler, /* 10 */
+ DMA1_Channel1_IRQHandler, /* 11 */
+ DMA1_Channel2_IRQHandler, /* 12 */
+ DMA1_Channel3_IRQHandler, /* 13 */
+ DMA1_Channel4_IRQHandler, /* 14 */
+ DMA1_Channel5_IRQHandler, /* 15 */
+ DMA1_Channel6_IRQHandler, /* 16 */
+ DMA1_Channel7_IRQHandler, /* 17 */
+ ADC1_IRQHandler, /* 18 */
+ USB_HP_IRQHandler, /* 19 */
+ USB_LP_IRQHandler, /* 20 */
+ DAC_IRQHandler, /* 21 */
+ COMP_CA_IRQHandler, /* 22 */
+ EXTI9_5_IRQHandler, /* 23 */
+ LCD_IRQHandler, /* 24 */
+ TIM9_IRQHandler, /* 25 */
+ TIM10_IRQHandler, /* 26 */
+ TIM11_IRQHandler, /* 27 */
+ TIM2_IRQHandler, /* 28 */
+ TIM3_IRQHandler, /* 29 */
+ TIM4_IRQHandler, /* 30 */
+ I2C1_EV_IRQHandler, /* 31 */
+ I2C1_ER_IRQHandler, /* 32 */
+ I2C2_EV_IRQHandler, /* 33 */
+ I2C2_ER_IRQHandler, /* 34 */
+ SPI1_IRQHandler, /* 35 */
+ SPI2_IRQHandler, /* 36 */
+ USART1_IRQHandler, /* 37 */
+ USART2_IRQHandler, /* 38 */
+ USART3_IRQHandler, /* 39 */
+ EXTI15_10_IRQHandler, /* 40 */
+ RTC_Alarm_IRQHandler, /* 41 */
+ USB_FS_WKUP_IRQHandler, /* 42 */
+ TIM6_IRQHandler, /* 43 */
+ TIM7_IRQHandler, /* 44 */
+ SDIO_IRQHandler, /* 45 */
+ TIM5_IRQHandler, /* 46 */
+ SPI3_IRQHandler, /* 47 */
+ UART4_IRQHandler, /* 48 */
+ UART5_IRQHandler, /* 49 */
+ DMA2_Channel1_IRQHandler, /* 50 */
+ DMA2_Channel2_IRQHandler, /* 51 */
+ DMA2_Channel3_IRQHandler, /* 52 */
+ DMA2_Channel4_IRQHandler, /* 53 */
+ DMA2_Channel5_IRQHandler, /* 54 */
+ AES_IRQHandler, /* 55 */
+ COMP_ACQ_IRQHandler, /* 56 */
+};
+
+void __attribute__((naked)) Reset_Handler()
+{
+ __asm__("ldr r0, =_estack\n\t"
+ "mov sp, r0");
+
+ /* Copy data section from flash to RAM */
+ uint32_t data_section_size = _edata - _sdata;
+ memcpy(_sdata, _sidata, data_section_size * 4);
+
+ /* Zero out bss */
+ uint32_t bss_section_size = _ebss - _sbss;
+ memset(_sbss, 0, bss_section_size * 4);
+
+ /* Set Interrupt Vector Table Offset */
+ uint32_t *vtor = (uint32_t *)0xE000ED08;
+ *vtor = (uint32_t)interrupt_vector_table;
+
+ main();
+}
diff --git a/boards/stm32l152re_nucleo/linker.ld b/boards/stm32l152re_nucleo/linker.ld
new file mode 100644
index 0000000..e8a116b
--- /dev/null
+++ b/boards/stm32l152re_nucleo/linker.ld
@@ -0,0 +1,121 @@
+/* Entry Point */
+ENTRY(Reset_Handler)
+
+/* Highest address of the user mode stack */
+_estack = 0x20014000; /* end of 80 KB RAM */
+/* Generate a link error if the stack don't fit into RAM */
+_Min_Stack_Size = 0x400; /* required amount of stack */
+
+/* Specify the memory areas */
+MEMORY
+{
+FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
+RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 80K
+}
+
+/* Define output sections */
+SECTIONS
+{
+ /* The startup code goes first into FLASH */
+ .isr_vector :
+ {
+ . = ALIGN(4);
+ KEEP(*(.isr_vector)) /* Startup code */
+ . = ALIGN(4);
+ } >FLASH
+
+ /* The program code and other data goes into FLASH */
+ .text :
+ {
+ . = ALIGN(4);
+ *(.text) /* .text sections (code) */
+ *(.text*) /* .text* sections (code) */
+ *(.glue_7) /* glue arm to thumb code */
+ *(.glue_7t) /* glue thumb to arm code */
+ *(.eh_frame)
+
+ KEEP (*(.init))
+ KEEP (*(.fini))
+
+ . = ALIGN(4);
+ _etext = .; /* define a global symbols at end of code */
+ } >FLASH
+
+ /* Constant data goes into FLASH */
+ .rodata :
+ {
+ . = ALIGN(4);
+ *(.rodata) /* .rodata sections (constants, strings, etc.) */
+ *(.rodata*) /* .rodata* sections (constants, strings, etc.) */
+ . = ALIGN(4);
+ } >FLASH
+
+ .ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH
+ .ARM : {
+ __exidx_start = .;
+ *(.ARM.exidx*)
+ __exidx_end = .;
+ } >FLASH
+
+ .preinit_array :
+ {
+ PROVIDE_HIDDEN (__preinit_array_start = .);
+ KEEP (*(.preinit_array*))
+ PROVIDE_HIDDEN (__preinit_array_end = .);
+ } >FLASH
+ .init_array :
+ {
+ PROVIDE_HIDDEN (__init_array_start = .);
+ KEEP (*(SORT(.init_array.*)))
+ KEEP (*(.init_array*))
+ PROVIDE_HIDDEN (__init_array_end = .);
+ } >FLASH
+ .fini_array :
+ {
+ PROVIDE_HIDDEN (__fini_array_start = .);
+ KEEP (*(SORT(.fini_array.*)))
+ KEEP (*(.fini_array*))
+ PROVIDE_HIDDEN (__fini_array_end = .);
+ } >FLASH
+
+ /* used by the startup to initialize data */
+ _sidata = LOADADDR(.data);
+
+ /* Initialized data sections goes into RAM, load LMA copy after code */
+ .data :
+ {
+ . = ALIGN(4);
+ _sdata = .; /* create a global symbol at data start */
+ *(.data) /* .data sections */
+ *(.data*) /* .data* sections */
+
+ . = ALIGN(4);
+ _edata = .; /* define a global symbol at data end */
+ } >RAM AT> FLASH
+
+ /* Uninitialized data section */
+ . = ALIGN(4);
+ .bss :
+ {
+ /* This is used by the startup in order to initialize the .bss section */
+ _sbss = .; /* define a global symbol at bss start */
+ __bss_start__ = _sbss;
+ *(.bss)
+ *(.bss*)
+ *(COMMON)
+
+ . = ALIGN(4);
+ _ebss = .; /* define a global symbol at bss end */
+ __bss_end__ = _ebss;
+ } >RAM
+
+ /* User_heap_stack section, used to check that there is enough RAM left */
+ ._user_heap_stack :
+ {
+ . = ALIGN(8);
+ PROVIDE ( end = . );
+ PROVIDE ( _end = . );
+ . = . + _Min_Stack_Size;
+ . = ALIGN(8);
+ } >RAM
+}
diff --git a/boards/stm32wb55xx_nucleo/board.c b/boards/stm32wb55xx_nucleo/board.c
new file mode 100644
index 0000000..d4fa48e
--- /dev/null
+++ b/boards/stm32wb55xx_nucleo/board.c
@@ -0,0 +1,197 @@
+/* Example board configuration for the STM32WB55 Nucleo dev board */
+
+#include
+#include
+#include "board.h"
+#include
+
+/* SysTick timing */
+volatile uint32_t g_tick = 0;
+
+void SysTick_Handler(void)
+{
+ g_tick++;
+}
+
+uint32_t Board_GetTick(void)
+{
+ return g_tick;
+}
+
+whal_Timeout g_whalTimeout = {
+ .timeoutTicks = 1000, /* 1s timeout */
+ .GetTick = Board_GetTick,
+};
+
+/* Clock */
+whal_Clock g_whalClock = {
+ .regmap = { WHAL_STM32WB55_RCC_REGMAP },
+};
+
+static const whal_Stm32wb_Rcc_PeriphClk g_periphClks[] = {
+ {WHAL_STM32WB55_GPIOA_GATE},
+ {WHAL_STM32WB55_GPIOB_GATE},
+ {WHAL_STM32WB55_UART1_GATE},
+};
+#define PERIPH_CLK_COUNT (sizeof(g_periphClks) / sizeof(g_periphClks[0]))
+
+/* GPIO */
+whal_Gpio g_whalGpio = {
+ .regmap = { WHAL_STM32WB55_GPIO_REGMAP },
+
+ .cfg = &(whal_Stm32wb_Gpio_Cfg) {
+ .pinCfg = (whal_Stm32wb_Gpio_PinCfg[PIN_COUNT]) {
+ /* LED: PB5, output, push-pull, low speed, pull-up */
+ [LED_PIN] = WHAL_STM32WB_GPIO_PIN(
+ WHAL_STM32WB_GPIO_PORT_B, 5, WHAL_STM32WB_GPIO_MODE_OUT,
+ WHAL_STM32WB_GPIO_OUTTYPE_PUSHPULL, WHAL_STM32WB_GPIO_SPEED_LOW,
+ WHAL_STM32WB_GPIO_PULL_UP, 0),
+ /* UART1 TX: PB6, AF7 */
+ [UART_TX_PIN] = WHAL_STM32WB_GPIO_PIN(
+ WHAL_STM32WB_GPIO_PORT_B, 6, WHAL_STM32WB_GPIO_MODE_ALTFN,
+ WHAL_STM32WB_GPIO_OUTTYPE_PUSHPULL, WHAL_STM32WB_GPIO_SPEED_FAST,
+ WHAL_STM32WB_GPIO_PULL_UP, 7),
+ /* UART1 RX: PB7, AF7 */
+ [UART_RX_PIN] = WHAL_STM32WB_GPIO_PIN(
+ WHAL_STM32WB_GPIO_PORT_B, 7, WHAL_STM32WB_GPIO_MODE_ALTFN,
+ WHAL_STM32WB_GPIO_OUTTYPE_PUSHPULL, WHAL_STM32WB_GPIO_SPEED_FAST,
+ WHAL_STM32WB_GPIO_PULL_UP, 7),
+ },
+ .pinCount = PIN_COUNT,
+ },
+};
+
+/* UART */
+whal_Uart g_whalUart = {
+ .regmap = { WHAL_STM32WB55_UART1_REGMAP },
+
+ .cfg = &(whal_Stm32wb_Uart_Cfg) {
+ .timeout = &g_whalTimeout,
+ .brr = WHAL_STM32WB_UART_BRR(64000000, 115200),
+ },
+};
+
+/* Timer (SysTick) */
+whal_Timer g_whalTimer = {
+ .regmap = { WHAL_CORTEX_M4_SYSTICK_REGMAP },
+ .driver = WHAL_CORTEX_M4_SYSTICK_DRIVER,
+
+ .cfg = &(whal_SysTick_Cfg) {
+ .cyclesPerTick = 64000000 / 1000,
+ .clkSrc = WHAL_SYSTICK_CLKSRC_SYSCLK,
+ .tickInt = WHAL_SYSTICK_TICKINT_ENABLED,
+ },
+};
+
+/* Flash */
+whal_Flash g_whalFlash = {
+ .regmap = { WHAL_STM32WB55_FLASH_REGMAP },
+ .driver = WHAL_STM32WB55_FLASH_DRIVER,
+
+ .cfg = &(whal_Stm32wb_Flash_Cfg) {
+ .timeout = &g_whalTimeout,
+ .startAddr = 0x08000000,
+ .size = 0x80000, /* 512 KB (upper half reserved for BLE stack) */
+ },
+};
+
+void Board_WaitMs(size_t ms)
+{
+ uint32_t startCount = g_tick;
+ while (g_tick - startCount < ms);
+}
+
+whal_Error Board_Init(void)
+{
+ whal_Error err;
+
+ /* Flash latency must be set before SYSCLK rises above 16 MHz. */
+ err = whal_Stm32wb_Flash_Ext_SetLatency(&g_whalFlash, WHAL_STM32WB_FLASH_LATENCY_3);
+ if (err)
+ return err;
+
+ /* MSI 4 MHz -> PLL VCO 128 MHz -> PLLR /2 = 64 MHz -> SYSCLK */
+ err = whal_Stm32wb_Rcc_EnableMsi(&g_whalClock, WHAL_STM32WB_RCC_MSIRANGE_4MHz);
+ if (err)
+ return err;
+
+ err = whal_Stm32wb_Rcc_EnablePll(&g_whalClock, &(whal_Stm32wb_Rcc_PllCfg){
+ .clkSrc = WHAL_STM32WB_RCC_PLLCLK_SRC_MSI,
+ .n = 32, .m = 0, .r = 1, .q = 0, .p = 0,
+ });
+ if (err)
+ return err;
+
+ err = whal_Stm32wb_Rcc_SetSysClock(&g_whalClock, WHAL_STM32WB_RCC_SYSCLK_SRC_PLL);
+ if (err)
+ return err;
+
+ for (size_t i = 0; i < PERIPH_CLK_COUNT; i++) {
+ err = whal_Stm32wb_Rcc_EnablePeriphClk(&g_whalClock, &g_periphClks[i]);
+ if (err)
+ return err;
+ }
+
+ err = whal_Gpio_Init(&g_whalGpio);
+ if (err)
+ return err;
+
+ err = whal_Uart_Init(&g_whalUart);
+ if (err)
+ return err;
+
+ err = whal_Flash_Init(&g_whalFlash);
+ if (err)
+ return err;
+
+ err = whal_Timer_Init(&g_whalTimer);
+ if (err)
+ return err;
+
+ err = whal_Timer_Start(&g_whalTimer);
+ if (err)
+ return err;
+
+ return WHAL_SUCCESS;
+}
+
+whal_Error Board_Deinit(void)
+{
+ whal_Error err;
+
+ err = whal_Timer_Stop(&g_whalTimer);
+ if (err)
+ return err;
+
+ err = whal_Timer_Deinit(&g_whalTimer);
+ if (err)
+ return err;
+
+ err = whal_Flash_Deinit(&g_whalFlash);
+ if (err)
+ return err;
+
+ err = whal_Uart_Deinit(&g_whalUart);
+ if (err)
+ return err;
+
+ err = whal_Gpio_Deinit(&g_whalGpio);
+ if (err)
+ return err;
+
+ for (size_t i = PERIPH_CLK_COUNT; i-- > 0; ) {
+ err = whal_Stm32wb_Rcc_DisablePeriphClk(&g_whalClock, &g_periphClks[i]);
+ if (err)
+ return err;
+ }
+
+ err = whal_Stm32wb_Rcc_SetSysClock(&g_whalClock, WHAL_STM32WB_RCC_SYSCLK_SRC_MSI);
+ if (err)
+ return err;
+
+ err = whal_Stm32wb_Rcc_DisablePll(&g_whalClock);
+ if (err)
+ return err;
+
+ return WHAL_SUCCESS;
+}
diff --git a/boards/stm32wb55xx_nucleo/board.h b/boards/stm32wb55xx_nucleo/board.h
new file mode 100644
index 0000000..cafcbef
--- /dev/null
+++ b/boards/stm32wb55xx_nucleo/board.h
@@ -0,0 +1,30 @@
+#ifndef BOARD_H
+#define BOARD_H
+
+#include
+#include
+#include
+
+extern whal_Clock g_whalClock;
+extern whal_Gpio g_whalGpio;
+extern whal_Timer g_whalTimer;
+extern whal_Uart g_whalUart;
+extern whal_Flash g_whalFlash;
+
+extern whal_Timeout g_whalTimeout;
+extern volatile uint32_t g_tick;
+
+enum {
+ LED_PIN,
+ UART_TX_PIN,
+ UART_RX_PIN,
+ PIN_COUNT,
+};
+
+#define BOARD_LED_PIN 0
+
+whal_Error Board_Init(void);
+whal_Error Board_Deinit(void);
+void Board_WaitMs(size_t ms);
+
+#endif /* BOARD_H */
diff --git a/boards/stm32wb55xx_nucleo/board.mk b/boards/stm32wb55xx_nucleo/board.mk
new file mode 100644
index 0000000..34a84a6
--- /dev/null
+++ b/boards/stm32wb55xx_nucleo/board.mk
@@ -0,0 +1,38 @@
+_BOARD_DIR := $(patsubst %/,%,$(dir $(lastword $(MAKEFILE_LIST))))
+
+PLATFORM = stm32wb
+TESTS ?= clock gpio uart
+
+GCC = $(GCC_PATH)arm-none-eabi-gcc
+LD = $(GCC_PATH)arm-none-eabi-ld
+OBJCOPY = $(GCC_PATH)arm-none-eabi-objcopy
+
+CFLAGS += -Wall -Werror $(INCLUDE) -g3 \
+ -ffreestanding -nostdlib -mcpu=cortex-m4 \
+ -DPLATFORM_STM32WB -MMD -MP \
+ -DWHAL_CFG_STM32WB_GPIO_DIRECT_API_MAPPING \
+ -DWHAL_CFG_STM32WB_UART_DIRECT_API_MAPPING \
+ -DWHAL_CFG_STM32WB_RCC_DIRECT_API_MAPPING
+LDFLAGS = --omagic -static
+
+LINKER_SCRIPT ?= $(_BOARD_DIR)/linker.ld
+
+INCLUDE += -I$(_BOARD_DIR)
+
+BOARD_SOURCE = $(_BOARD_DIR)/ivt.c
+BOARD_SOURCE += $(_BOARD_DIR)/board.c
+BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/timer.c)
+BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/flash.c)
+BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/stm32wb_*.c)
+BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/systick.c)
+
+# Flash via openocd: make flash BOARD= IMAGE=
+OPENOCD ?= /opt/openocd/bin/openocd
+OPENOCD_INTERFACE ?= interface/stlink.cfg
+OPENOCD_TARGET ?= target/stm32wbx.cfg
+
+.PHONY: flash
+flash:
+ @test -n "$(IMAGE)" || { echo "IMAGE= required" >&2; exit 1; }
+ $(OPENOCD) -f $(OPENOCD_INTERFACE) -f $(OPENOCD_TARGET) \
+ -c "program $(IMAGE) verify reset exit"
diff --git a/boards/stm32wb55xx_nucleo/ivt.c b/boards/stm32wb55xx_nucleo/ivt.c
new file mode 100644
index 0000000..a8b8d89
--- /dev/null
+++ b/boards/stm32wb55xx_nucleo/ivt.c
@@ -0,0 +1,216 @@
+#include
+#include
+
+extern uint32_t _estack[];
+extern uint32_t _sidata[];
+extern uint32_t _sdata[];
+extern uint32_t _edata[];
+extern uint32_t _sbss[];
+extern uint32_t _ebss[];
+
+extern void main();
+
+void __attribute__((naked,noreturn)) Default_Handler()
+{
+ while(1);
+}
+
+void Reset_Handler() __attribute__((weak));
+void NMI_Handler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void HardFault_Handler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void MemManage_Handler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void BusFault_Handler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void UsageFault_Handler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void SVC_Handler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void DebugMon_Handler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void PendSV_Handler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void SysTick_Handler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void WWDG_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void PVD_PVM_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void TAMP_STAMP_LSECSS_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void RTC_WKUP_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void FLASH_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void RCC_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void EXTI0_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void EXTI1_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void EXTI2_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void EXTI3_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void EXTI4_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void DMA1_Channel1_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void DMA1_Channel2_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void DMA1_Channel3_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void DMA1_Channel4_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void DMA1_Channel5_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void DMA1_Channel6_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void DMA1_Channel7_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void ADC1_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void USB_HP_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void USB_LP_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void C2SEV_PWR_C2H_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void COMP_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void EXTI9_5_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void TIM1_BRK_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void TIM1_UP_TIM16_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void TIM1_TRG_COM_TIM17_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void TIM1_CC_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void TIM2_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void PKA_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void I2C1_EV_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void I2C1_ER_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void I2C3_EV_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void I2C3_ER_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void SPI1_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void SPI2_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void USART1_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void LPUART1_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void SAI1_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void TSC_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void EXTI15_10_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void RTC_Alarm_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void CRS_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void PWR_SOTF_BLEACT_802ACT_RFPHASE_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void IPCC_C1_RX_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void IPCC_C1_TX_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void HSEM_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void LPTIM1_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void LPTIM2_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void LCD_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void QUADSPI_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void AES1_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void AES2_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void RNG_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void FPU_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void DMA2_Channel1_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void DMA2_Channel2_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void DMA2_Channel3_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void DMA2_Channel4_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void DMA2_Channel5_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void DMA2_Channel6_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void DMA2_Channel7_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+void DMAMUX1_OVR_IRQHandler() __attribute__((weak, noreturn, alias("Default_Handler")));
+
+#define RESERVED Default_Handler
+
+void (* const interrupt_vector_table[])() __attribute__((section(".isr_vector"))) = {
+ (void (*)())_estack,
+ Reset_Handler,
+ NMI_Handler,
+ HardFault_Handler,
+ MemManage_Handler,
+ BusFault_Handler,
+ UsageFault_Handler,
+ RESERVED,
+ RESERVED,
+ RESERVED,
+ RESERVED,
+ SVC_Handler,
+ DebugMon_Handler,
+ RESERVED,
+ PendSV_Handler,
+ SysTick_Handler,
+ WWDG_IRQHandler,
+ PVD_PVM_IRQHandler,
+ TAMP_STAMP_LSECSS_IRQHandler,
+ RTC_WKUP_IRQHandler,
+ FLASH_IRQHandler,
+ RCC_IRQHandler,
+ EXTI0_IRQHandler,
+ EXTI1_IRQHandler,
+ EXTI2_IRQHandler,
+ EXTI3_IRQHandler,
+ EXTI4_IRQHandler,
+ DMA1_Channel1_IRQHandler,
+ DMA1_Channel2_IRQHandler,
+ DMA1_Channel3_IRQHandler,
+ DMA1_Channel4_IRQHandler,
+ DMA1_Channel5_IRQHandler,
+ DMA1_Channel6_IRQHandler,
+ DMA1_Channel7_IRQHandler,
+ ADC1_IRQHandler,
+ USB_HP_IRQHandler,
+ USB_LP_IRQHandler,
+ C2SEV_PWR_C2H_IRQHandler,
+ COMP_IRQHandler,
+ EXTI9_5_IRQHandler,
+ TIM1_BRK_IRQHandler,
+ TIM1_UP_TIM16_IRQHandler,
+ TIM1_TRG_COM_TIM17_IRQHandler,
+ TIM1_CC_IRQHandler,
+ TIM2_IRQHandler,
+ PKA_IRQHandler,
+ I2C1_EV_IRQHandler,
+ I2C1_ER_IRQHandler,
+ I2C3_EV_IRQHandler,
+ I2C3_ER_IRQHandler,
+ SPI1_IRQHandler,
+ SPI2_IRQHandler,
+ USART1_IRQHandler,
+ LPUART1_IRQHandler,
+ SAI1_IRQHandler,
+ TSC_IRQHandler,
+ EXTI15_10_IRQHandler,
+ RTC_Alarm_IRQHandler,
+ CRS_IRQHandler,
+ PWR_SOTF_BLEACT_802ACT_RFPHASE_IRQHandler,
+ IPCC_C1_RX_IRQHandler,
+ IPCC_C1_TX_IRQHandler,
+ HSEM_IRQHandler,
+ LPTIM1_IRQHandler,
+ LPTIM2_IRQHandler,
+ LCD_IRQHandler,
+ QUADSPI_IRQHandler,
+ AES1_IRQHandler,
+ AES2_IRQHandler,
+ RNG_IRQHandler,
+ FPU_IRQHandler,
+ DMA2_Channel1_IRQHandler,
+ DMA2_Channel2_IRQHandler,
+ DMA2_Channel3_IRQHandler,
+ DMA2_Channel4_IRQHandler,
+ DMA2_Channel5_IRQHandler,
+ DMA2_Channel6_IRQHandler,
+ DMA2_Channel7_IRQHandler,
+ DMAMUX1_OVR_IRQHandler,
+};
+
+void *memcpy(void *dest, const void *src, size_t n)
+{
+ unsigned char *d = dest;
+ const unsigned char *s = src;
+
+ for (size_t i = 0; i < n; i++)
+ d[i] = s[i];
+
+ return dest;
+}
+
+void *memset(void *s, int c, size_t n)
+{
+ unsigned char *p = s;
+ unsigned char v = (unsigned char)c;
+
+ for (size_t i = 0; i < n; i++)
+ p[i] = v;
+
+ return s;
+}
+
+void __attribute__((naked)) Reset_Handler()
+{
+ __asm__("ldr r0, =_estack\n\t"
+ "mov sp, r0");
+
+ // Copy data section from flash memory to ram
+ uint32_t data_section_size = _edata - _sdata;
+ memcpy(_sdata, _sidata, data_section_size*4);
+
+ // Zero out bss
+ uint32_t bss_section_size = _ebss - _sbss;
+ memset(_sbss, 0, bss_section_size*4);
+
+ // Set Interrupt Vector Table Offset
+ uint32_t *vtor = (uint32_t *)0xE000ED08;
+ *vtor = (uint32_t)interrupt_vector_table;
+
+ main();
+}
diff --git a/boards/stm32wb55xx_nucleo/linker.ld b/boards/stm32wb55xx_nucleo/linker.ld
new file mode 100644
index 0000000..5de0dd2
--- /dev/null
+++ b/boards/stm32wb55xx_nucleo/linker.ld
@@ -0,0 +1,120 @@
+ENTRY(Reset_Handler)
+
+_estack = 0x20030000; /* end of RAM */
+_Min_Stack_Size = 0x500; /* required amount of stack */
+
+/* Specify the memory areas */
+MEMORY
+{
+FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
+RAM1 (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00030000
+}
+
+/* Define output sections */
+SECTIONS
+{
+ /* The startup code goes first into FLASH */
+ .isr_vector :
+ {
+ . = ALIGN(4);
+ KEEP(*(.isr_vector)) /* Startup code */
+ . = ALIGN(4);
+ } >FLASH
+
+ /* The program code and other data goes into FLASH */
+ .text :
+ {
+ . = ALIGN(4);
+ *(.text) /* .text sections (code) */
+ *(.text*) /* .text* sections (code) */
+ *(.glue_7) /* glue arm to thumb code */
+ *(.glue_7t) /* glue thumb to arm code */
+ *(.eh_frame)
+
+ KEEP (*(.init))
+ KEEP (*(.fini))
+
+ . = ALIGN(4);
+ _etext = .; /* define a global symbols at end of code */
+ } >FLASH
+
+ /* Constant data goes into FLASH */
+ .rodata :
+ {
+ . = ALIGN(4);
+ *(.rodata) /* .rodata sections (constants, strings, etc.) */
+ *(.rodata*) /* .rodata* sections (constants, strings, etc.) */
+ . = ALIGN(4);
+ } >FLASH
+
+ .ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH
+ .ARM : {
+ __exidx_start = .;
+ *(.ARM.exidx*)
+ __exidx_end = .;
+ } >FLASH
+
+ .preinit_array :
+ {
+ PROVIDE_HIDDEN (__preinit_array_start = .);
+ KEEP (*(.preinit_array*))
+ PROVIDE_HIDDEN (__preinit_array_end = .);
+ } >FLASH
+ .init_array :
+ {
+ PROVIDE_HIDDEN (__init_array_start = .);
+ KEEP (*(SORT(.init_array.*)))
+ KEEP (*(.init_array*))
+ PROVIDE_HIDDEN (__init_array_end = .);
+ } >FLASH
+ .fini_array :
+ {
+ PROVIDE_HIDDEN (__fini_array_start = .);
+ KEEP (*(SORT(.fini_array.*)))
+ KEEP (*(.fini_array*))
+ PROVIDE_HIDDEN (__fini_array_end = .);
+ } >FLASH
+
+ /* used by the startup to initialize data */
+ _sidata = LOADADDR(.data);
+
+ /* Initialized data sections goes into RAM, load LMA copy after code */
+ .data :
+ {
+ . = ALIGN(4);
+ _sdata = .; /* create a global symbol at data start */
+ *(.data) /* .data sections */
+ *(.data*) /* .data* sections */
+
+ . = ALIGN(4);
+ _edata = .; /* define a global symbol at data end */
+ } >RAM1 AT> FLASH
+
+
+ /* Uninitialized data section */
+ . = ALIGN(4);
+ .bss :
+ {
+ /* This is used by the startup in order to initialize the .bss section */
+ _sbss = .; /* define a global symbol at bss start */
+ __bss_start__ = _sbss;
+ *(.bss)
+ *(.bss*)
+ *(COMMON)
+
+ . = ALIGN(4);
+ _ebss = .; /* define a global symbol at bss end */
+ __bss_end__ = _ebss;
+ } >RAM1
+
+ /* User_heap_stack section, used to check that there is enough RAM left */
+ ._user_heap_stack :
+ {
+ . = ALIGN(8);
+ PROVIDE ( end = . );
+ PROVIDE ( _end = . );
+ . = . + _Min_Stack_Size;
+ . = ALIGN(8);
+ } >RAM1
+
+}
diff --git a/docs/adding_a_board.md b/docs/adding_a_board.md
new file mode 100644
index 0000000..619aad7
--- /dev/null
+++ b/docs/adding_a_board.md
@@ -0,0 +1,182 @@
+# Adding a New Board
+
+This guide covers adding a new board configuration to wolfHAL.
+
+## Overview
+
+A board ties a platform to concrete hardware by defining peripheral instances,
+pin assignments, clock settings, and startup code. Each board lives in its own
+directory under `boards/` named `_/`.
+
+## Required Files
+
+### board.h
+
+Exports global peripheral instances and board-specific constants:
+
+```c
+#pragma once
+
+#include
+
+extern whal_Clock g_whalClock;
+extern whal_Gpio g_whalGpio;
+extern whal_Uart g_whalUart;
+extern whal_Timer g_whalTimer;
+extern whal_Flash g_whalFlash;
+extern whal_Timeout g_whalTimeout;
+
+#define BOARD_LED_PIN 0
+
+whal_Error Board_Init(void);
+whal_Error Board_Deinit(void);
+void Board_WaitMs(size_t ms);
+```
+
+### board.c
+
+Defines global device instances with their configurations and implements
+`Board_Init()` and `Board_Deinit()`.
+
+`Board_Init()` is responsible for initializing all peripherals in dependency
+order. For example, the clock controller must be initialized before peripherals
+that depend on it, and a power supply controller (if present) may need to come
+before the clock. It should return `WHAL_SUCCESS` on success or an error code
+on failure.
+
+`Board_Deinit()` tears down peripherals in reverse order.
+
+```c
+#include "board.h"
+#include
+
+static whal_Myplatform_Gpio_PinCfg pinCfg[] = { /* ... */ };
+
+static whal_Myplatform_Gpio_Cfg gpioConfig = {
+ .pinCfg = pinCfg,
+ .pinCount = sizeof(pinCfg) / sizeof(pinCfg[0]),
+};
+
+whal_Gpio g_whalGpio = {
+ .regmap = { WHAL_MYPLATFORM_GPIO_REGMAP },
+ .cfg = &gpioConfig,
+};
+
+static const whal_Myplatform_Clock_PeriphClk g_periphClks[] = {
+ {MY_PLATFORM_GPIO_CLOCK},
+ {MY_PLATFORM_UART_CLOCK},
+};
+#define PERIPH_CLK_COUNT \
+ (sizeof(g_periphClks) / sizeof(g_periphClks[0]))
+
+whal_Error Board_Init(void)
+{
+ whal_Error err;
+
+ /* Bring up the clock tree imperatively. The chip's clock driver
+ * exposes Enable*/Disable*/Set* helpers; boards call them in order.
+ * The exact sequence is chip-specific — see the chip's clock header. */
+ err = whal_Myplatform_Clock_EnableOsc(&g_whalClock,
+ &(whal_Myplatform_Clock_OscCfg){WHAL_MYPLATFORM_CLOCK_OSC0_CFG});
+ if (err)
+ return err;
+ err = whal_Myplatform_Clock_SetSysClock(&g_whalClock,
+ WHAL_MYPLATFORM_CLOCK_SYSCLK_SRC_OSC0);
+ if (err)
+ return err;
+
+ for (size_t i = 0; i < PERIPH_CLK_COUNT; i++) {
+ err = whal_Myplatform_Clock_EnablePeriphClk(&g_whalClock,
+ &g_periphClks[i]);
+ if (err)
+ return err;
+ }
+
+ err = whal_Gpio_Init(&g_whalGpio);
+ if (err)
+ return err;
+
+ err = whal_Uart_Init(&g_whalUart);
+ if (err)
+ return err;
+
+ err = whal_Timer_Init(&g_whalTimer);
+ if (err)
+ return err;
+
+ err = whal_Timer_Start(&g_whalTimer);
+ if (err)
+ return err;
+
+ return WHAL_SUCCESS;
+}
+```
+
+```c
+whal_Error Board_Deinit(void)
+{
+ whal_Error err;
+
+ whal_Timer_Stop(&g_whalTimer);
+ whal_Timer_Deinit(&g_whalTimer);
+ whal_Uart_Deinit(&g_whalUart);
+ whal_Gpio_Deinit(&g_whalGpio);
+
+ for (size_t i = PERIPH_CLK_COUNT; i-- > 0; ) {
+ err = whal_Myplatform_Clock_DisablePeriphClk(&g_whalClock,
+ &g_periphClks[i]);
+ if (err)
+ return err;
+ }
+
+ return WHAL_SUCCESS;
+}
+```
+
+### board.mk
+
+Defines the toolchain, compiler flags, and source file list:
+
+```makefile
+_BOARD_DIR := $(patsubst %/,%,$(dir $(lastword $(MAKEFILE_LIST))))
+
+PLATFORM = myplatform
+TESTS = gpio clock uart flash timer
+
+GCC = arm-none-eabi-gcc
+LD = arm-none-eabi-gcc
+OBJCOPY = arm-none-eabi-objcopy
+
+CFLAGS = -mcpu=cortex-m4 -mthumb -Os -Wall -MMD $(INCLUDE) -I$(_BOARD_DIR) \
+ -DWHAL_CFG_GPIO_API_MAPPING_MYPLATFORM \
+ -DWHAL_CFG_CLOCK_API_MAPPING_MYPLATFORM \
+ -DWHAL_CFG_UART_API_MAPPING_MYPLATFORM
+LDFLAGS = -mcpu=cortex-m4 -mthumb -nostdlib -lgcc
+
+LINKER_SCRIPT = $(_BOARD_DIR)/linker.ld
+
+INCLUDE += -I$(_BOARD_DIR)
+
+BOARD_SOURCE = $(_BOARD_DIR)/board.c
+BOARD_SOURCE += $(_BOARD_DIR)/ivt.c
+BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*.c)
+# Dispatch sources for mapped types are excluded; keep dispatch sources
+# for types whose drivers don't use direct API mapping (e.g., flash on
+# boards that mix on-chip flash with external SPI NOR).
+BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/timer.c)
+BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/flash.c)
+BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/myplatform_*.c)
+BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/systick.c)
+```
+
+### linker.ld
+
+Linker script defining the memory layout for your board's MCU. Must define
+FLASH and RAM regions, place `.isr_vector` at the start of FLASH, and set up
+`.text`, `.data`, and `.bss` sections.
+
+### ivt.c (ARM targets)
+
+Interrupt vector table and `Reset_Handler`. The reset handler copies `.data`
+from FLASH to RAM, zeroes `.bss`, and calls `main()`. This file is specific to
+ARM Cortex-M targets. Other architectures will need their own startup code.
diff --git a/docs/adding_a_test.md b/docs/adding_a_test.md
new file mode 100644
index 0000000..0bc8b2d
--- /dev/null
+++ b/docs/adding_a_test.md
@@ -0,0 +1,130 @@
+# Adding a Test
+
+This guide covers how to add hardware tests to the wolfHAL test suite.
+
+## Test Framework
+
+Tests use the macros defined in `tests/test.h`:
+
+- `WHAL_TEST_SUITE_START(name)` — print a suite header
+- `WHAL_TEST_SUITE_END()` — end a suite
+- `WHAL_TEST(fn)` — run a test function and report PASS/FAIL
+- `WHAL_ASSERT_EQ(a, b)` — assert equal, prints line number and values on
+ failure, then returns from the test function
+- `WHAL_ASSERT_NEQ(a, b)` — assert not equal
+- `WHAL_ASSERT_MEM_EQ(a, b, len)` — assert memory regions are equal, reports
+ the byte offset of the first mismatch
+- `WHAL_SKIP()` — skip the current test (marks it as SKIP instead of
+ PASS/FAIL and returns early)
+
+Test functions take no arguments and return void. On assertion failure the
+function returns early and the test is marked as failed.
+
+## Generic Tests
+
+Generic tests exercise the wolfHAL API using only the public interface. They
+rely on global peripheral instances and board constants from `board.h` (e.g.,
+`g_whalGpio`, `BOARD_LED_PIN`), so they run on any board that supports the
+device type without modification.
+
+Generic tests should verify the API contract — that functions return the
+expected error codes, that Set followed by Get produces the correct value, that
+data written can be read back, etc. They should not include platform-specific
+headers or make assumptions about register layouts.
+
+Create `tests//test_.c`:
+
+```c
+#include
+#include "board.h"
+#include "test.h"
+
+static void Test_Foo_BasicOperation(void)
+{
+ WHAL_ASSERT_EQ(whal_Foo_Init(&g_whalFoo), WHAL_SUCCESS);
+}
+
+void whal_Test_Foo(void)
+{
+ WHAL_TEST_SUITE_START("foo");
+ WHAL_TEST(Test_Foo_BasicOperation);
+ WHAL_TEST_SUITE_END();
+}
+```
+
+The entry point function must be named `whal_Test_` (capitalized device
+name). Individual test functions are static and named `Test__`.
+
+## Platform-Specific Tests
+
+Platform-specific tests verify that the driver correctly manipulates hardware
+registers. They include platform-specific headers and read registers directly
+using `whal_Reg_Get()` to confirm that Init configured the right fields, that
+Set wrote to the correct output register, etc.
+
+These tests are automatically detected and compiled when building for the
+matching platform. The Makefile looks for files named
+`test__.c` and compiles them when the board's `PLATFORM`
+variable matches the `` portion of the filename.
+
+Create `tests//test__.c`:
+
+```c
+#include
+#include /_.h>
+#include
+#include "board.h"
+#include "test.h"
+
+static void Test_Foo_SomeRegister(void)
+{
+ size_t val = 0;
+ whal_Reg_Get(g_whalFoo.regmap.base, REG_OFFSET, MASK, POS, &val);
+ WHAL_ASSERT_EQ(val, EXPECTED);
+}
+
+void whal_Test_Foo_Platform(void)
+{
+ WHAL_TEST(Test_Foo_SomeRegister);
+}
+```
+
+The entry point must be named `whal_Test__Platform`. No
+`WHAL_TEST_SUITE_START` is needed — platform tests run as a continuation of
+the generic suite.
+
+## Hooking into main.c
+
+Add your test to `tests/main.c`:
+
+1. Add a conditional declaration block:
+
+```c
+#ifdef WHAL_TEST_ENABLE_FOO
+void whal_Test_Foo(void);
+#ifdef WHAL_TEST_ENABLE_FOO_PLATFORM
+void whal_Test_Foo_Platform(void);
+#endif
+#endif
+```
+
+2. Add the corresponding call block in `main()`:
+
+```c
+#ifdef WHAL_TEST_ENABLE_FOO
+ whal_Test_Foo();
+#ifdef WHAL_TEST_ENABLE_FOO_PLATFORM
+ whal_Test_Foo_Platform();
+#endif
+#endif
+```
+
+## Enabling on Boards
+
+Add `foo` to the `TESTS` list in each board's `board.mk` that supports this
+device type. The Makefile automatically:
+
+- Defines `-DWHAL_TEST_ENABLE_FOO` so the test compiles in
+- Compiles `foo/test_foo.c`
+- Detects and compiles `foo/test__foo.c` if it exists (and defines
+ `-DWHAL_TEST_ENABLE_FOO_PLATFORM`)
diff --git a/docs/adding_an_example.md b/docs/adding_an_example.md
new file mode 100644
index 0000000..8dc7827
--- /dev/null
+++ b/docs/adding_an_example.md
@@ -0,0 +1,104 @@
+# Adding a New Example
+
+This guide covers adding a new example application to wolfHAL.
+
+## Overview
+
+Each example is a standalone application with its own `main.c` and `Makefile`.
+Board support is pulled in from the top-level `boards/` directory.
+
+Examples may be platform-specific if they exercise platform-specific features,
+but you should aim to make them as general as possible so they can run on
+multiple boards.
+
+## Step 1: Create the Directory
+
+Create `examples//` with two files:
+
+### main.c
+
+```c
+#include
+#include "board.h"
+
+void main(void)
+{
+ if (Board_Init() != WHAL_SUCCESS)
+ goto loop;
+
+ /* Application code using g_whal* peripherals from board.h */
+
+loop:
+ while (1);
+}
+```
+
+Key points:
+- Include `wolfHAL/wolfHAL.h` for the wolfHAL API
+- Include `board.h` for peripheral instances and board constants
+- Call `Board_Init()` before using any peripherals
+- Use the global peripheral instances (e.g., `g_whalGpio`, `g_whalUart`)
+- Use board constants (e.g., `BOARD_LED_PIN`) for portability across boards
+
+### Makefile
+
+```makefile
+WHAL_DIR = $(CURDIR)/../..
+
+BOARD ?= stm32wb55xx_nucleo
+BOARD_DIR = $(WHAL_DIR)/boards/$(BOARD)
+BUILD_DIR = build/$(BOARD)
+
+INCLUDE = -I$(WHAL_DIR)
+
+include $(BOARD_DIR)/board.mk
+
+SOURCE = main.c
+SOURCE += $(BOARD_SOURCE)
+
+OBJECTS = $(patsubst %.c,$(BUILD_DIR)/%.o,$(SOURCE))
+DEPENDS = $(OBJECTS:.o=.d)
+
+all: $(BUILD_DIR)/$(notdir $(CURDIR)).bin
+
+$(BUILD_DIR)/%.o: %.c Makefile
+ @mkdir -p $(dir $@)
+ $(GCC) $(CFLAGS) -c -o $@ $<
+
+.SECONDARY:
+$(BUILD_DIR)/%.elf: $(OBJECTS) $(LINKER_SCRIPT)
+ @mkdir -p $(dir $@)
+ $(LD) $(LDFLAGS) -T $(LINKER_SCRIPT) -o $@ $(OBJECTS)
+
+$(BUILD_DIR)/%.bin: $(BUILD_DIR)/%.elf
+ $(OBJCOPY) $^ -O binary $@
+
+.PHONY: clean
+clean:
+ rm -rf build
+
+-include $(DEPENDS)
+```
+
+This Makefile is the same for all examples. The board's `board.mk` provides
+the toolchain, flags, and platform sources.
+
+## Step 2: Build
+
+```sh
+cd examples/
+make BOARD=
+```
+
+The output binary is placed in `build//.bin`.
+
+## Adding Extra Source Files
+
+If your example needs additional source files, add them to `SOURCE` in the
+Makefile:
+
+```makefile
+SOURCE = main.c
+SOURCE += helper.c
+SOURCE += $(BOARD_SOURCE)
+```
diff --git a/docs/getting_started.md b/docs/getting_started.md
new file mode 100644
index 0000000..047c4ea
--- /dev/null
+++ b/docs/getting_started.md
@@ -0,0 +1,381 @@
+# Getting Started
+
+This guide walks through integrating wolfHAL into a bare-metal embedded project.
+
+## Project Layout
+
+A typical project using wolfHAL looks like this:
+
+```
+my_project/
+ wolfHAL/ wolfHAL repository (submodule, copy, etc.)
+ boards/
+ /
+ board.h Peripheral externs and board constants
+ board.c Device instances, configuration, and Board_Init
+ ivt.c Interrupt vector table and Reset_Handler
+ linker.ld Linker script for your MCU
+ board.mk Toolchain and source list
+ src/
+ main.c Application entry point
+ ... Additional application sources
+ Makefile
+```
+
+The key idea is that your project provides the board-level glue (device
+instances, pin assignments, clock config, startup code) and wolfHAL provides
+the driver implementations and API.
+
+## Adding wolfHAL to Your Project
+
+wolfHAL is a source-level library with no external dependencies beyond a C
+compiler and standard headers (`stdint.h`, `stddef.h`). To use it:
+
+1. Add the wolfHAL repository root to your include path (e.g., `-I/path/to/wolfHAL`)
+2. Compile the generic dispatch sources for the modules you need:
+
+```
+src/gpio/gpio.c
+src/uart/uart.c
+src/flash/flash.c
+...
+```
+
+3. Compile the platform-specific driver sources for your target:
+
+```
+src/clock/_rcc.c
+src/gpio/_gpio.c
+src/uart/_uart.c
+src/flash/_flash.c
+...
+```
+
+You only need to include the modules and platform drivers your project actually
+uses.
+
+## The Device Model
+
+wolfHAL has three driver categories — platform, peripheral, and board-level
+(see `docs/writing_a_driver.md`). Their device structs have slightly
+different shapes.
+
+### Platform drivers
+
+Platform drivers operate directly on SoC registers. The device struct has
+three fields:
+
+```c
+struct whal_Gpio {
+ const whal_Regmap regmap; /* base address and size */
+ const whal_GpioDriver *driver; /* vtable of function pointers */
+ const void *cfg; /* platform-specific configuration */
+};
+```
+
+- **regmap** — identifies the peripheral's memory-mapped register block
+- **driver** — points to the driver implementation (the vtable)
+- **cfg** — points to a driver-specific configuration struct that the
+ driver reads during Init
+
+Platform headers provide `_REGMAP` and `_DRIVER` macros for each peripheral,
+so you only need to fill in the `cfg`:
+
+```c
+#include
+
+whal_Gpio g_whalGpio = {
+ .regmap = { WHAL_STM32WB55_GPIO_REGMAP },
+ .driver = WHAL_STM32WB55_GPIO_DRIVER,
+ .cfg = &gpioConfig,
+};
+```
+
+When direct API mapping is active for a device type, the `.driver` field is
+omitted since calls go directly to the driver implementation.
+
+### Peripheral drivers
+
+Peripheral drivers talk to external chips over a bus (SPI, I2C, MDIO). The
+device struct shape mirrors platform drivers — same vtable + cfg pattern —
+but instead of a register block the device carries the bus handle and any
+chip-specific addressing inside its cfg:
+
+```c
+whal_Flash g_whalSpiFlash = {
+ .driver = WHAL_SPI_NOR_W25Q64_FLASH_DRIVER,
+ .cfg = &(whal_SpiNor_W25q64_Cfg) {
+ .spi = &g_whalSpi, /* underlying bus driver */
+ .csPin = SPI_FLASH_CS_PIN,
+ /* ...chip-specific fields... */
+ },
+};
+```
+
+The application calls the same generic API (`whal_Flash_Read(&g_whalSpiFlash, ...)`)
+whether the flash is on-chip (platform driver) or external SPI NOR (peripheral
+driver) — the device pointer determines which implementation runs.
+
+### Board-level drivers
+
+Board-level drivers (clock, power) only expose chip-specific helpers — no
+vtable, no generic `whal__*` API. The device struct is just a regmap:
+
+```c
+whal_Clock g_whalClock = {
+ .regmap = { WHAL_STM32WB55_RCC_REGMAP },
+};
+```
+
+There is no `.driver` or `.cfg` field to fill in. Boards bring up the clock
+tree imperatively in `Board_Init` by calling chip-specific helpers like
+`whal_Stm32wb_Rcc_EnableOsc()`, `EnablePll()`, `SetSysClock()`, etc., in the
+required order. Applications that need to trigger board-level behavior at
+runtime (e.g., enter low-power mode) call a board-provided wrapper such as
+`Board_Sleep()` rather than a generic `whal_X` function.
+
+## Configuring Devices
+
+Each platform driver defines its own configuration struct with the parameters
+it needs. For example, a GPIO driver takes a pin configuration table; the
+platform usually provides a `WHAL__GPIO_PIN(...)` macro to populate
+each entry compactly:
+
+```c
+whal_Gpio g_whalGpio = {
+ .regmap = { WHAL_STM32WB55_GPIO_REGMAP },
+ /* .driver: direct API mapping */
+
+ .cfg = &(whal_Stm32wb_Gpio_Cfg) {
+ .pinCfg = (whal_Stm32wb_Gpio_PinCfg[PIN_COUNT]) {
+ [LED_PIN] = WHAL_STM32WB_GPIO_PIN(
+ WHAL_STM32WB_GPIO_PORT_B, 5, WHAL_STM32WB_GPIO_MODE_OUT,
+ WHAL_STM32WB_GPIO_OUTTYPE_PUSHPULL, WHAL_STM32WB_GPIO_SPEED_LOW,
+ WHAL_STM32WB_GPIO_PULL_UP, 0),
+ },
+ .pinCount = PIN_COUNT,
+ },
+};
+```
+
+A UART driver typically takes a pre-computed baud rate register value and a
+timeout:
+
+```c
+whal_Uart g_whalUart = {
+ .regmap = { WHAL_STM32WB55_UART1_REGMAP },
+ /* .driver: direct API mapping */
+
+ .cfg = &(whal_Stm32wb_Uart_Cfg) {
+ .brr = WHAL_STM32WB_UART_BRR(64000000, 115200),
+ .timeout = &g_whalTimeout,
+ },
+};
+```
+
+The `.driver: direct API mapping` comment indicates this peripheral is using
+the optimization path described above — calls to `whal_Uart_*` link directly
+to the chip-specific implementation, no vtable dispatch.
+
+See the platform-specific headers in `wolfHAL//` for the full set
+of configuration options for each driver, and the example boards in `boards/`
+for full instantiations.
+
+## Initialization
+
+The board is responsible for initializing peripherals in dependency order.
+Drivers do not enable their own clocks or power supplies — the board must
+handle these prerequisites explicitly before calling a driver's Init.
+
+A typical initialization sequence:
+
+1. Do any pre-clock-controller initialization (e.g., flash wait states,
+ power supplies)
+2. Bring up the clock tree (oscillators, optional PLL, sysclk source)
+3. Enable peripheral clocks
+4. Initialize peripheral drivers
+5. Start timers
+
+The chip's clock driver exposes `Enable*`/`Disable*`/`Set*` helpers that
+the board calls in order. There is no generic `whal_Clock_Init` walker —
+clock-tree shape varies too much across vendors to abstract.
+
+```c
+static const whal_Myplatform_Clock_PeriphClk g_periphClks[] = {
+ {MY_PLATFORM_GPIOB_CLOCK},
+ {MY_PLATFORM_UART1_CLOCK},
+};
+#define PERIPH_CLK_COUNT \
+ (sizeof(g_periphClks) / sizeof(g_periphClks[0]))
+
+whal_Error Board_Init(void)
+{
+ whal_Error err;
+
+ /* Bring up clocks (chip-specific helpers, called in order) */
+ err = whal_Myplatform_Clock_EnableOsc(&g_whalClock,
+ &(whal_Myplatform_Clock_OscCfg){WHAL_MYPLATFORM_CLOCK_OSC0_CFG});
+ if (err)
+ return err;
+ err = whal_Myplatform_Clock_SetSysClock(&g_whalClock,
+ WHAL_MYPLATFORM_CLOCK_SYSCLK_SRC_OSC0);
+ if (err)
+ return err;
+
+ /* Enable peripheral clocks */
+ for (size_t i = 0; i < PERIPH_CLK_COUNT; i++) {
+ err = whal_Myplatform_Clock_EnablePeriphClk(&g_whalClock,
+ &g_periphClks[i]);
+ if (err)
+ return err;
+ }
+
+ /* Initialize peripherals */
+ err = whal_Gpio_Init(&g_whalGpio);
+ if (err)
+ return err;
+
+ err = whal_Uart_Init(&g_whalUart);
+ if (err)
+ return err;
+
+ err = whal_Timer_Init(&g_whalTimer);
+ if (err)
+ return err;
+
+ err = whal_Timer_Start(&g_whalTimer);
+ if (err)
+ return err;
+
+ return WHAL_SUCCESS;
+}
+```
+
+See the board examples in `boards/` for complete initialization sequences
+including platform-specific steps.
+
+## Using the API
+
+After initialization, use the wolfHAL API to interact with peripherals:
+
+```c
+#include
+#include "board.h"
+
+void main(void)
+{
+ if (Board_Init() != WHAL_SUCCESS)
+ while (1);
+
+ while (1) {
+ whal_Gpio_Set(&g_whalGpio, BOARD_LED_PIN, 1);
+ whal_Uart_Send(&g_whalUart, "Hello!\r\n", 8);
+ Board_WaitMs(1000);
+
+ whal_Gpio_Set(&g_whalGpio, BOARD_LED_PIN, 0);
+ Board_WaitMs(1000);
+ }
+}
+```
+
+All API functions return `whal_Error`. Check for `WHAL_SUCCESS` to confirm the
+operation completed. The error codes are:
+
+| Code | Meaning |
+|------|---------|
+| `WHAL_SUCCESS` | Operation completed successfully |
+| `WHAL_EINVAL` | Invalid argument (null device pointer, null data pointer) |
+| `WHAL_ENOTREADY` | Resource is busy or not yet available |
+| `WHAL_EHARDWARE` | Hardware error (e.g., RNG entropy failure) |
+| `WHAL_ETIMEOUT` | Operation timed out waiting for hardware |
+| `WHAL_ENOTSUP` | Operation or parameter not supported by this driver/hardware |
+
+## Optimizing for Size
+
+wolfHAL gives you several ways to reduce code size depending on how much
+control you want.
+
+### Direct API Mapping
+
+Each driver source (platform or peripheral) provides an `#ifdef` block that
+renames its driver functions to the top-level API names. When the
+corresponding `WHAL_CFG__API_MAPPING_` flag is defined, the
+driver file itself provides the definition of the top-level API — no
+wrapper, no vtable indirection, no runtime null-check.
+
+For example, `-DWHAL_CFG_UART_API_MAPPING_STM32WB` causes
+`src/uart/stm32wb_uart.c` to emit external symbols named `whal_Uart_Init`,
+`whal_Uart_Deinit`, `whal_Uart_Send`, and `whal_Uart_Recv`, each bound to
+the polled STM32WB UART driver body. Application code calling
+`whal_Uart_Send(&dev, buf, sz)` links directly to the driver.
+
+The same pattern works for peripheral drivers: enabling
+`WHAL_CFG_SDHC_SPI_BLOCK_DIRECT_API_MAPPING`, for instance, makes the
+external SD-card-over-SPI driver provide the top-level `whal_Block_*` API
+symbols directly.
+
+Direct API mapping is only safe when a single driver of that type is
+present in the build. Boards that combine multiple drivers of the same type
+(e.g., on-chip flash + external SPI NOR flash) cannot enable mapping for
+that type — they keep the vtable dispatch so both drivers can be linked
+simultaneously.
+
+**The dispatch source `src//.c` must not be compiled when the
+corresponding mapping flag is active.** Both the dispatch source and the
+mapped driver source provide the same top-level symbols, which would cause
+a multiple-definition link error. Exclude the dispatch source from the
+board's source list.
+
+Only one mapping flag may be active per device type per build.
+
+### Custom Vtables
+
+The platform drivers provide a pre-built vtable with all operations populated.
+If you only use a subset of a driver's functionality, you can define your own
+vtable that only includes the functions you need:
+
+```c
+static const whal_GpioDriver myGpioDriver = {
+ .Init = whal_Stm32wb_Gpio_Init,
+ .Deinit = whal_Stm32wb_Gpio_Deinit,
+ .Set = whal_Stm32wb_Gpio_Set,
+ /* Get left as NULL — calls return WHAL_ENOTSUP, saves pulling in that code */
+};
+
+whal_Gpio g_whalGpio = {
+ .regmap = { .base = 0x48000000, .size = 0x2000 },
+ .driver = &myGpioDriver,
+ .cfg = &gpioConfig,
+};
+```
+
+With link-time optimization (`-flto`) or garbage collection (`-ffunction-sections`
++ `-Wl,--gc-sections`), any driver functions not referenced through the vtable
+will be stripped from the final binary.
+
+### Calling Driver Functions Directly
+
+For maximum control, you can skip the vtable entirely and call the underlying
+platform driver functions directly:
+
+```c
+#include
+
+whal_Stm32wb_Gpio_Init(&g_whalGpio);
+whal_Stm32wb_Gpio_Set(&g_whalGpio, BOARD_LED_PIN, 1);
+```
+
+This eliminates the vtable indirection and lets the compiler inline or optimize
+the calls more aggressively.
+
+Register-level drivers do not call other drivers internally, so this works
+without any caveats. Peripheral drivers (e.g., SPI flash) still call their
+bus driver through the vtable.
+
+## Next Steps
+
+- See `boards/` for complete board configuration examples
+- See [Writing a Driver](writing_a_driver.md) for how to add support for a new
+ platform
+- See [Adding a Board](adding_a_board.md) for how to create a board
+ configuration for your hardware
diff --git a/docs/writing_a_driver.md b/docs/writing_a_driver.md
new file mode 100644
index 0000000..38b48dd
--- /dev/null
+++ b/docs/writing_a_driver.md
@@ -0,0 +1,1299 @@
+# Writing a Driver
+
+This guide covers how to implement wolfHAL drivers.
+
+## Driver Categories
+
+wolfHAL has three categories of drivers:
+
+**Platform drivers** operate directly on SoC hardware registers at fixed memory
+addresses. They are tied to a specific microcontroller family and use the
+`whal_Reg_*` helpers to read and write peripheral registers. Examples:
+`stm32wb_uart`, `stm32wb_gpio`, `stm32wb_dma`.
+
+**Peripheral drivers** communicate with external chips over a bus (SPI, I2C,
+MDIO). They call platform drivers (e.g., `whal_Spi_SendRecv`) to reach the
+hardware and are portable across any SoC that provides the required bus.
+Examples: `spi_nor_flash` (SPI flash), `sdhc_spi_block` (SD card over SPI),
+`bmi270_sensor` (IMU over I2C), `lan8742a_eth_phy` (Ethernet PHY over MDIO).
+
+**Board-level drivers** only expose a chip-specific interface — no vtable, no
+generic `whal__*` API. Their parameters and operations are chip-specific
+enough that abstracting them serves no portable consumer; every caller lives
+in `board.c`. Applications reach board-level behavior through
+`Board_()` wrapper functions in `board.c` when needed. A board-level
+driver can be either platform (e.g., the on-MCU clock controller) or
+peripheral (e.g., an external clock generator IC over I2C, or a PMIC). Examples:
+clock, power.
+
+Platform and peripheral drivers share the same vtable interface — the
+application calls `whal_Flash_Read()` whether the flash is on-chip (platform
+driver) or external SPI NOR (peripheral driver). Board-level drivers are the
+exception: there is no generic API for applications to call.
+
+## Common Pattern
+
+Platform and peripheral drivers follow the same structure:
+
+1. A **driver vtable** — a struct of function pointers that the driver must
+ populate.
+2. A **device struct** — contains a register map, a pointer to the driver
+ vtable, and a pointer to driver-specific configuration.
+3. A **generic dispatch layer** — validates inputs and calls through the vtable.
+
+To write a driver for a device type, you implement the functions defined in that
+type's vtable and expose them as a const driver instance.
+
+Board-level drivers follow a different pattern: no vtable, no generic
+`whal__*` API, no `DIRECT_API_MAPPING`. They expose chip-specific
+helper functions (e.g., `whal___EnableOsc`) that boards call
+directly. The "Clock" section below is the canonical example. The rest of
+this "Common Pattern" section applies to platform and peripheral drivers
+only.
+
+### File Layout
+
+For a platform driver implementing device type `foo` on platform `myplatform`:
+
+- `wolfHAL/foo/myplatform_foo.h` — configuration types and driver extern
+- `src/foo/myplatform_foo.c` — driver implementation and vtable definition
+
+For a peripheral driver implementing device type `foo` for chip `mychip`:
+
+- `wolfHAL/foo/mychip_foo.h` — configuration types and driver extern
+- `src/foo/mychip_foo.c` — driver implementation and vtable definition
+
+For a board-level driver implementing device type `foo` on platform `myplatform`:
+
+- `wolfHAL/foo/myplatform_foo.h` — types, enums, descriptor macros, and
+ chip-specific helper declarations (no driver extern, no `_DRIVER` macro)
+- `src/foo/myplatform_foo.c` — chip-specific helper implementations (no
+ vtable, no `DIRECT_API_MAPPING` block)
+
+### Driver Vtable
+
+Every vtable includes `Init` and `Deinit`. The remaining functions are specific
+to the device type. All functions receive a pointer to the device instance as
+their first argument and return `whal_Error`.
+
+```c
+const whal_FooDriver whal_Myplatform_Foo_Driver = {
+ .Init = whal_Myplatform_Foo_Init,
+ .Deinit = whal_Myplatform_Foo_Deinit,
+ /* device-specific operations */
+};
+```
+
+### Direct API Mapping
+
+Every driver should include a rename-at-definition block that lets the build
+system map chip-specific function names to the top-level API. Place this
+`#ifdef` block after your register defines and before the first function
+definition:
+
+```c
+#ifdef WHAL_CFG_MYPLATFORM_FOO_DIRECT_API_MAPPING
+#define whal_Myplatform_Foo_Init whal_Foo_Init
+#define whal_Myplatform_Foo_Deinit whal_Foo_Deinit
+/* ... one #define per mapped function ... */
+#endif
+```
+
+When the flag is defined, the preprocessor renames each function to its
+generic API name at the definition site. The driver source uses chip-specific
+names everywhere — the macros handle the rest. The flag name is
+`WHAL_CFG__DIRECT_API_MAPPING` where `` is the driver's full
+prefix in upper case, e.g., `WHAL_CFG_STM32WB_UART_DIRECT_API_MAPPING` for a
+platform driver or `WHAL_CFG_BMI270_SENSOR_DIRECT_API_MAPPING` for a
+peripheral driver.
+
+Wrap the vtable in `#ifndef` since it is unused when mapping is active:
+
+```c
+#ifndef WHAL_CFG_MYPLATFORM_FOO_DIRECT_API_MAPPING
+const whal_FooDriver whal_Myplatform_Foo_Driver = {
+ .Init = whal_Myplatform_Foo_Init,
+ .Deinit = whal_Myplatform_Foo_Deinit,
+};
+#endif
+```
+
+In the chip header, wrap the mapped function prototypes and extern vtable
+declaration in the same `#ifndef` guard. Types and configuration structs
+stay unconditional. Extension functions (`Ext_*`) that have no generic
+equivalent are never mapped and stay unconditional.
+
+### Configuration
+
+The device struct's `cfg` field points to your platform-specific configuration.
+Cast it in your driver functions:
+
+```c
+static whal_Error whal_Myplatform_Foo_Init(whal_Foo *fooDev)
+{
+ whal_Myplatform_Foo_Cfg *cfg = (whal_Myplatform_Foo_Cfg *)fooDev->cfg;
+ /* ... */
+}
+```
+
+### No Cross-Driver Calls
+
+Platform drivers should only touch their own registers. The board handles all
+cross-peripheral setup — clock enables, power-rail sequencing, flash wait
+states — before calling Init.
+
+There are two exceptions:
+
+- **Peripheral drivers** call their underlying bus driver (SPI, I2C, MDIO) to
+ communicate with the external chip.
+- **DMA-backed platform drivers** (e.g., `stm32wb_uart_dma`) call the DMA
+ driver to set up and start transfers. The DMA device is passed through the
+ driver's configuration struct.
+
+### Register Access
+
+wolfHAL provides register access helpers in `wolfHAL/regmap.h`:
+
+- `whal_Reg_Write()` — write a full register value
+- `whal_Reg_Read()` — read a full register value
+- `whal_Reg_Update()` — read-modify-write a specific field within a register
+- `whal_Reg_Get()` — read and extract a masked field from a register
+
+Use `Write` and `Read` for whole-register access. Use `Update` and `Get` when
+you need to modify or read individual fields within a register without
+disturbing other bits.
+
+### Timeouts
+
+Most drivers poll hardware status registers in busy-wait loops (e.g., waiting
+for a DMA transfer to complete, a flash erase to finish, or an AES computation
+to produce a result). wolfHAL provides an optional timeout mechanism to bound
+these waits and prevent infinite hangs if hardware misbehaves.
+
+#### Timeout Struct
+
+The board creates a `whal_Timeout` instance with a tick source and a timeout
+duration. Drivers receive a pointer to it through their configuration struct:
+
+```c
+typedef struct {
+ uint32_t timeoutTicks; /* max ticks before timeout */
+ uint32_t startTick; /* snapshot, set by START */
+ uint32_t (*GetTick)(void); /* board-provided tick source */
+} whal_Timeout;
+```
+
+The tick units are determined by the board's `GetTick` implementation. A 1 kHz
+SysTick gives millisecond ticks; a 1 MHz timer gives microsecond ticks. Drivers
+do not need to know the tick rate.
+
+#### whal_Reg_ReadPoll
+
+For the common case of polling a register bit, use `whal_Reg_ReadPoll` from
+`wolfHAL/regmap.h`:
+
+```c
+whal_Error whal_Reg_ReadPoll(size_t base, size_t offset,
+ size_t mask, size_t value,
+ whal_Timeout *timeout);
+```
+
+This polls until `(reg & mask) == value` or the timeout expires. Pass the mask
+as the value to wait for bits to be set, or `0` to wait for bits to be clear:
+
+```c
+/* Wait for TXE flag to be set */
+err = whal_Reg_ReadPoll(base, SPI_SR_REG, SPI_SR_TXE_Msk,
+ SPI_SR_TXE_Msk, cfg->timeout);
+
+/* Wait for BSY flag to be clear */
+err = whal_Reg_ReadPoll(base, SPI_SR_REG, SPI_SR_BSY_Msk,
+ 0, cfg->timeout);
+```
+
+A NULL timeout pointer means unbounded wait (poll forever).
+
+#### Driver-Specific Helpers
+
+When a driver has many polling sites that also need post-poll cleanup (e.g.,
+clearing a flag), wrap the pattern in a local helper to avoid code duplication:
+
+```c
+static whal_Error WaitForCCF(size_t base, whal_Timeout *timeout)
+{
+ whal_Error err;
+ err = whal_Reg_ReadPoll(base, AES_SR_REG, AES_SR_CCF_Msk,
+ AES_SR_CCF_Msk, timeout);
+ if (err)
+ return err;
+ whal_Reg_Update(base, AES_CR_REG, AES_CR_CCFC_Msk, AES_CR_CCFC_Msk);
+ return WHAL_SUCCESS;
+}
+```
+
+This keeps code size small — one function body shared across all call sites
+instead of inlined polling loops at each location.
+
+#### Cleanup on Timeout
+
+When a timeout occurs during an operation that has enabled a hardware mode
+(e.g., flash programming mode, AES enable), the driver must still clean up
+before returning. Use a `goto cleanup` pattern:
+
+```c
+whal_Error err = WHAL_SUCCESS;
+
+whal_Reg_Update(base, CR_REG, PG_Msk, 1); /* enable programming mode */
+
+for (...) {
+ err = whal_Reg_ReadPoll(base, SR_REG, BSY_Msk, 0, cfg->timeout);
+ if (err)
+ goto cleanup;
+}
+
+cleanup:
+ whal_Reg_Update(base, CR_REG, PG_Msk, 0); /* always disable */
+ return err;
+```
+
+Never return directly from inside a polling loop if the peripheral is in a
+mode that requires cleanup.
+
+#### Compile-Time Disable
+
+Define `WHAL_CFG_NO_TIMEOUT` to remove all timeout logic from the binary.
+When defined, `WHAL_TIMEOUT_START` becomes a no-op and `WHAL_TIMEOUT_EXPIRED`
+always evaluates to `0`, so polling loops run until the hardware condition is
+met with no overhead.
+
+#### Adding Timeout to a Config Struct
+
+Driver config structs that use polling should include an optional timeout
+pointer:
+
+```c
+typedef struct {
+ /* ... other config fields ... */
+ whal_Timeout *timeout;
+} whal_Myplatform_Foo_Cfg;
+```
+
+The timeout is optional — if the board does not set it (NULL), all waits are
+unbounded.
+
+### Reusing a Driver Across Platforms
+
+Many MCU families share identical peripheral IP blocks. For example, the
+STM32WB and STM32H5 have register-compatible GPIO and UART peripherals.
+Rather than duplicating driver code, create thin alias files for the new
+platform that re-export the existing driver under platform-specific names.
+
+#### Header
+
+The alias header `typedef`s the config structs and `#define`s the driver
+instance, functions, and any enum constants. The driver and function aliases
+are wrapped in `#ifndef` so they are omitted when direct API mapping is active
+for the alias platform:
+
+```c
+#ifndef WHAL_STM32H5_GPIO_H
+#define WHAL_STM32H5_GPIO_H
+
+#include
+
+typedef whal_Stm32wb_Gpio_Cfg whal_Stm32h5_Gpio_Cfg;
+typedef whal_Stm32wb_Gpio_PinCfg whal_Stm32h5_Gpio_PinCfg;
+
+#ifndef WHAL_CFG_GPIO_API_MAPPING_STM32H5
+#define whal_Stm32h5_Gpio_Driver whal_Stm32wb_Gpio_Driver
+#define whal_Stm32h5_Gpio_Init whal_Stm32wb_Gpio_Init
+#define whal_Stm32h5_Gpio_Deinit whal_Stm32wb_Gpio_Deinit
+#define whal_Stm32h5_Gpio_Get whal_Stm32wb_Gpio_Get
+#define whal_Stm32h5_Gpio_Set whal_Stm32wb_Gpio_Set
+#endif
+
+/* Re-export enum constants under the new platform name */
+#define WHAL_STM32H5_GPIO_MODE_OUT WHAL_STM32WB_GPIO_MODE_OUT
+/* ... */
+
+#endif
+```
+
+Use `typedef` for types (gives proper type-checking and debugger visibility)
+and `#define` for the driver instance and functions (which are values, not
+types).
+
+#### Source
+
+The source file includes the original implementation directly:
+
+```c
+#include "stm32wb_gpio.c"
+```
+
+This works because `#include` is textual insertion — the compiler does not
+distinguish `.h` from `.c`. The new platform's `board.mk` compiles this
+file and does **not** compile the original. The original platform's
+`board.mk` still compiles its own file directly. Both must never appear in
+the same build.
+
+#### When to alias vs. write a new driver
+
+Alias when the register layout is identical — same offsets, same bit positions,
+same behavior. If even one register differs (different bit position, extra
+field, different reset value that affects behavior), write a new driver. A
+partial alias that papers over register differences with workarounds is worse
+than a clean separate implementation.
+
+### Platform Device Macros
+
+Add regmap and driver macros to your platform header
+(`wolfHAL/platform//.h`) so that board configs can instantiate
+devices without knowing the register addresses or driver symbols:
+
+```c
+#define WHAL_MYPLATFORM_FOO_REGMAP \
+ .base = 0x40000000, \
+ .size = 0x400
+#define WHAL_MYPLATFORM_FOO_DRIVER &whal_Myplatform_Foo_Driver
+```
+
+The board uses these in device struct initializers:
+
+```c
+whal_Foo g_whalFoo = {
+ .regmap = { WHAL_MYPLATFORM_FOO_REGMAP },
+ .driver = WHAL_MYPLATFORM_FOO_DRIVER,
+ .cfg = &fooCfg,
+};
+```
+
+When direct API mapping is active for a device type, the board omits the
+`.driver` field since the vtable is unused. It's a good idea to note that
+you are doing so with the following comment.
+
+```c
+whal_Foo g_whalFoo = {
+ .regmap = { WHAL_MYPLATFORM_FOO_REGMAP },
+ /* .driver: direct API mapping */
+ .cfg = &fooCfg,
+};
+```
+
+---
+
+## Clock
+
+Header: `wolfHAL/clock/clock.h`
+
+Clock is a **board-level driver** (see Driver Categories). The generic
+`clock.h` declares only the typed handle `whal_Clock { regmap }` — no
+`whal_Clock_Init`/`Deinit`/`Enable`/`Disable` API, no `whal_ClockDriver`
+vtable. Each chip clock driver exposes imperative chip-specific helpers
+that boards call directly from `Board_Init` in the right order.
+
+### API contract
+
+The chip clock driver header may declare ONLY functions whose names match
+one of these prefix patterns:
+
+- `whal___Enable(...)` — turn a clock node on (and wait
+ ready, if applicable)
+- `whal___Disable(...)` — turn a clock node off
+- `whal___Set(...)` — change a clock node's
+ selection/value (e.g. `SetSysClock`, peripheral-mux selects, divider/range
+ selects)
+
+Where `` is the chip's clock-controller name (e.g. `Rcc` on STM32,
+`Clock` on PIC32CZ). NOT allowed in the public header: `Init`, `Deinit`,
+`Configure`, `BringUp`, `GetFreq`, or any other operation. Internal helpers go
+in the `.c` file as `static`.
+
+Configuration and enabling are bundled into a single `Enable*` call where
+the order is fixed (e.g. `EnablePll` writes the dividers/source then turns
+the PLL on and waits for ready). Boards do not call separate `Configure`
+operations.
+
+The chip header may declare types, enums, and `_CFG` field-initializer
+macros freely — those are not constrained.
+
+### Typical helpers
+
+A chip with a fairly common clock tree exposes:
+
+```c
+whal_Error whal___EnableOsc(whal_Clock *,
+ const whal___OscCfg *);
+whal_Error whal___DisableOsc(whal_Clock *,
+ const whal___OscCfg *);
+whal_Error whal___EnablePll(whal_Clock *,
+ const whal___PllCfg *);
+whal_Error whal___DisablePll(whal_Clock *);
+whal_Error whal___SetSysClock(whal_Clock *,
+ whal___SysClockSrc);
+whal_Error whal___EnablePeriphClk(whal_Clock *,
+ const whal___PeriphClk *);
+whal_Error whal___DisablePeriphClk(whal_Clock *,
+ const whal___PeriphClk *);
+```
+
+Adapt the set to what the chip actually has. A chip without a PLL drops
+`EnablePll`/`DisablePll`. A chip with extra muxes adds `Set*` helpers. A
+chip with a different topology (e.g., generic-clock generators routing to
+peripheral channels) adds operations like `EnableGclkGen` and keeps to the same
+naming convention.
+
+### Reference implementation
+
+`wolfHAL/clock/stm32wb_rcc.h` and `src/clock/stm32wb_rcc.c` are the
+canonical reference. New chip clock drivers should be modeled on its
+shape. `wolfHAL/clock/pic32cz_clock.h` shows how the same convention
+covers a fundamentally different topology (oscillators + GCLK generators
++ peripheral channels).
+
+### Board responsibilities
+
+The board calls helpers in the right order in `Board_Init` — typically
+oscillator(s) on, PLL configure-and-enable, sysclk source switch,
+peripheral clock enables. The board also handles flash wait states and
+voltage scaling around the sysclk transition. There is no walker; the
+ordering is explicit at the call site.
+
+---
+
+## GPIO
+
+Header: `wolfHAL/gpio/gpio.h`
+
+The GPIO driver configures and controls general-purpose I/O pins. The
+configuration describes a table of pins, and the API operates on pins by their
+index in that table (not raw hardware pin/port numbers).
+
+### Init
+
+Configure all pins described in the device's configuration. For each pin this
+typically involves:
+
+- Setting the pin mode (input, output, alternate function, analog)
+- Configuring output type (push-pull or open-drain), speed, and pull
+ resistors as applicable
+- Setting the alternate function mux if the pin is in alternate function mode
+ (e.g., for UART TX/RX or SPI signals)
+
+The board must enable GPIO port clocks before calling Init.
+
+If any pin configuration fails, Init should stop and return an error.
+
+### Deinit
+
+Reset GPIO pin configurations as needed. The board is responsible for
+disabling GPIO port clocks after Deinit.
+
+### Get
+
+Read the current state of a pin. The `pin` parameter is an index into the
+configured pin table, not a raw hardware pin number. The driver should look up
+the actual port and pin from the configuration.
+
+For output pins, read the output data register (the value being driven). For
+input pins, read the input data register (the value being sampled from the
+pad). Store the result (0 or 1) in the output pointer.
+
+### Set
+
+Drive a pin to the given value (0 or 1). The `pin` parameter is an index into
+the configured pin table. The driver writes to the output data register for the
+corresponding port and pin.
+
+---
+
+## UART
+
+Header: `wolfHAL/uart/uart.h`
+
+The UART driver provides basic serial transmit and receive. All operations are
+blocking — Send does not return until all bytes have been transmitted, and Recv
+does not return until all requested bytes have been received.
+
+### Init
+
+Configure and enable the UART peripheral:
+
+- Write the baud rate register value from the configuration. The board
+ pre-computes this value from the clock frequency and desired baud rate
+ (e.g., `BRR = clockFreq / baud`)
+- Configure word length, stop bits, and parity as needed
+- Enable the transmitter and receiver
+- Enable the UART peripheral
+
+The board must enable the peripheral clock before calling Init.
+
+On platforms with synchronization requirements (e.g., Microchip SERCOM), the
+driver must poll synchronization busy flags after writing to certain registers
+before proceeding.
+
+### Deinit
+
+Disable the UART peripheral:
+
+- Disable the transmitter and receiver
+- Clear the baud rate register
+
+### Send
+
+Transmit `dataSz` bytes from the provided buffer. For each byte:
+
+1. Poll the transmit-ready flag (TX empty / data register empty) until the
+ hardware is ready to accept a byte
+2. Write the byte to the transmit data register
+
+After sending all bytes, poll the transmission-complete flag to ensure the last
+byte has fully shifted out before returning.
+
+### Recv
+
+Receive `dataSz` bytes into the provided buffer. For each byte:
+
+1. Poll the receive-ready flag (RX not empty / receive complete) until a byte
+ is available
+2. Read the byte from the receive data register and store it in the buffer
+
+### SendAsync
+
+Start a non-blocking transmit. Returns immediately after initiating the
+transfer. The buffer must remain valid until the transfer completes. The
+driver signals completion through a platform-specific mechanism.
+
+Drivers that do not support async should set SendAsync to NULL in the vtable.
+The dispatch layer returns WHAL_ENOTSUP when the caller tries to use any
+NULL vtable entry (or when the driver pointer itself is NULL). When direct
+API mapping is active, polled drivers provide stub implementations that
+return WHAL_ENOTSUP directly.
+
+### RecvAsync
+
+Start a non-blocking receive. Returns immediately after initiating the
+transfer. The buffer must remain valid until the transfer completes.
+
+The async variants are optional — a driver vtable only needs to populate
+them if the platform supports non-blocking transfers. Polled-only drivers
+leave these NULL (the dispatch layer returns WHAL_ENOTSUP) or provide
+stubs returning WHAL_ENOTSUP (direct API mapping).
+
+---
+
+## IRQ
+
+Header: `wolfHAL/irq/irq.h`
+
+The IRQ driver controls an interrupt controller. It provides a
+platform-independent way to enable and disable individual interrupt lines.
+
+### Init
+
+Initialize the interrupt controller.
+
+### Deinit
+
+Shut down the interrupt controller.
+
+### Enable
+
+Enable an interrupt line. The `irqCfg` parameter is platform-specific and
+can contain settings such as priority. Pass NULL for defaults.
+
+### Disable
+
+Disable an interrupt line.
+
+---
+
+## SPI
+
+Header: `wolfHAL/spi/spi.h`
+
+The SPI driver provides serial peripheral interface communication. A
+communication session is bracketed by `StartCom` / `EndCom`, which configure
+the peripheral for a specific mode and speed. All transfers within a session
+use the same settings, allowing the bus to be shared between devices with
+different configurations by starting a new session for each device.
+
+### Init
+
+Perform one-time SPI peripheral configuration that remains fixed for the
+lifetime of the device. Do not configure mode, baud rate, or data size
+here — these are applied per-session via `StartCom`.
+
+The board must enable the peripheral clock before calling Init. The
+configuration struct should include the peripheral clock frequency so the
+driver can compute baud rate prescalers.
+
+### Deinit
+
+Disable the SPI peripheral.
+
+### StartCom
+
+Begin a communication session. Configures the peripheral from the
+platform-independent `whal_Spi_ComCfg` struct:
+
+- `freq` — bus frequency in Hz
+- `mode` — SPI mode (CPOL/CPHA)
+- `wordSz` — word size in bits (e.g. 8)
+- `dataLines` — number of data lines (1 for standard SPI)
+
+The driver should disable the peripheral, apply the new settings (mode, baud
+rate prescaler, data size, FIFO threshold), and re-enable it. Return
+`WHAL_EINVAL` if a requested setting is not supported by the hardware.
+
+### EndCom
+
+End the current communication session by disabling the peripheral.
+
+### SendRecv
+
+Perform a full-duplex SPI transfer. This is the only transfer function — there
+are no separate Send or Recv operations.
+
+The driver clocks `max(txLen, rxLen)` bytes:
+
+- When `tx` is NULL or exhausted, send `0xFF` for remaining clocks
+- When `rx` is NULL or exhausted, discard received bytes
+- Every TX byte produces an RX byte; always drain the RX FIFO to prevent
+ overflow
+
+After the loop, wait for the bus to go idle before returning.
+
+---
+
+## I2C
+
+Header: `wolfHAL/i2c/i2c.h`
+
+The I2C driver provides inter-integrated circuit bus communication. A
+communication session is bracketed by `StartCom` / `EndCom`, which configure
+the peripheral for a specific target address and bus frequency. The message-based
+`Transfer` API gives full control over bus conditions (START, STOP, direction).
+
+Convenience helpers `whal_I2c_WriteReg` and `whal_I2c_ReadReg` are provided
+as inline functions for the common register read/write patterns.
+
+### Init
+
+Perform one-time I2C peripheral configuration (noise filters, enable). Do not
+configure timing or address here — these are applied per-session via `StartCom`.
+
+The board must enable the peripheral clock before calling Init.
+
+### Deinit
+
+Disable the I2C peripheral.
+
+### StartCom
+
+Begin a communication session. Configures the peripheral from the
+platform-independent `whal_I2c_ComCfg` struct:
+
+- `freq` — bus frequency in Hz (100000, 400000, 1000000)
+- `addr` — target device address (7-bit or 10-bit)
+- `addrSz` — address size in bits (7 or 10)
+
+The driver should compute timing parameters from `freq` and the peripheral
+clock, then write the target address into the appropriate hardware register.
+
+### EndCom
+
+End the current communication session by clearing the target address.
+
+### Transfer
+
+Execute a sequence of I2C messages. Each message has a data buffer, size, and
+flags that control bus conditions:
+
+- `WHAL_I2C_MSG_WRITE` (0) — master write
+- `WHAL_I2C_MSG_READ` — master read
+- `WHAL_I2C_MSG_START` — generate START (or repeated START)
+- `WHAL_I2C_MSG_STOP` — generate STOP after this message
+
+The driver processes messages sequentially. When the current message has no
+STOP and the next message has no START, use hardware RELOAD to continue the
+same transfer without re-addressing (for multi-part writes). When the next
+message has START, use TC (transfer complete) so the hardware can generate a
+repeated START for direction changes.
+
+For messages larger than 255 bytes, the driver must split into chunks using
+the hardware RELOAD mechanism internally.
+
+---
+
+## Sensor
+
+Header: `wolfHAL/sensor/sensor.h`
+
+The sensor driver provides a bus-agnostic API for reading sensor data. Each
+sensor driver implements the vtable and uses the appropriate bus (I2C, SPI,
+etc.) internally. The `Read` function fills a driver-defined data struct
+passed as a void pointer.
+
+### Init
+
+Initialize the sensor hardware. This may involve loading configuration data,
+verifying device identity, and configuring operating parameters. The driver
+should call `StartCom` / `EndCom` on its bus device to bracket I2C/SPI access.
+
+### Deinit
+
+Shut down the sensor (e.g., soft reset). Should bracket bus access with
+`StartCom` / `EndCom`.
+
+### Read
+
+Read sensor data into a driver-defined struct. The driver fetches a new sample
+from the hardware and fills the struct. The struct type is defined by each
+sensor driver (e.g., `whal_Bmi270_Data` with `accelX/Y/Z`, `gyroX/Y/Z`).
+
+Each `Read` call should bracket its bus access with `StartCom` / `EndCom` so
+the bus is released between reads and other devices can use it.
+
+---
+
+## Flash
+
+Header: `wolfHAL/flash/flash.h`
+
+The flash driver provides access to on-chip or external flash memory. Flash
+has specific constraints around alignment, erase-before-write, and region
+protection that the driver must handle.
+
+### Init
+
+Initialize the flash controller. This may involve clearing error flags or
+releasing hardware mutex locks. The board must enable the flash interface clock
+before calling Init.
+
+### Deinit
+
+Release flash controller resources.
+
+### Lock
+
+Write-protect a flash region to prevent modification. On some platforms this is
+a global lock (the `addr` and `len` parameters are ignored and the entire flash
+is locked). On others it may protect specific regions. After locking, Write and
+Erase operations on the protected region should fail until Unlock is called.
+
+The implementation varies significantly by platform — some use an unlock key
+sequence (where Lock simply sets a lock bit), while others have dedicated
+write-protect registers for individual regions.
+
+### Unlock
+
+Remove write protection to allow Write and Erase operations. On platforms that
+use a key sequence, this typically involves writing two specific magic values to
+a key register in the correct order. An incorrect sequence may trigger a bus
+fault (this is a hardware security feature).
+
+### Read
+
+Read data from flash. On most platforms, flash is memory-mapped, so this is a
+straightforward memory copy from the flash address into the provided buffer. No
+special flash controller interaction is needed.
+
+Some platforms may require acquiring a mutex or performing cache maintenance
+before reading.
+
+### Write
+
+Program data into flash starting at the given address. The caller must ensure
+the region is unlocked and erased before writing (flash can only transition bits
+from 1 to 0; erasing sets all bits to 1).
+
+Key constraints:
+- **Alignment**: writes must be aligned to the platform's programming unit
+ (e.g., 8-byte double-word on STM32, 8 or 32 bytes on PIC32CZ). The driver
+ should validate alignment and return an error if misaligned.
+- **Programming unit**: the driver writes data in hardware-defined chunks. Some
+ platforms support multiple write sizes (e.g., single double-word vs. quad
+ double-word) and the driver can optimize by using larger writes when
+ alignment permits.
+- **Busy polling**: after each write, poll the flash controller's busy flag
+ until the operation completes.
+- **Error flags**: clear any pending error flags before starting, and check for
+ new errors after each operation.
+
+### Erase
+
+Erase a flash region. Flash erase operates at sector/page granularity
+(typically 4 KB). The driver should:
+
+- Calculate the page range covering the requested address and size
+- Erase each page individually, polling for completion between pages
+- Validate that the region is unlocked before erasing
+
+The `addr` does not need to be page-aligned — the driver should erase all pages
+that overlap with the requested range. Peripheral flash drivers (e.g., SPI-NOR)
+may enforce stricter alignment requirements where the underlying hardware
+requires aligned erase addresses.
+
+---
+
+## Block
+
+Header: `wolfHAL/block/block.h`
+
+The block driver provides access to block-addressable storage devices such as
+SD cards and eMMC. Unlike flash, block devices are addressed by block number
+rather than byte address, and all operations work in units of fixed-size blocks
+(e.g. 512 bytes).
+
+Block drivers are peripheral drivers — they call their underlying bus driver
+(SPI, SDIO) to communicate with the storage device.
+
+### Init
+
+Initialize the storage device. This includes any device-specific initialization
+sequence required to bring the device into a usable state. The board must have
+already initialized the bus driver and enabled the relevant clocks and GPIOs
+before calling Init.
+
+### Deinit
+
+Release the storage device.
+
+### Read
+
+Read one or more blocks from the device into a buffer. The driver should handle
+both single-block and multi-block reads based on the block count.
+
+### Write
+
+Write one or more blocks from a buffer to the device. The driver should handle
+both single-block and multi-block writes, and wait for the device to finish
+programming before returning.
+
+### Erase
+
+Erase a range of blocks on the device.
+
+---
+
+## Timer
+
+Header: `wolfHAL/timer/timer.h`
+
+The timer driver provides periodic tick or counter functionality. The most
+common implementation is the ARM Cortex-M SysTick timer, which provides a
+simple periodic interrupt for system timekeeping.
+
+### Init
+
+Configure the timer hardware but do **not** start it. This includes:
+
+- Setting the reload value (determines the tick interval:
+ interval = reload / clock_frequency)
+- Selecting the clock source (e.g., CPU clock vs. external reference)
+- Enabling or disabling the tick interrupt
+
+The timer should not begin counting until Start is called.
+
+### Deinit
+
+Stop the timer and release resources.
+
+### Start
+
+Start the timer counting. The timer begins decrementing from the configured
+reload value, generating interrupts (if enabled) each time it reaches zero and
+reloading automatically.
+
+### Stop
+
+Stop the timer without resetting it. The counter holds its current value.
+
+### Reset
+
+Reset the timer counter back to its initialized state.
+
+---
+
+## RNG
+
+Header: `wolfHAL/rng/rng.h`
+
+The RNG driver provides access to a hardware random number generator. The
+hardware typically uses an analog entropy source to produce true random numbers.
+
+### Init
+
+Initialize the RNG hardware. The board must enable the peripheral clock (and
+any additional clock sources the RNG's entropy source requires) before calling
+Init.
+
+### Deinit
+
+Shut down the RNG hardware.
+
+### Generate
+
+Fill the provided buffer with random data. The driver should:
+
+1. Enable the RNG peripheral
+2. Loop until the buffer is filled:
+ - Check for hardware errors (seed errors, clock errors). If the entropy
+ source has failed, disable the RNG and return `WHAL_EHARDWARE`
+ - Poll the data-ready flag until a new random word is available
+ - Read the random word (typically 32 bits) and extract as many bytes as
+ needed into the output buffer
+3. Disable the RNG peripheral before returning
+
+The RNG is enabled only for the duration of the Generate call to minimize power
+consumption and avoid unnecessary entropy source wear.
+
+---
+
+## Crypto
+
+Header: `wolfHAL/crypto/crypto.h`
+
+The crypto driver provides access to hardware cryptographic accelerators
+(ciphers, hashes, MACs, and public key operations). The driver vtable uses a
+unified **StartOp / Process / EndOp** pattern that supports both one-shot and
+streaming use cases.
+
+### Device Struct
+
+```c
+struct whal_Crypto {
+ const whal_Regmap regmap;
+ const whal_CryptoDriver *driver;
+ const void *cfg;
+};
+```
+
+### Driver Vtable
+
+```c
+typedef struct {
+ whal_Error (*Init)(whal_Crypto *cryptoDev);
+ whal_Error (*Deinit)(whal_Crypto *cryptoDev);
+ whal_Error (*StartOp)(whal_Crypto *cryptoDev, size_t opId, void *opArgs);
+ whal_Error (*Process)(whal_Crypto *cryptoDev, size_t opId, void *opArgs);
+ whal_Error (*EndOp)(whal_Crypto *cryptoDev, size_t opId, void *opArgs);
+} whal_CryptoDriver;
+```
+
+The `opId` parameter is a framework-defined enum value (e.g. `WHAL_CRYPTO_AES_GCM`,
+`WHAL_CRYPTO_SHA256`). The `opArgs` parameter is a pointer to an
+algorithm-specific argument struct (e.g., `whal_Crypto_AesGcmArgs`,
+`whal_Crypto_HashArgs`). The driver casts it to the correct type based on
+`opId`. See `wolfHAL/crypto/crypto.h` for the full set of argument structs and
+operation IDs.
+
+### Init / Deinit
+
+The board must enable the peripheral clock before calling Init. Deinit should
+disable the crypto accelerator peripheral.
+
+### Operations
+
+Each crypto operation is split into three phases:
+
+- **StartOp** — Configure hardware, load key/IV, process AAD for AEAD modes.
+- **Process** — Feed data through the hardware. May be called multiple times
+ for streaming. Optional for single-shot operations (e.g. GMAC has no
+ payload).
+- **EndOp** — Finalize the operation, read output (tag, digest), release
+ hardware. On StartOp failure, the driver cleans up internally and EndOp
+ should not be called.
+
+Unsupported `opId` values return `WHAL_ENOTSUP`. Unsupported parameter
+combinations (e.g. AES-192 on hardware that only supports 128/256) also
+return `WHAL_ENOTSUP`.
+
+### Convenience Wrappers
+
+`crypto.h` provides typed inline wrappers for each algorithm. One-shot
+wrappers call all three phases in sequence:
+
+```c
+whal_Crypto_AesGcmArgs args = {
+ .dir = WHAL_CRYPTO_ENCRYPT, .key = key, .keySz = 32,
+ .iv = iv, .ivSz = 12,
+ .in = plaintext, .out = ct, .sz = sizeof(plaintext),
+ .aad = aad, .aadSz = sizeof(aad),
+ .tag = tag, .tagSz = 16,
+};
+whal_Crypto_AesGcm(&g_whalCrypto, &args);
+```
+
+Streaming wrappers expose each phase individually with typed parameters:
+
+```c
+whal_Crypto_Sha256_Start(&g_whalHash);
+whal_Crypto_Sha256_Update(&g_whalHash, chunk1, chunk1Sz);
+whal_Crypto_Sha256_Update(&g_whalHash, chunk2, chunk2Sz);
+whal_Crypto_Sha256_Finalize(&g_whalHash, digest, 32);
+```
+
+Both wrapper styles are guarded by `WHAL_CFG_CRYPTO_` defines (e.g.
+`WHAL_CFG_CRYPTO_AES_GCM`, `WHAL_CFG_CRYPTO_SHA256`).
+
+### Board Integration
+
+The board enables supported algorithms via `-D` flags in `board.mk` and
+instantiates the crypto device:
+
+```c
+whal_Crypto g_whalCrypto = {
+ .regmap = { WHAL_STM32WB55_AES1_REGMAP },
+ .cfg = &(whal_Stm32wb_Aes_Cfg) { .timeout = &g_whalTimeout },
+};
+```
+
+When API mapping is active (e.g. `-DWHAL_CFG_CRYPTO_API_MAPPING_STM32WB_AES`),
+the driver functions are mapped directly to the top-level API, eliminating the
+vtable indirection.
+
+---
+
+## Power
+
+Header: `wolfHAL/power/power.h`
+
+Power is a **board-level driver** (see Driver Categories). The generic
+`power.h` declares only the typed handle `whal_Power { regmap }` — no
+`whal_Power_Init`/`Deinit`/`Enable`/`Disable` API, no `whal_PowerDriver`
+vtable. Each chip power driver exposes imperative chip-specific helpers
+that boards call directly from `Board_Init` (typically before clock setup,
+to bring up regulators that downstream peripherals depend on).
+
+### API contract
+
+The chip power driver header declares chip-specific helpers named
+`whal___(...)`, where `` is the chip's
+power-controller name (e.g. `Pwr` on STM32L1, `Supc` on PIC32CZ). The set
+of operations is whatever the chip actually exposes — there is no fixed
+list. Examples:
+
+- `whal_Stm32l1_Pwr_SetVosRange(whal_Power *, range, timeout)` — voltage
+ scaling range select with ready-bit poll
+- `whal_Pic32cz_Supc_EnableSupply(whal_Power *, const whal_Pic32cz_Supc_Supply *)`
+ / `DisableSupply(...)` — toggle a regulator output identified by a
+ descriptor (register mask + position)
+
+NOT allowed in the public header: `Init`, `Deinit`, or any abstracted
+"generic power" operation. Internal helpers go in the `.c` file as
+`static`.
+
+The chip header may declare types, enums, and descriptor-initializer
+macros freely.
+
+### Reference implementations
+
+`wolfHAL/power/stm32l1_pwr.h` (single voltage-scaling helper) and
+`wolfHAL/power/pic32cz_supc.h` (regulator enable/disable by descriptor)
+are the canonical examples for the two common shapes.
+
+### Board responsibilities
+
+The board calls power helpers from `Board_Init` in the right order —
+typically before clock configuration, since some clock nodes (e.g., a
+PLL's analog regulator) require their supply to be enabled first. There
+is no separate Init/Deinit step; helpers are imperative and only do what
+they're called to do.
+
+---
+
+## Ethernet
+
+Header: `wolfHAL/eth/eth.h`
+
+The Ethernet driver controls a MAC (Media Access Controller) with an integrated
+DMA engine. It manages descriptor rings in RAM for transmit and receive, handles
+MDIO bus access for PHY communication, and configures the MAC for the negotiated
+link speed and duplex.
+
+### Init
+
+Initialize the MAC, DMA, and MTL. Set up TX and RX descriptor rings in RAM,
+program the MAC address, and configure DMA bus mode and burst lengths. Does NOT
+enable TX/RX — call Start for that. The descriptor and buffer memory must be
+pre-allocated by the board and passed via the config struct. Validate all config
+fields (descriptor counts > 0, buffer pointers non-NULL, buffer sizes > 0).
+
+### Deinit
+
+Perform a DMA software reset to clear all state.
+
+### Start
+
+Configure the MAC speed and duplex to match the PHY, enable MAC TX/RX, start
+the DMA TX and RX engines, and kick the RX DMA by writing the tail pointer.
+Speed and duplex are passed as parameters — the board reads these from the PHY
+driver before calling Start.
+
+### Stop
+
+Stop DMA TX and RX engines, then disable MAC TX and RX.
+
+### Send
+
+Transmit a single Ethernet frame. Find the next available TX descriptor (OWN=0),
+copy the frame into the pre-allocated TX buffer, fill in the descriptor fields
+(buffer address, length, OWN=1, FD, LD), and write the DMA tail pointer to kick
+transmission. Return WHAL_ENOTREADY if no descriptor is available. Validate that
+the frame length does not exceed the TX buffer size.
+
+### Recv
+
+Receive a single Ethernet frame by polling. Check the next RX descriptor — if
+OWN=0, DMA has written a frame. Read the packet length from the descriptor, copy
+the frame data to the caller's buffer, re-arm the descriptor (set OWN=1), and
+update the tail pointer. Return WHAL_ENOTREADY if no frame is available, or
+WHAL_EHARDWARE if the error summary bit is set.
+
+### MdioRead
+
+Read a 16-bit PHY register via the MDIO management bus. Write the PHY address,
+register address, and read command to the MDIO address register, poll for
+completion, then read the data register. Use a timeout to avoid infinite hang
+if the PHY is not responding.
+
+### MdioWrite
+
+Write a 16-bit value to a PHY register via MDIO. Write the data first, then
+issue the write command and poll for completion with a timeout.
+
+---
+
+## EthPhy
+
+Header: `wolfHAL/eth_phy/eth_phy.h`
+
+The Ethernet PHY driver handles link negotiation and status for an external PHY
+chip connected to a MAC via the MDIO bus. The PHY device struct holds a pointer
+to its parent MAC (for MDIO access) and the PHY address on the bus. Different
+PHY chips (e.g., LAN8742A, DP83848) have different vendor-specific registers
+but share the same API.
+
+### Init
+
+Reset the PHY via software reset (BCR bit 15), wait for the reset bit to
+self-clear, then enable autonegotiation. Does not block waiting for link — the
+board or application polls GetLinkState separately.
+
+### Deinit
+
+Power down the PHY or release resources. May be a no-op on simple PHYs.
+
+### GetLinkState
+
+Read the current link status, negotiated speed, and duplex mode. The IEEE 802.3
+BSR register (reg 1) link bit is latching-low — read it twice and use the second
+result for current status. Speed and duplex are read from a vendor-specific
+status register (e.g., register 0x1F on LAN8742A). Return speed as 10 or 100,
+and duplex as WHAL_ETH_DUPLEX_HALF or WHAL_ETH_DUPLEX_FULL.
+
+---
+
+## DMA
+
+Header: `wolfHAL/dma/dma.h`
+
+The DMA driver controls a DMA controller. A single device instance represents
+one controller, and individual channels are identified by index. Channel
+configuration is platform-specific and passed as an opaque pointer.
+
+DMA is a service peripheral — peripheral drivers (UART, SPI) consume it
+internally. The application never calls the DMA API directly. Peripheral
+drivers receive a `whal_Dma` pointer and channel number through their
+configuration struct and use them to set up transfers.
+
+### Init
+
+Initialize the DMA controller. Clear any pending interrupt flags and reset
+controller state. The board must enable the DMA controller clock before calling
+Init.
+
+### Deinit
+
+Shut down the DMA controller.
+
+### Configure
+
+Configure a DMA channel for transfers. The `chCfg` parameter is a
+platform-specific struct containing:
+
+- Transfer direction (memory-to-peripheral, peripheral-to-memory, etc.)
+- Source and destination addresses
+- Transfer width (8, 16, 32 bit)
+- Buffer address and length
+- Burst size (if supported)
+- Peripheral request mapping (e.g., DMAMUX request ID)
+
+The DMA driver does not store callbacks. Instead, the board defines ISR
+entries in the vector table and calls the driver's IRQ handler (e.g.,
+`whal_Stm32wb_Dma_IRQHandler()`), passing a callback and context pointer.
+The IRQ handler checks and clears the interrupt flags, then invokes the
+callback. Peripheral drivers expose their completion callbacks for the
+board to wire up (e.g., `whal_Stm32wb_UartDma_TxCallback`).
+
+Configure sets up all channel registers but does not start the transfer.
+Call Start to begin. A channel can be reconfigured between transfers (e.g.,
+to change the buffer address and length) by calling Configure again.
+
+### Start
+
+Start a previously configured DMA channel. This enables the channel,
+beginning the transfer. The channel must have been configured via Configure
+before calling Start.
+
+### Stop
+
+Stop a DMA channel. This aborts any in-progress transfer and disables the
+channel. The peripheral driver should call Stop in its cleanup path or when
+a transfer needs to be cancelled.
+
+---
+
+## Watchdog
+
+Header: `wolfHAL/watchdog/watchdog.h`
+
+The watchdog driver provides access to hardware watchdog timers that reset the
+system if not periodically refreshed. On most hardware, the watchdog cannot be
+stopped once started — only a system reset clears it.
+
+### Init
+
+Configure and start the watchdog. This typically involves:
+
+- Setting the prescaler and reload/counter values from the configuration
+- Enabling the watchdog peripheral
+
+On some hardware (e.g., STM32 IWDG), the watchdog must be started before its
+configuration registers can be written. The driver should handle this ordering
+internally.
+
+The board must enable any required clocks before calling Init. For example, the
+STM32 WWDG requires an APB clock, while the IWDG requires the LSI oscillator.
+
+The watchdog is NOT initialized in `Board_Init` — the test or application
+controls when it starts, since once started it cannot be stopped.
+
+### Deinit
+
+Shut down the watchdog. On hardware where the watchdog cannot be stopped (e.g.,
+STM32 IWDG), this function is a no-op.
+
+### Refresh
+
+Reload the watchdog counter to prevent a reset. Must be called periodically
+within the configured timeout window. The exact mechanism is hardware-specific
+(e.g., writing a reload key to IWDG_KR, or writing the counter value back to
+WWDG_CR).
+
+For window watchdogs, the refresh must occur within the valid window — refreshing
+too early or too late triggers a reset. The driver does not enforce window timing;
+it writes the reload value unconditionally and relies on the hardware to enforce
+the window.
diff --git a/examples/README.md b/examples/README.md
new file mode 100644
index 0000000..46d5d48
--- /dev/null
+++ b/examples/README.md
@@ -0,0 +1,27 @@
+# wolfHAL Examples
+
+## Building
+
+From an example directory:
+
+```
+cd blinky
+make BOARD=
+```
+
+The output binary is placed in `build//`.
+
+## Example Structure
+
+Each example contains its application source and a Makefile that references a
+board from the top-level `boards/` directory:
+
+```
+/
+ main.c
+ Makefile
+```
+
+Board support (device instances, linker scripts, etc.) lives in the top-level
+`boards//` directory. See [boards/README.md](../boards/README.md) for
+details.
diff --git a/examples/blinky/Makefile b/examples/blinky/Makefile
new file mode 100644
index 0000000..e31db81
--- /dev/null
+++ b/examples/blinky/Makefile
@@ -0,0 +1,37 @@
+WHAL_DIR = $(CURDIR)/../..
+
+.DEFAULT_GOAL := all
+
+BOARD ?= stm32wb55xx_nucleo
+BOARD_DIR = $(WHAL_DIR)/boards/$(BOARD)
+BUILD_DIR = build/$(BOARD)
+
+INCLUDE = -I$(WHAL_DIR)
+
+include $(BOARD_DIR)/board.mk
+
+SOURCE = main.c
+SOURCE += $(BOARD_SOURCE)
+
+OBJECTS = $(patsubst %.c,$(BUILD_DIR)/%.o,$(SOURCE))
+DEPENDS = $(OBJECTS:.o=.d)
+
+all: $(BUILD_DIR)/$(notdir $(CURDIR)).bin
+
+$(BUILD_DIR)/%.o: %.c Makefile
+ @mkdir -p $(dir $@)
+ $(GCC) $(CFLAGS) -c -o $@ $<
+
+.SECONDARY:
+$(BUILD_DIR)/%.elf: $(OBJECTS) $(LINKER_SCRIPT)
+ @mkdir -p $(dir $@)
+ $(LD) $(LDFLAGS) -T $(LINKER_SCRIPT) -o $@ $(OBJECTS)
+
+$(BUILD_DIR)/%.bin: $(BUILD_DIR)/%.elf
+ $(OBJCOPY) $^ -O binary $@
+
+.PHONY: clean
+clean:
+ rm -rf build
+
+-include $(DEPENDS)
diff --git a/examples/blinky/main.c b/examples/blinky/main.c
new file mode 100644
index 0000000..89760d6
--- /dev/null
+++ b/examples/blinky/main.c
@@ -0,0 +1,21 @@
+#include
+#include
+#include "board.h"
+
+void main(void)
+{
+ if (Board_Init() != WHAL_SUCCESS)
+ goto loop;
+
+ while (1) {
+ whal_Gpio_Set(&g_whalGpio, BOARD_LED_PIN, 1);
+ whal_Uart_Send(&g_whalUart, "Blink!\r\n", 8);
+ Board_WaitMs(1000);
+ whal_Gpio_Set(&g_whalGpio, BOARD_LED_PIN, 0);
+ whal_Uart_Send(&g_whalUart, "Blink!\r\n", 8);
+ Board_WaitMs(1000);
+ }
+
+loop:
+ while (1);
+}
diff --git a/src/clock/stm32l1_rcc.c b/src/clock/stm32l1_rcc.c
new file mode 100644
index 0000000..35f650c
--- /dev/null
+++ b/src/clock/stm32l1_rcc.c
@@ -0,0 +1,129 @@
+#include
+#include
+#include
+#include
+#include
+
+#define RCC_CR_REG 0x00
+#define RCC_CR_PLLON_Msk (1UL << 24)
+#define RCC_CR_PLLRDY_Msk (1UL << 25)
+#define RCC_CR_PLLRDY_Pos 25
+
+#define RCC_CFGR_REG 0x08
+#define RCC_CFGR_SW_Pos 0
+#define RCC_CFGR_SW_Msk (WHAL_BITMASK(2) << RCC_CFGR_SW_Pos)
+#define RCC_CFGR_SWS_Pos 2
+#define RCC_CFGR_SWS_Msk (WHAL_BITMASK(2) << RCC_CFGR_SWS_Pos)
+#define RCC_CFGR_PLLSRC_Pos 16
+#define RCC_CFGR_PLLSRC_Msk (1UL << RCC_CFGR_PLLSRC_Pos)
+#define RCC_CFGR_PLLMUL_Pos 18
+#define RCC_CFGR_PLLMUL_Msk (WHAL_BITMASK(4) << RCC_CFGR_PLLMUL_Pos)
+#define RCC_CFGR_PLLDIV_Pos 22
+#define RCC_CFGR_PLLDIV_Msk (WHAL_BITMASK(2) << RCC_CFGR_PLLDIV_Pos)
+
+whal_Error whal_Stm32l1_Rcc_EnableOsc(whal_Clock *clkDev,
+ const whal_Stm32l1_Rcc_OscCfg *cfg)
+{
+ size_t rdy;
+
+ if (!clkDev || !cfg)
+ return WHAL_EINVAL;
+
+ whal_Reg_Update(clkDev->regmap.base, cfg->onReg, cfg->onMsk, cfg->onMsk);
+ do {
+ whal_Reg_Get(clkDev->regmap.base, cfg->rdyReg, cfg->rdyMsk,
+ cfg->rdyPos, &rdy);
+ } while (!rdy);
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_Stm32l1_Rcc_DisableOsc(whal_Clock *clkDev,
+ const whal_Stm32l1_Rcc_OscCfg *cfg)
+{
+ if (!clkDev || !cfg)
+ return WHAL_EINVAL;
+
+ whal_Reg_Update(clkDev->regmap.base, cfg->onReg, cfg->onMsk, 0);
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_Stm32l1_Rcc_EnablePll(whal_Clock *clkDev,
+ const whal_Stm32l1_Rcc_PllCfg *cfg)
+{
+ size_t rdy;
+ uint32_t pllsrc;
+
+ if (!clkDev || !cfg)
+ return WHAL_EINVAL;
+
+ /* Disable PLL before reconfiguring; wait until it's actually off. */
+ whal_Reg_Update(clkDev->regmap.base, RCC_CR_REG, RCC_CR_PLLON_Msk, 0);
+ do {
+ whal_Reg_Get(clkDev->regmap.base, RCC_CR_REG, RCC_CR_PLLRDY_Msk,
+ RCC_CR_PLLRDY_Pos, &rdy);
+ } while (rdy);
+
+ pllsrc = (cfg->clkSrc == WHAL_STM32L1_RCC_PLLSRC_HSE) ? 1 : 0;
+ whal_Reg_Update(clkDev->regmap.base, RCC_CFGR_REG, RCC_CFGR_PLLSRC_Msk,
+ whal_SetBits(RCC_CFGR_PLLSRC_Msk, RCC_CFGR_PLLSRC_Pos,
+ pllsrc));
+ whal_Reg_Update(clkDev->regmap.base, RCC_CFGR_REG, RCC_CFGR_PLLMUL_Msk,
+ whal_SetBits(RCC_CFGR_PLLMUL_Msk, RCC_CFGR_PLLMUL_Pos,
+ cfg->pllmul));
+ whal_Reg_Update(clkDev->regmap.base, RCC_CFGR_REG, RCC_CFGR_PLLDIV_Msk,
+ whal_SetBits(RCC_CFGR_PLLDIV_Msk, RCC_CFGR_PLLDIV_Pos,
+ cfg->plldiv));
+
+ whal_Reg_Update(clkDev->regmap.base, RCC_CR_REG, RCC_CR_PLLON_Msk,
+ RCC_CR_PLLON_Msk);
+ do {
+ whal_Reg_Get(clkDev->regmap.base, RCC_CR_REG, RCC_CR_PLLRDY_Msk,
+ RCC_CR_PLLRDY_Pos, &rdy);
+ } while (!rdy);
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_Stm32l1_Rcc_DisablePll(whal_Clock *clkDev)
+{
+ if (!clkDev)
+ return WHAL_EINVAL;
+ whal_Reg_Update(clkDev->regmap.base, RCC_CR_REG, RCC_CR_PLLON_Msk, 0);
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_Stm32l1_Rcc_SetSysClock(whal_Clock *clkDev,
+ whal_Stm32l1_Rcc_SysClockSrc src)
+{
+ size_t sws;
+
+ if (!clkDev)
+ return WHAL_EINVAL;
+
+ whal_Reg_Update(clkDev->regmap.base, RCC_CFGR_REG, RCC_CFGR_SW_Msk,
+ whal_SetBits(RCC_CFGR_SW_Msk, RCC_CFGR_SW_Pos, src));
+ do {
+ whal_Reg_Get(clkDev->regmap.base, RCC_CFGR_REG, RCC_CFGR_SWS_Msk,
+ RCC_CFGR_SWS_Pos, &sws);
+ } while (sws != (size_t)src);
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_Stm32l1_Rcc_EnablePeriphClk(whal_Clock *clkDev,
+ const whal_Stm32l1_Rcc_PeriphClk *clk)
+{
+ if (!clkDev || !clk)
+ return WHAL_EINVAL;
+ whal_Reg_Update(clkDev->regmap.base, clk->regOffset, clk->enableMask,
+ whal_SetBits(clk->enableMask, clk->enablePos, 1));
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_Stm32l1_Rcc_DisablePeriphClk(whal_Clock *clkDev,
+ const whal_Stm32l1_Rcc_PeriphClk *clk)
+{
+ if (!clkDev || !clk)
+ return WHAL_EINVAL;
+ whal_Reg_Update(clkDev->regmap.base, clk->regOffset, clk->enableMask,
+ whal_SetBits(clk->enableMask, clk->enablePos, 0));
+ return WHAL_SUCCESS;
+}
diff --git a/src/clock/stm32wb_rcc.c b/src/clock/stm32wb_rcc.c
new file mode 100644
index 0000000..11e6cbd
--- /dev/null
+++ b/src/clock/stm32wb_rcc.c
@@ -0,0 +1,167 @@
+#include
+#include
+#include
+#include
+#include
+
+/*
+ * STM32WB RCC Register Definitions
+ */
+
+/* Clock Control Register */
+#define RCC_CR_REG 0x000
+#define RCC_CR_MSION_Msk (1UL << 0)
+#define RCC_CR_MSIRDY_Msk (1UL << 1)
+#define RCC_CR_MSIRDY_Pos 1
+#define RCC_CR_MSIRANGE_Pos 4
+#define RCC_CR_MSIRANGE_Msk (WHAL_BITMASK(4) << RCC_CR_MSIRANGE_Pos)
+#define RCC_CR_PLLON_Msk (1UL << 24)
+#define RCC_CR_PLLRDY_Msk (1UL << 25)
+#define RCC_CR_PLLRDY_Pos 25
+
+/* Clock Configuration Register */
+#define RCC_CFGR_REG 0x008
+#define RCC_CFGR_SW_Pos 0
+#define RCC_CFGR_SW_Msk (WHAL_BITMASK(2) << RCC_CFGR_SW_Pos)
+#define RCC_CFGR_SWS_Pos 2
+#define RCC_CFGR_SWS_Msk (WHAL_BITMASK(2) << RCC_CFGR_SWS_Pos)
+
+/* PLL Configuration Register */
+#define RCC_PLLCFGR_REG 0x00C
+#define RCC_PLLCFGR_PLLSRC_Pos 0
+#define RCC_PLLCFGR_PLLSRC_Msk (WHAL_BITMASK(2) << RCC_PLLCFGR_PLLSRC_Pos)
+#define RCC_PLLCFGR_PLLM_Pos 4
+#define RCC_PLLCFGR_PLLM_Msk (WHAL_BITMASK(3) << RCC_PLLCFGR_PLLM_Pos)
+#define RCC_PLLCFGR_PLLN_Pos 8
+#define RCC_PLLCFGR_PLLN_Msk (WHAL_BITMASK(7) << RCC_PLLCFGR_PLLN_Pos)
+#define RCC_PLLCFGR_PLLP_Pos 17
+#define RCC_PLLCFGR_PLLP_Msk (WHAL_BITMASK(5) << RCC_PLLCFGR_PLLP_Pos)
+#define RCC_PLLCFGR_PLLQ_Pos 25
+#define RCC_PLLCFGR_PLLQ_Msk (WHAL_BITMASK(3) << RCC_PLLCFGR_PLLQ_Pos)
+#define RCC_PLLCFGR_PLLREN_Pos 28
+#define RCC_PLLCFGR_PLLREN_Msk (1UL << RCC_PLLCFGR_PLLREN_Pos)
+#define RCC_PLLCFGR_PLLR_Pos 29
+#define RCC_PLLCFGR_PLLR_Msk (WHAL_BITMASK(3) << RCC_PLLCFGR_PLLR_Pos)
+#define RCC_PLLCFGR_Msk \
+ (RCC_PLLCFGR_PLLSRC_Msk | RCC_PLLCFGR_PLLM_Msk | RCC_PLLCFGR_PLLN_Msk | \
+ RCC_PLLCFGR_PLLP_Msk | RCC_PLLCFGR_PLLQ_Msk | RCC_PLLCFGR_PLLREN_Msk | \
+ RCC_PLLCFGR_PLLR_Msk)
+
+whal_Error whal_Stm32wb_Rcc_EnableOsc(whal_Clock *clkDev,
+ const whal_Stm32wb_Rcc_OscCfg *cfg)
+{
+ size_t rdy;
+
+ if (!clkDev || !cfg)
+ return WHAL_EINVAL;
+
+ whal_Reg_Update(clkDev->regmap.base, cfg->onReg, cfg->onMsk, cfg->onMsk);
+ do {
+ whal_Reg_Get(clkDev->regmap.base, cfg->rdyReg, cfg->rdyMsk,
+ cfg->rdyPos, &rdy);
+ } while (!rdy);
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_Stm32wb_Rcc_DisableOsc(whal_Clock *clkDev,
+ const whal_Stm32wb_Rcc_OscCfg *cfg)
+{
+ if (!clkDev || !cfg)
+ return WHAL_EINVAL;
+
+ whal_Reg_Update(clkDev->regmap.base, cfg->onReg, cfg->onMsk, 0);
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_Stm32wb_Rcc_EnableMsi(whal_Clock *clkDev,
+ whal_Stm32wb_Rcc_MsiRange range)
+{
+ size_t rdy;
+
+ if (!clkDev)
+ return WHAL_EINVAL;
+
+ whal_Reg_Update(clkDev->regmap.base, RCC_CR_REG, RCC_CR_MSION_Msk,
+ RCC_CR_MSION_Msk);
+ do {
+ whal_Reg_Get(clkDev->regmap.base, RCC_CR_REG, RCC_CR_MSIRDY_Msk,
+ RCC_CR_MSIRDY_Pos, &rdy);
+ } while (!rdy);
+ whal_Reg_Update(clkDev->regmap.base, RCC_CR_REG, RCC_CR_MSIRANGE_Msk,
+ whal_SetBits(RCC_CR_MSIRANGE_Msk, RCC_CR_MSIRANGE_Pos,
+ range));
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_Stm32wb_Rcc_EnablePll(whal_Clock *clkDev,
+ const whal_Stm32wb_Rcc_PllCfg *cfg)
+{
+ size_t rdy;
+
+ if (!clkDev || !cfg)
+ return WHAL_EINVAL;
+
+ whal_Reg_Update(clkDev->regmap.base, RCC_PLLCFGR_REG, RCC_PLLCFGR_Msk,
+ whal_SetBits(RCC_PLLCFGR_PLLSRC_Msk, RCC_PLLCFGR_PLLSRC_Pos, cfg->clkSrc) |
+ whal_SetBits(RCC_PLLCFGR_PLLM_Msk, RCC_PLLCFGR_PLLM_Pos, cfg->m) |
+ whal_SetBits(RCC_PLLCFGR_PLLN_Msk, RCC_PLLCFGR_PLLN_Pos, cfg->n) |
+ whal_SetBits(RCC_PLLCFGR_PLLP_Msk, RCC_PLLCFGR_PLLP_Pos, cfg->p) |
+ whal_SetBits(RCC_PLLCFGR_PLLQ_Msk, RCC_PLLCFGR_PLLQ_Pos, cfg->q) |
+ whal_SetBits(RCC_PLLCFGR_PLLREN_Msk, RCC_PLLCFGR_PLLREN_Pos, 1) |
+ whal_SetBits(RCC_PLLCFGR_PLLR_Msk, RCC_PLLCFGR_PLLR_Pos, cfg->r));
+ whal_Reg_Update(clkDev->regmap.base, RCC_CR_REG, RCC_CR_PLLON_Msk,
+ RCC_CR_PLLON_Msk);
+ do {
+ whal_Reg_Get(clkDev->regmap.base, RCC_CR_REG, RCC_CR_PLLRDY_Msk,
+ RCC_CR_PLLRDY_Pos, &rdy);
+ } while (!rdy);
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_Stm32wb_Rcc_DisablePll(whal_Clock *clkDev)
+{
+ if (!clkDev)
+ return WHAL_EINVAL;
+
+ whal_Reg_Update(clkDev->regmap.base, RCC_CR_REG, RCC_CR_PLLON_Msk, 0);
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_Stm32wb_Rcc_SetSysClock(whal_Clock *clkDev,
+ whal_Stm32wb_Rcc_SysClockSrc src)
+{
+ size_t sws;
+
+ if (!clkDev)
+ return WHAL_EINVAL;
+
+ whal_Reg_Update(clkDev->regmap.base, RCC_CFGR_REG, RCC_CFGR_SW_Msk,
+ whal_SetBits(RCC_CFGR_SW_Msk, RCC_CFGR_SW_Pos, src));
+ do {
+ whal_Reg_Get(clkDev->regmap.base, RCC_CFGR_REG, RCC_CFGR_SWS_Msk,
+ RCC_CFGR_SWS_Pos, &sws);
+ } while (sws != (size_t)src);
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_Stm32wb_Rcc_EnablePeriphClk(whal_Clock *clkDev,
+ const whal_Stm32wb_Rcc_PeriphClk *clk)
+{
+ if (!clkDev || !clk)
+ return WHAL_EINVAL;
+
+ whal_Reg_Update(clkDev->regmap.base, clk->regOffset, clk->enableMask,
+ whal_SetBits(clk->enableMask, clk->enablePos, 1));
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_Stm32wb_Rcc_DisablePeriphClk(whal_Clock *clkDev,
+ const whal_Stm32wb_Rcc_PeriphClk *clk)
+{
+ if (!clkDev || !clk)
+ return WHAL_EINVAL;
+
+ whal_Reg_Update(clkDev->regmap.base, clk->regOffset, clk->enableMask,
+ whal_SetBits(clk->enableMask, clk->enablePos, 0));
+ return WHAL_SUCCESS;
+}
diff --git a/src/flash/flash.c b/src/flash/flash.c
new file mode 100644
index 0000000..ca71d82
--- /dev/null
+++ b/src/flash/flash.c
@@ -0,0 +1,77 @@
+#include
+#include
+#include
+
+whal_Error whal_Flash_Init(whal_Flash *flashDev)
+{
+ if (!flashDev)
+ return WHAL_EINVAL;
+ if (!flashDev->driver || !flashDev->driver->Init)
+ return WHAL_ENOTSUP;
+
+ return flashDev->driver->Init(flashDev);
+}
+
+whal_Error whal_Flash_Deinit(whal_Flash *flashDev)
+{
+ if (!flashDev)
+ return WHAL_EINVAL;
+ if (!flashDev->driver || !flashDev->driver->Deinit)
+ return WHAL_ENOTSUP;
+
+ return flashDev->driver->Deinit(flashDev);
+}
+
+whal_Error whal_Flash_Lock(whal_Flash *flashDev, size_t addr, size_t len)
+{
+ if (!flashDev)
+ return WHAL_EINVAL;
+ if (!flashDev->driver || !flashDev->driver->Lock)
+ return WHAL_ENOTSUP;
+
+ return flashDev->driver->Lock(flashDev, addr, len);
+}
+
+whal_Error whal_Flash_Unlock(whal_Flash *flashDev, size_t addr, size_t len)
+{
+ if (!flashDev)
+ return WHAL_EINVAL;
+ if (!flashDev->driver || !flashDev->driver->Unlock)
+ return WHAL_ENOTSUP;
+
+ return flashDev->driver->Unlock(flashDev, addr, len);
+}
+
+whal_Error whal_Flash_Read(whal_Flash *flashDev, size_t addr, void *data,
+ size_t dataSz)
+{
+ if (!flashDev || !data)
+ return WHAL_EINVAL;
+ if (!flashDev->driver || !flashDev->driver->Read)
+ return WHAL_ENOTSUP;
+
+ return flashDev->driver->Read(flashDev, addr, data, dataSz);
+}
+
+whal_Error whal_Flash_Write(whal_Flash *flashDev, size_t addr, const void *data,
+ size_t dataSz)
+{
+ if (!flashDev || !data)
+ return WHAL_EINVAL;
+ if (!flashDev->driver || !flashDev->driver->Write)
+ return WHAL_ENOTSUP;
+
+ return flashDev->driver->Write(flashDev, addr, data, dataSz);
+}
+
+whal_Error whal_Flash_Erase(whal_Flash *flashDev, size_t addr,
+ size_t dataSz)
+{
+ if (!flashDev)
+ return WHAL_EINVAL;
+ if (!flashDev->driver || !flashDev->driver->Erase)
+ return WHAL_ENOTSUP;
+
+ return flashDev->driver->Erase(flashDev, addr, dataSz);
+}
+
diff --git a/src/flash/stm32l1_flash.c b/src/flash/stm32l1_flash.c
new file mode 100644
index 0000000..6fda4d4
--- /dev/null
+++ b/src/flash/stm32l1_flash.c
@@ -0,0 +1,343 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/*
+ * STM32L1 Flash Register Definitions (RM0038 Section 3.9)
+ *
+ * The STM32L1 flash uses a PECR-based access model that differs from
+ * other STM32 families. All program/erase operations are controlled
+ * through FLASH_PECR bits, and unlocking requires a two-stage key
+ * sequence (PEKEYR then PRGKEYR).
+ */
+
+#define FLASH_ACR_REG 0x00
+#define FLASH_ACR_LATENCY_Pos 0
+#define FLASH_ACR_LATENCY_Msk (1UL << FLASH_ACR_LATENCY_Pos)
+#define FLASH_ACR_PRFTEN_Pos 1
+#define FLASH_ACR_PRFTEN_Msk (1UL << FLASH_ACR_PRFTEN_Pos)
+#define FLASH_ACR_ACC64_Pos 2
+#define FLASH_ACR_ACC64_Msk (1UL << FLASH_ACR_ACC64_Pos)
+
+#define FLASH_BASE_ADDR 0x40023C00
+
+#define FLASH_PECR_REG 0x04
+#define FLASH_PECR_PELOCK_Pos 0
+#define FLASH_PECR_PELOCK_Msk (1UL << FLASH_PECR_PELOCK_Pos)
+#define FLASH_PECR_PRGLOCK_Pos 1
+#define FLASH_PECR_PRGLOCK_Msk (1UL << FLASH_PECR_PRGLOCK_Pos)
+#define FLASH_PECR_PROG_Pos 3
+#define FLASH_PECR_PROG_Msk (1UL << FLASH_PECR_PROG_Pos)
+#define FLASH_PECR_ERASE_Pos 9
+#define FLASH_PECR_ERASE_Msk (1UL << FLASH_PECR_ERASE_Pos)
+
+#define FLASH_PEKEYR_REG 0x0C
+#define FLASH_PRGKEYR_REG 0x10
+
+#define FLASH_SR_REG 0x18
+#define FLASH_SR_BSY_Pos 0
+#define FLASH_SR_BSY_Msk (1UL << FLASH_SR_BSY_Pos)
+#define FLASH_SR_EOP_Pos 1
+#define FLASH_SR_EOP_Msk (1UL << FLASH_SR_EOP_Pos)
+#define FLASH_SR_WRPERR_Pos 8
+#define FLASH_SR_WRPERR_Msk (1UL << FLASH_SR_WRPERR_Pos)
+#define FLASH_SR_PGAERR_Pos 9
+#define FLASH_SR_PGAERR_Msk (1UL << FLASH_SR_PGAERR_Pos)
+#define FLASH_SR_SIZERR_Pos 10
+#define FLASH_SR_SIZERR_Msk (1UL << FLASH_SR_SIZERR_Pos)
+
+#define FLASH_SR_ALL_ERR (FLASH_SR_WRPERR_Msk | FLASH_SR_PGAERR_Msk | \
+ FLASH_SR_SIZERR_Msk)
+
+/* Unlock keys */
+#define PEKEY1 0x89ABCDEF
+#define PEKEY2 0x02030405
+#define PRGKEY1 0x8C9DAEBF
+#define PRGKEY2 0x13141516
+
+/* Page size: 256 bytes */
+#define PAGE_SIZE 256
+#define PAGE_SHIFT 8
+
+#ifdef WHAL_CFG_STM32L1_FLASH_DIRECT_API_MAPPING
+#define whal_Stm32l1_Flash_Init whal_Flash_Init
+#define whal_Stm32l1_Flash_Deinit whal_Flash_Deinit
+#define whal_Stm32l1_Flash_Lock whal_Flash_Lock
+#define whal_Stm32l1_Flash_Unlock whal_Flash_Unlock
+#define whal_Stm32l1_Flash_Read whal_Flash_Read
+#define whal_Stm32l1_Flash_Write whal_Flash_Write
+#define whal_Stm32l1_Flash_Erase whal_Flash_Erase
+#endif
+
+/*
+ * Unlock FLASH_PECR by writing the PEKEY sequence to FLASH_PEKEYR.
+ * This clears PELOCK if the keys are correct.
+ */
+static void UnlockPecr(size_t base)
+{
+ size_t pelock;
+ whal_Reg_Get(base, FLASH_PECR_REG, FLASH_PECR_PELOCK_Msk,
+ FLASH_PECR_PELOCK_Pos, &pelock);
+ if (!pelock)
+ return;
+
+ whal_Reg_Write(base, FLASH_PEKEYR_REG, PEKEY1);
+ whal_Reg_Write(base, FLASH_PEKEYR_REG, PEKEY2);
+}
+
+/*
+ * Unlock program memory by writing the PRGKEY sequence to FLASH_PRGKEYR.
+ * PECR must be unlocked first.
+ */
+static void UnlockProgram(size_t base)
+{
+ size_t prglock;
+ whal_Reg_Get(base, FLASH_PECR_REG, FLASH_PECR_PRGLOCK_Msk,
+ FLASH_PECR_PRGLOCK_Pos, &prglock);
+ if (!prglock)
+ return;
+
+ whal_Reg_Write(base, FLASH_PRGKEYR_REG, PRGKEY1);
+ whal_Reg_Write(base, FLASH_PRGKEYR_REG, PRGKEY2);
+}
+
+whal_Error whal_Stm32l1_Flash_Init(whal_Flash *flashDev)
+{
+ if (!flashDev || !flashDev->cfg)
+ return WHAL_EINVAL;
+
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_Stm32l1_Flash_Deinit(whal_Flash *flashDev)
+{
+ if (!flashDev)
+ return WHAL_EINVAL;
+
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_Stm32l1_Flash_Lock(whal_Flash *flashDev, size_t addr, size_t len)
+{
+ (void)addr;
+ (void)len;
+
+ if (!flashDev)
+ return WHAL_EINVAL;
+
+ /* Set PELOCK to lock everything */
+ whal_Reg_Update(flashDev->regmap.base, FLASH_PECR_REG,
+ FLASH_PECR_PELOCK_Msk,
+ whal_SetBits(FLASH_PECR_PELOCK_Msk,
+ FLASH_PECR_PELOCK_Pos, 1));
+
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_Stm32l1_Flash_Unlock(whal_Flash *flashDev, size_t addr, size_t len)
+{
+ size_t base, pelock, prglock;
+
+ (void)addr;
+ (void)len;
+
+ if (!flashDev)
+ return WHAL_EINVAL;
+
+ base = flashDev->regmap.base;
+
+ /* Unlock PECR then program memory */
+ UnlockPecr(base);
+ UnlockProgram(base);
+
+ /* Verify both lock bits cleared. If either is still set, the key
+ * sequence was rejected — typically because of a prior bad write. */
+ whal_Reg_Get(base, FLASH_PECR_REG, FLASH_PECR_PELOCK_Msk,
+ FLASH_PECR_PELOCK_Pos, &pelock);
+ whal_Reg_Get(base, FLASH_PECR_REG, FLASH_PECR_PRGLOCK_Msk,
+ FLASH_PECR_PRGLOCK_Pos, &prglock);
+ if (pelock || prglock)
+ return WHAL_EHARDWARE;
+
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_Stm32l1_Flash_Read(whal_Flash *flashDev, size_t addr, void *data,
+ size_t dataSz)
+{
+ whal_Stm32l1_Flash_Cfg *cfg;
+ uint8_t *dataBuf = (uint8_t *)data;
+
+ if (!flashDev || !flashDev->cfg || !data)
+ return WHAL_EINVAL;
+
+ if (dataSz == 0)
+ return WHAL_SUCCESS;
+
+ cfg = flashDev->cfg;
+
+ if (addr < cfg->startAddr || addr + dataSz > cfg->startAddr + cfg->size)
+ return WHAL_EINVAL;
+
+ /* Flash is memory-mapped, direct read */
+ uint8_t *flashAddr = (uint8_t *)addr;
+ for (size_t i = 0; i < dataSz; ++i)
+ dataBuf[i] = flashAddr[i];
+
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_Stm32l1_Flash_Write(whal_Flash *flashDev, size_t addr,
+ const void *data, size_t dataSz)
+{
+ whal_Stm32l1_Flash_Cfg *cfg;
+ const uint8_t *buf = (const uint8_t *)data;
+ size_t base;
+ whal_Error err = WHAL_SUCCESS;
+
+ if (!flashDev || !flashDev->cfg || !data)
+ return WHAL_EINVAL;
+
+ if (dataSz == 0)
+ return WHAL_SUCCESS;
+
+ /* Word-aligned writes only */
+ if ((addr & 0x3) || (dataSz & 0x3))
+ return WHAL_EINVAL;
+
+ cfg = flashDev->cfg;
+ base = flashDev->regmap.base;
+
+ if (addr < cfg->startAddr || addr + dataSz > cfg->startAddr + cfg->size)
+ return WHAL_EINVAL;
+
+ /* Clear error flags */
+ whal_Reg_Write(base, FLASH_SR_REG, FLASH_SR_ALL_ERR);
+
+ /*
+ * Fast Word Write: PECR and program memory must already be unlocked.
+ * Simply write a 32-bit word to the flash address; the hardware
+ * performs the programming automatically.
+ */
+ for (size_t i = 0; i < dataSz; i += 4) {
+ volatile uint32_t *flashAddr = (volatile uint32_t *)(addr + i);
+ uint32_t word = (uint32_t)buf[i]
+ | ((uint32_t)buf[i + 1] << 8)
+ | ((uint32_t)buf[i + 2] << 16)
+ | ((uint32_t)buf[i + 3] << 24);
+
+ *flashAddr = word;
+
+ err = whal_Reg_ReadPoll(base, FLASH_SR_REG, FLASH_SR_BSY_Msk, 0,
+ cfg->timeout);
+ if (err)
+ return err;
+
+ if (whal_Reg_Read(base, FLASH_SR_REG) & FLASH_SR_ALL_ERR)
+ return WHAL_EHARDWARE;
+ }
+
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_Stm32l1_Flash_Erase(whal_Flash *flashDev, size_t addr,
+ size_t dataSz)
+{
+ whal_Stm32l1_Flash_Cfg *cfg;
+ size_t base;
+ whal_Error err = WHAL_SUCCESS;
+
+ if (!flashDev || !flashDev->cfg)
+ return WHAL_EINVAL;
+
+ if (dataSz == 0)
+ return WHAL_SUCCESS;
+
+ cfg = flashDev->cfg;
+ base = flashDev->regmap.base;
+
+ if (addr < cfg->startAddr || addr + dataSz > cfg->startAddr + cfg->size)
+ return WHAL_EINVAL;
+
+ err = whal_Reg_ReadPoll(base, FLASH_SR_REG, FLASH_SR_BSY_Msk, 0,
+ cfg->timeout);
+ if (err)
+ return err;
+
+ whal_Reg_Write(base, FLASH_SR_REG, FLASH_SR_ALL_ERR);
+
+ size_t startPage = (addr - cfg->startAddr) >> PAGE_SHIFT;
+ size_t endPage = ((addr - cfg->startAddr) + dataSz - 1) >> PAGE_SHIFT;
+
+ whal_Reg_Update(base, FLASH_PECR_REG, FLASH_PECR_ERASE_Msk,
+ whal_SetBits(FLASH_PECR_ERASE_Msk,
+ FLASH_PECR_ERASE_Pos, 1));
+ whal_Reg_Update(base, FLASH_PECR_REG, FLASH_PECR_PROG_Msk,
+ whal_SetBits(FLASH_PECR_PROG_Msk,
+ FLASH_PECR_PROG_Pos, 1));
+
+ for (size_t page = startPage; page <= endPage; ++page) {
+ volatile uint32_t *pageAddr =
+ (volatile uint32_t *)(cfg->startAddr + (page << PAGE_SHIFT));
+ *pageAddr = 0x00000000;
+
+ err = whal_Reg_ReadPoll(base, FLASH_SR_REG, FLASH_SR_BSY_Msk, 0,
+ cfg->timeout);
+ if (err)
+ goto cleanup;
+
+ if (whal_Reg_Read(base, FLASH_SR_REG) & FLASH_SR_ALL_ERR) {
+ err = WHAL_EHARDWARE;
+ goto cleanup;
+ }
+ }
+
+cleanup:
+ whal_Reg_Update(base, FLASH_PECR_REG,
+ FLASH_PECR_ERASE_Msk | FLASH_PECR_PROG_Msk, 0);
+
+ return err;
+}
+
+whal_Error whal_Stm32l1_Flash_Ext_SetLatency(whal_Stm32l1_Flash_Latency latency)
+{
+ size_t val;
+
+ whal_Reg_Update(FLASH_BASE_ADDR, FLASH_ACR_REG, FLASH_ACR_ACC64_Msk,
+ whal_SetBits(FLASH_ACR_ACC64_Msk,
+ FLASH_ACR_ACC64_Pos, 1));
+ do {
+ whal_Reg_Get(FLASH_BASE_ADDR, FLASH_ACR_REG,
+ FLASH_ACR_ACC64_Msk, FLASH_ACR_ACC64_Pos, &val);
+ } while (!val);
+
+ whal_Reg_Update(FLASH_BASE_ADDR, FLASH_ACR_REG, FLASH_ACR_PRFTEN_Msk,
+ whal_SetBits(FLASH_ACR_PRFTEN_Msk,
+ FLASH_ACR_PRFTEN_Pos, 1));
+
+ whal_Reg_Update(FLASH_BASE_ADDR, FLASH_ACR_REG, FLASH_ACR_LATENCY_Msk,
+ whal_SetBits(FLASH_ACR_LATENCY_Msk,
+ FLASH_ACR_LATENCY_Pos, latency));
+ do {
+ whal_Reg_Get(FLASH_BASE_ADDR, FLASH_ACR_REG,
+ FLASH_ACR_LATENCY_Msk, FLASH_ACR_LATENCY_Pos, &val);
+ } while (val != (size_t)latency);
+
+ return WHAL_SUCCESS;
+}
+
+#ifndef WHAL_CFG_STM32L1_FLASH_DIRECT_API_MAPPING
+const whal_FlashDriver whal_Stm32l1_Flash_Driver = {
+ .Init = whal_Stm32l1_Flash_Init,
+ .Deinit = whal_Stm32l1_Flash_Deinit,
+ .Lock = whal_Stm32l1_Flash_Lock,
+ .Unlock = whal_Stm32l1_Flash_Unlock,
+ .Read = whal_Stm32l1_Flash_Read,
+ .Write = whal_Stm32l1_Flash_Write,
+ .Erase = whal_Stm32l1_Flash_Erase,
+};
+#endif /* !WHAL_CFG_STM32L1_FLASH_DIRECT_API_MAPPING */
diff --git a/src/flash/stm32wb_flash.c b/src/flash/stm32wb_flash.c
new file mode 100644
index 0000000..d5edaf1
--- /dev/null
+++ b/src/flash/stm32wb_flash.c
@@ -0,0 +1,322 @@
+#include
+#include
+#include
+#include
+#include
+#include
+
+/*
+ * STM32WB Flash Register Definitions
+ *
+ * The flash controller manages embedded flash memory operations including
+ * programming, erasing, and access control. Flash is organized in 4 KB pages.
+ */
+
+/* Access Control Register - configures latency and caches */
+#define FLASH_ACR_REG 0x00
+#define FLASH_ACR_LATENCY_Pos 0 /* Wait states (0-3) */
+#define FLASH_ACR_LATENCY_Msk (WHAL_BITMASK(3) << FLASH_ACR_LATENCY_Pos)
+
+/* Key Register - unlock sequence for write operations */
+#define FLASH_KEYR_REG 0x08
+#define FLASH_KEYR_KEY_Msk (~0UL)
+
+/* Status Register - operation status and error flags */
+#define FLASH_SR_REG 0x10
+#define FLASH_SR_EOP_Pos 0 /* End of operation */
+#define FLASH_SR_EOP_Msk (1UL << FLASH_SR_EOP_Pos)
+
+#define FLASH_SR_OP_ERR_Pos 1 /* Operation error */
+#define FLASH_SR_OP_ERR_Msk (1UL << FLASH_SR_OP_ERR_Pos)
+
+#define FLASH_SR_PROG_ERR_Pos 3 /* Programming error */
+#define FLASH_SR_PROG_ERR_Msk (1UL << FLASH_SR_PROG_ERR_Pos)
+
+#define FLASH_SR_WRP_ERR_Pos 4 /* Write protection error */
+#define FLASH_SR_WRP_ERR_Msk (1UL << FLASH_SR_WRP_ERR_Pos)
+
+#define FLASH_SR_PGA_ERR_Pos 5 /* Programming alignment error */
+#define FLASH_SR_PGA_ERR_Msk (1UL << FLASH_SR_PGA_ERR_Pos)
+
+#define FLASH_SR_SIZ_ERR_Pos 6 /* Size error */
+#define FLASH_SR_SIZ_ERR_Msk (1UL << FLASH_SR_SIZ_ERR_Pos)
+
+#define FLASH_SR_PGS_ERR_Pos 7 /* Programming sequence error */
+#define FLASH_SR_PGS_ERR_Msk (1UL << FLASH_SR_PGS_ERR_Pos)
+
+#define FLASH_SR_MISS_ERR_Pos 8 /* Fast programming miss error */
+#define FLASH_SR_MISS_ERR_Msk (1UL << FLASH_SR_MISS_ERR_Pos)
+
+#define FLASH_SR_FAST_ERR_Pos 9 /* Fast programming error */
+#define FLASH_SR_FAST_ERR_Msk (1UL << FLASH_SR_FAST_ERR_Pos)
+
+#define FLASH_SR_RD_ERR_Pos 14 /* Read protection error */
+#define FLASH_SR_RD_ERR_Msk (1UL << FLASH_SR_RD_ERR_Pos)
+
+#define FLASH_SR_OPTV_ERR_Pos 15 /* Option validity error */
+#define FLASH_SR_OPTV_ERR_Msk (1UL << FLASH_SR_OPTV_ERR_Pos)
+
+#define FLASH_SR_BSY_Pos 16 /* Busy flag */
+#define FLASH_SR_BSY_Msk (1UL << FLASH_SR_BSY_Pos)
+
+#define FLASH_SR_CFGBSY_Pos 18 /* Configuration busy */
+#define FLASH_SR_CFGBSY_Msk (1UL << FLASH_SR_CFGBSY_Pos)
+
+#define FLASH_SR_PESD_Pos 19 /* Programming/erase suspended */
+#define FLASH_SR_PESD_Msk (1UL << FLASH_SR_PESD_Pos)
+
+/* Combined mask for all error flags */
+#define FLASH_SR_ALL_ERR (FLASH_SR_OP_ERR_Msk | FLASH_SR_PROG_ERR_Msk | FLASH_SR_WRP_ERR_Msk | \
+ FLASH_SR_PGA_ERR_Msk | FLASH_SR_SIZ_ERR_Msk | FLASH_SR_PGS_ERR_Msk | \
+ FLASH_SR_MISS_ERR_Msk | FLASH_SR_FAST_ERR_Msk | FLASH_SR_RD_ERR_Msk | \
+ FLASH_SR_OPTV_ERR_Msk)
+
+/* Control Register - enables operations and selects pages */
+#define FLASH_CR_REG 0x14
+#define FLASH_CR_PG_Pos 0 /* Programming enable */
+#define FLASH_CR_PG_Msk (1UL << FLASH_CR_PG_Pos)
+
+#define FLASH_CR_PER_Pos 1 /* Page erase enable */
+#define FLASH_CR_PER_Msk (1UL << FLASH_CR_PER_Pos)
+
+#define FLASH_CR_PNB_Pos 3 /* Page number for erase */
+#define FLASH_CR_PNB_Msk (WHAL_BITMASK(8) << FLASH_CR_PNB_Pos)
+
+#define FLASH_CR_STRT_Pos 16 /* Start erase operation */
+#define FLASH_CR_STRT_Msk (1UL << FLASH_CR_STRT_Pos)
+
+#define FLASH_CR_LOCK_Pos 31 /* Lock flash control */
+#define FLASH_CR_LOCK_Msk (1UL << FLASH_CR_LOCK_Pos)
+
+#ifdef WHAL_CFG_STM32WB_FLASH_DIRECT_API_MAPPING
+#define whal_Stm32wb_Flash_Init whal_Flash_Init
+#define whal_Stm32wb_Flash_Deinit whal_Flash_Deinit
+#define whal_Stm32wb_Flash_Lock whal_Flash_Lock
+#define whal_Stm32wb_Flash_Unlock whal_Flash_Unlock
+#define whal_Stm32wb_Flash_Read whal_Flash_Read
+#define whal_Stm32wb_Flash_Write whal_Flash_Write
+#define whal_Stm32wb_Flash_Erase whal_Flash_Erase
+#endif /* WHAL_CFG_STM32WB_FLASH_DIRECT_API_MAPPING */
+
+whal_Error whal_Stm32wb_Flash_Init(whal_Flash *flashDev)
+{
+ if (!flashDev) {
+ return WHAL_EINVAL;
+ }
+
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_Stm32wb_Flash_Deinit(whal_Flash *flashDev)
+{
+ if (!flashDev) {
+ return WHAL_EINVAL;
+ }
+
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_Stm32wb_Flash_Lock(whal_Flash *flashDev, size_t addr, size_t len)
+{
+ const whal_Regmap *regmap;
+
+ (void)addr;
+ (void)len;
+
+ if (!flashDev) {
+ return WHAL_EINVAL;
+ }
+
+ regmap = &flashDev->regmap;
+
+ /* Setting LOCK bit prevents further flash modifications until next unlock */
+ whal_Reg_Update(regmap->base, FLASH_CR_REG, FLASH_CR_LOCK_Msk,
+ whal_SetBits(FLASH_CR_LOCK_Msk, FLASH_CR_LOCK_Pos, 1));
+
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_Stm32wb_Flash_Unlock(whal_Flash *flashDev, size_t addr, size_t len)
+{
+ const whal_Regmap *regmap;
+
+ (void)addr;
+ (void)len;
+
+ if (!flashDev) {
+ return WHAL_EINVAL;
+ }
+
+ regmap = &flashDev->regmap;
+
+ /*
+ * Unlock sequence: write KEY1 then KEY2 to KEYR register.
+ * Incorrect sequence or order will trigger a bus error.
+ */
+ whal_Reg_Update(regmap->base, FLASH_KEYR_REG, FLASH_KEYR_KEY_Msk, 0x45670123);
+ whal_Reg_Update(regmap->base, FLASH_KEYR_REG, FLASH_KEYR_KEY_Msk, 0xCDEF89AB);
+
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_Stm32wb_Flash_Read(whal_Flash *flashDev, size_t addr, void *data,
+ size_t dataSz)
+{
+ whal_Stm32wb_Flash_Cfg *cfg;
+ uint8_t *dataBuf = (uint8_t *)data;
+
+ if (!flashDev || !flashDev->cfg || !data)
+ return WHAL_EINVAL;
+
+ if (dataSz == 0)
+ return WHAL_SUCCESS;
+
+ cfg = flashDev->cfg;
+
+ if (addr < cfg->startAddr || addr + dataSz > cfg->startAddr + cfg->size)
+ return WHAL_EINVAL;
+
+ /* Flash is memory-mapped, so reading is a simple memory copy */
+ uint8_t *flashAddr = (uint8_t *)addr;
+ for (size_t i = 0; i < dataSz; ++i) {
+ dataBuf[i] = flashAddr[i];
+ }
+ return WHAL_SUCCESS;
+}
+
+/*
+ * Internal helper for write and erase operations.
+ *
+ * For write (write=1): Programs data in 64-bit (8 byte) chunks.
+ * For erase (write=0): Erases 4 KB pages covering the address range.
+ */
+static whal_Error whal_Stm32wb_Flash_WriteOrErase(whal_Flash *flashDev, size_t addr, const uint8_t *data,
+ size_t dataSz, uint8_t write)
+{
+ whal_Stm32wb_Flash_Cfg *cfg;
+ const whal_Regmap *regmap;
+ size_t bsy;
+ size_t pesd;
+
+ if (!flashDev || !flashDev->cfg) {
+ return WHAL_EINVAL;
+ }
+
+ cfg = flashDev->cfg;
+ regmap = &flashDev->regmap;
+
+ if (dataSz == 0)
+ return WHAL_SUCCESS;
+
+ /* Validate address alignment and bounds */
+ if (addr & 0x7 || addr < cfg->startAddr || addr + dataSz > cfg->startAddr + cfg->size ||
+ (write && (data == NULL || (dataSz & 0x7)))) {
+ return WHAL_EINVAL;
+ }
+
+ /* Check if flash is busy or suspended */
+ whal_Reg_Get(regmap->base, FLASH_SR_REG, FLASH_SR_BSY_Msk, FLASH_SR_BSY_Pos, &bsy);
+ whal_Reg_Get(regmap->base, FLASH_SR_REG, FLASH_SR_PESD_Msk, FLASH_SR_PESD_Pos, &pesd);
+
+ if (bsy || pesd) {
+ return WHAL_ENOTREADY;
+ }
+
+ /* Clear all error flags by writing 1 to each */
+ whal_Reg_Update(regmap->base, FLASH_SR_REG, FLASH_SR_ALL_ERR, 0xffffffff);
+
+ whal_Error err = WHAL_SUCCESS;
+
+ if (write) {
+ /* Enable flash programming mode */
+ whal_Reg_Update(regmap->base, FLASH_CR_REG, FLASH_CR_PG_Msk, 1);
+
+ /* Program data in 64-bit (8 byte) double-word chunks */
+ for (size_t i = 0; i < dataSz; i += 8) {
+ uint32_t *flashAddr = (uint32_t *)(addr + i);
+ uint32_t *dataAddr = (uint32_t *)(data + i);
+
+ /* Write both 32-bit words to trigger the 64-bit programming */
+ flashAddr[0] = dataAddr[0];
+ flashAddr[1] = dataAddr[1];
+
+ /* Wait for programming to complete */
+ err = whal_Reg_ReadPoll(regmap->base, FLASH_SR_REG,
+ FLASH_SR_CFGBSY_Msk, 0, cfg->timeout);
+ if (err)
+ goto cleanup;
+ }
+ }
+ else {
+ /* Calculate page range to erase (4 KB per page) */
+ size_t startPage, endPage;
+ startPage = (addr - cfg->startAddr) >> 12;
+ endPage = ((addr - cfg->startAddr) + dataSz - 1) >> 12;
+
+ /* Enable page erase mode */
+ whal_Reg_Update(regmap->base, FLASH_CR_REG, FLASH_CR_PER_Msk,
+ whal_SetBits(FLASH_CR_PER_Msk, FLASH_CR_PER_Pos, 1));
+
+ /* Erase each page in the range */
+ for (size_t page = startPage; page <= endPage; ++page) {
+ /* Select page number */
+ whal_Reg_Update(regmap->base, FLASH_CR_REG, FLASH_CR_PNB_Msk,
+ whal_SetBits(FLASH_CR_PNB_Msk, FLASH_CR_PNB_Pos, page));
+
+ /* Start erase operation */
+ whal_Reg_Update(regmap->base, FLASH_CR_REG, FLASH_CR_STRT_Msk,
+ whal_SetBits(FLASH_CR_STRT_Msk, FLASH_CR_STRT_Pos, 1));
+
+ /* Wait for erase to complete */
+ err = whal_Reg_ReadPoll(regmap->base, FLASH_SR_REG,
+ FLASH_SR_CFGBSY_Msk, 0, cfg->timeout);
+ if (err)
+ goto cleanup;
+ }
+
+ /* Disable page erase mode */
+ whal_Reg_Update(regmap->base, FLASH_CR_REG, FLASH_CR_PER_Msk,
+ whal_SetBits(FLASH_CR_PER_Msk, FLASH_CR_PER_Pos, 0));
+ }
+
+cleanup:
+ /* Disable flash programming mode */
+ whal_Reg_Update(regmap->base, FLASH_CR_REG, FLASH_CR_PG_Msk, 0);
+
+ return err;
+}
+
+whal_Error whal_Stm32wb_Flash_Write(whal_Flash *flashDev, size_t addr, const void *data,
+ size_t dataSz)
+{
+ return whal_Stm32wb_Flash_WriteOrErase(flashDev, addr, (const uint8_t *)data, dataSz, 1);
+}
+
+whal_Error whal_Stm32wb_Flash_Erase(whal_Flash *flashDev, size_t addr,
+ size_t dataSz)
+{
+ return whal_Stm32wb_Flash_WriteOrErase(flashDev, addr, NULL, dataSz, 0);
+}
+
+whal_Error whal_Stm32wb_Flash_Ext_SetLatency(whal_Flash *flashDev, enum whal_Stm32wb_Flash_Latency latency)
+{
+ if (!flashDev) {
+ return WHAL_EINVAL;
+ }
+
+ const whal_Regmap *reg = &flashDev->regmap;
+ whal_Reg_Update(reg->base, FLASH_ACR_REG, FLASH_ACR_LATENCY_Msk, latency);
+ return WHAL_SUCCESS;
+}
+
+#ifndef WHAL_CFG_STM32WB_FLASH_DIRECT_API_MAPPING
+const whal_FlashDriver whal_Stm32wb_Flash_Driver = {
+ .Init = whal_Stm32wb_Flash_Init,
+ .Deinit = whal_Stm32wb_Flash_Deinit,
+ .Lock = whal_Stm32wb_Flash_Lock,
+ .Unlock = whal_Stm32wb_Flash_Unlock,
+ .Read = whal_Stm32wb_Flash_Read,
+ .Write = whal_Stm32wb_Flash_Write,
+ .Erase = whal_Stm32wb_Flash_Erase,
+};
+#endif /* !WHAL_CFG_STM32WB_FLASH_DIRECT_API_MAPPING */
diff --git a/src/gpio/gpio.c b/src/gpio/gpio.c
new file mode 100644
index 0000000..527b5cb
--- /dev/null
+++ b/src/gpio/gpio.c
@@ -0,0 +1,43 @@
+#include
+#include
+
+whal_Error whal_Gpio_Init(whal_Gpio *gpioDev)
+{
+ if (!gpioDev)
+ return WHAL_EINVAL;
+ if (!gpioDev->driver || !gpioDev->driver->Init)
+ return WHAL_ENOTSUP;
+
+ return gpioDev->driver->Init(gpioDev);
+}
+
+whal_Error whal_Gpio_Deinit(whal_Gpio *gpioDev)
+{
+ if (!gpioDev)
+ return WHAL_EINVAL;
+ if (!gpioDev->driver || !gpioDev->driver->Deinit)
+ return WHAL_ENOTSUP;
+
+ return gpioDev->driver->Deinit(gpioDev);
+}
+
+whal_Error whal_Gpio_Get(whal_Gpio *gpioDev, size_t pin, size_t *value)
+{
+ if (!gpioDev || !value)
+ return WHAL_EINVAL;
+ if (!gpioDev->driver || !gpioDev->driver->Get)
+ return WHAL_ENOTSUP;
+
+ return gpioDev->driver->Get(gpioDev, pin, value);
+}
+
+whal_Error whal_Gpio_Set(whal_Gpio *gpioDev, size_t pin, size_t value)
+{
+ if (!gpioDev)
+ return WHAL_EINVAL;
+ if (!gpioDev->driver || !gpioDev->driver->Set)
+ return WHAL_ENOTSUP;
+
+ return gpioDev->driver->Set(gpioDev, pin, value);
+}
+
diff --git a/src/gpio/stm32l1_gpio.c b/src/gpio/stm32l1_gpio.c
new file mode 100644
index 0000000..7ddf812
--- /dev/null
+++ b/src/gpio/stm32l1_gpio.c
@@ -0,0 +1 @@
+#include "stm32wb_gpio.c"
diff --git a/src/gpio/stm32wb_gpio.c b/src/gpio/stm32wb_gpio.c
new file mode 100644
index 0000000..9861c83
--- /dev/null
+++ b/src/gpio/stm32wb_gpio.c
@@ -0,0 +1,199 @@
+#include
+#include
+#include
+#include
+#include
+#include
+
+/*
+ * STM32WB GPIO Register Definitions
+ *
+ * Each GPIO port has a 0x400 byte register block. Register offsets
+ * are relative to the port base address.
+ */
+
+/* Size of each GPIO port register block */
+#define GPIO_PORT_SIZE 0x400
+
+/* Mode register - 2 bits per pin, selects input/output/altfn/analog */
+#define GPIO_MODE_REG 0x00
+/* Output type register - 1 bit per pin, push-pull or open-drain */
+#define GPIO_OUTTYPE_REG 0x04
+/* Output speed register - 2 bits per pin */
+#define GPIO_SPEED_REG 0x08
+/* Pull-up/pull-down register - 2 bits per pin */
+#define GPIO_PULL_REG 0x0C
+/* Input data register - read-only, 1 bit per pin */
+#define GPIO_IDR_REG 0x10
+/* Output data register - 1 bit per pin */
+#define GPIO_ODR_REG 0x14
+/* Alternate function low register - 4 bits per pin for pins 0-7 */
+#define GPIO_ALTFNL_REG 0x20
+/* Alternate function high register - 4 bits per pin for pins 8-15 */
+#define GPIO_ALTFNH_REG 0x24
+
+#if defined(WHAL_CFG_STM32WB_GPIO_DIRECT_API_MAPPING) || \
+ defined(WHAL_CFG_STM32F4_GPIO_DIRECT_API_MAPPING) || \
+ defined(WHAL_CFG_STM32H5_GPIO_DIRECT_API_MAPPING) || \
+ defined(WHAL_CFG_STM32C0_GPIO_DIRECT_API_MAPPING) || \
+ defined(WHAL_CFG_STM32F0_GPIO_DIRECT_API_MAPPING) || \
+ defined(WHAL_CFG_STM32F3_GPIO_DIRECT_API_MAPPING) || \
+ defined(WHAL_CFG_STM32L1_GPIO_DIRECT_API_MAPPING) || \
+ defined(WHAL_CFG_STM32N6_GPIO_DIRECT_API_MAPPING)
+#define whal_Stm32wb_Gpio_Init whal_Gpio_Init
+#define whal_Stm32wb_Gpio_Deinit whal_Gpio_Deinit
+#define whal_Stm32wb_Gpio_Get whal_Gpio_Get
+#define whal_Stm32wb_Gpio_Set whal_Gpio_Set
+#endif /* WHAL_CFG_GPIO_API_MAPPING */
+
+/*
+ * Configure alternate function for a pin.
+ *
+ * The AFRL (pins 0-7) and AFRH (pins 8-15) registers are combined into
+ * a 64-bit value for easier manipulation. Each pin uses 4 bits to select
+ * one of 16 alternate functions (AF0-AF15).
+ */
+/*
+ * Initialize a single GPIO pin with the specified configuration.
+ */
+static inline whal_Error whal_Stm32wb_Gpio_InitPin(whal_Gpio *gpioDev,
+ whal_Stm32wb_Gpio_PinCfg cfg)
+{
+ uint8_t pin = WHAL_STM32WB_GPIO_GET_PIN(cfg);
+ size_t portBase;
+ uint8_t pos2;
+ size_t mask2, mask1;
+
+ if (pin > 15)
+ return WHAL_EINVAL;
+
+ portBase = gpioDev->regmap.base +
+ (WHAL_STM32WB_GPIO_GET_PORT(cfg) * GPIO_PORT_SIZE);
+ pos2 = pin << 1;
+ mask2 = WHAL_BITMASK(2) << pos2;
+ mask1 = 1UL << pin;
+
+ whal_Reg_Update(portBase, GPIO_MODE_REG, mask2,
+ WHAL_STM32WB_GPIO_GET_MODE(cfg) << pos2);
+
+ whal_Reg_Update(portBase, GPIO_SPEED_REG, mask2,
+ WHAL_STM32WB_GPIO_GET_SPEED(cfg) << pos2);
+
+ whal_Reg_Update(portBase, GPIO_PULL_REG, mask2,
+ WHAL_STM32WB_GPIO_GET_PULL(cfg) << pos2);
+
+ whal_Reg_Update(portBase, GPIO_OUTTYPE_REG, mask1,
+ WHAL_STM32WB_GPIO_GET_OUTTYPE(cfg) << pin);
+
+ if (WHAL_STM32WB_GPIO_GET_MODE(cfg) == WHAL_STM32WB_GPIO_MODE_ALTFN) {
+ uint8_t afPos = (pin & 7) << 2;
+
+ whal_Reg_Update(portBase,
+ (pin < 8) ? GPIO_ALTFNL_REG : GPIO_ALTFNH_REG,
+ WHAL_BITMASK(4) << afPos,
+ WHAL_STM32WB_GPIO_GET_ALTFN(cfg) << afPos);
+ }
+
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_Stm32wb_Gpio_Init(whal_Gpio *gpioDev)
+{
+ whal_Error err;
+ whal_Stm32wb_Gpio_Cfg *cfg;
+ whal_Stm32wb_Gpio_PinCfg *pinCfg;
+
+ if (!gpioDev || !gpioDev->cfg) {
+ return WHAL_EINVAL;
+ }
+
+ cfg = (whal_Stm32wb_Gpio_Cfg *)gpioDev->cfg;
+ pinCfg = cfg->pinCfg;
+
+ /* Initialize each pin in the configuration array */
+ for (size_t pin = 0; pin < cfg->pinCount; ++pin) {
+ err = whal_Stm32wb_Gpio_InitPin(gpioDev, pinCfg[pin]);
+ if (err) {
+ return err;
+ }
+ }
+
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_Stm32wb_Gpio_Deinit(whal_Gpio *gpioDev)
+{
+ if (!gpioDev) {
+ return WHAL_EINVAL;
+ }
+
+ return WHAL_SUCCESS;
+}
+
+/*
+ * Shared helper for reading or writing GPIO pin values.
+ *
+ * For set operations (set=1): writes value to ODR register
+ * For get operations (set=0): reads value from IDR register
+ */
+static whal_Error whal_Stm32wb_Gpio_SetOrGet(whal_Gpio *gpioDev, size_t idx,
+ size_t *value, uint8_t set)
+{
+ whal_Stm32wb_Gpio_Cfg *cfg;
+ whal_Stm32wb_Gpio_PinCfg pinCfg;
+ uint8_t port, pin;
+ size_t portBase, mask;
+
+ if (!gpioDev || !gpioDev->cfg || !value) {
+ return WHAL_EINVAL;
+ }
+
+ cfg = (whal_Stm32wb_Gpio_Cfg *)gpioDev->cfg;
+ if (idx >= cfg->pinCount) {
+ return WHAL_EINVAL;
+ }
+ pinCfg = cfg->pinCfg[idx];
+ port = WHAL_STM32WB_GPIO_GET_PORT(pinCfg);
+ pin = WHAL_STM32WB_GPIO_GET_PIN(pinCfg);
+
+ if (pin > 15) {
+ return WHAL_EINVAL;
+ }
+
+ portBase = gpioDev->regmap.base + (port * GPIO_PORT_SIZE);
+ mask = 1UL << pin;
+
+ if (set) {
+ whal_Reg_Update(portBase, GPIO_ODR_REG, mask,
+ whal_SetBits(mask, pin, *value));
+ } else {
+ whal_Reg_Get(portBase, GPIO_IDR_REG, mask, pin, value);
+ }
+
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_Stm32wb_Gpio_Get(whal_Gpio *gpioDev, size_t pin, size_t *value)
+{
+ return whal_Stm32wb_Gpio_SetOrGet(gpioDev, pin, value, 0);
+}
+
+whal_Error whal_Stm32wb_Gpio_Set(whal_Gpio *gpioDev, size_t pin, size_t value)
+{
+ return whal_Stm32wb_Gpio_SetOrGet(gpioDev, pin, &value, 1);
+}
+
+#if !defined(WHAL_CFG_STM32WB_GPIO_DIRECT_API_MAPPING) && \
+ !defined(WHAL_CFG_STM32F4_GPIO_DIRECT_API_MAPPING) && \
+ !defined(WHAL_CFG_STM32H5_GPIO_DIRECT_API_MAPPING) && \
+ !defined(WHAL_CFG_STM32C0_GPIO_DIRECT_API_MAPPING) && \
+ !defined(WHAL_CFG_STM32F0_GPIO_DIRECT_API_MAPPING) && \
+ !defined(WHAL_CFG_STM32F3_GPIO_DIRECT_API_MAPPING) && \
+ !defined(WHAL_CFG_STM32L1_GPIO_DIRECT_API_MAPPING)
+const whal_GpioDriver whal_Stm32wb_Gpio_Driver = {
+ .Init = whal_Stm32wb_Gpio_Init,
+ .Deinit = whal_Stm32wb_Gpio_Deinit,
+ .Get = whal_Stm32wb_Gpio_Get,
+ .Set = whal_Stm32wb_Gpio_Set,
+};
+#endif /* !WHAL_CFG_GPIO_API_MAPPING */
diff --git a/src/irq/cortex_m_nvic.c b/src/irq/cortex_m_nvic.c
new file mode 100644
index 0000000..84813af
--- /dev/null
+++ b/src/irq/cortex_m_nvic.c
@@ -0,0 +1,86 @@
+#include
+#include
+#include
+
+/*
+ * ARM Cortex-M NVIC register offsets (relative to 0xE000E100).
+ *
+ * ISER: Interrupt Set-Enable Registers (0x000-0x01C, 32 IRQs per register)
+ * ICER: Interrupt Clear-Enable Registers (0x080-0x09C)
+ * IPR: Interrupt Priority Registers (0x300-0x37F, 4 IRQs per register)
+ */
+#define NVIC_ISER_REG(irq) (0x000 + (((irq) >> 5) << 2))
+#define NVIC_ICER_REG(irq) (0x080 + (((irq) >> 5) << 2))
+#define NVIC_IPR_REG(irq) (0x300 + (((irq) >> 2) << 2))
+#define NVIC_IPR_SHIFT(irq) (((irq) & 0x3) << 3)
+
+#ifdef WHAL_CFG_NVIC_IRQ_DIRECT_API_MAPPING
+#define whal_Nvic_Init whal_Irq_Init
+#define whal_Nvic_Deinit whal_Irq_Deinit
+#define whal_Nvic_Enable whal_Irq_Enable
+#define whal_Nvic_Disable whal_Irq_Disable
+#endif /* WHAL_CFG_NVIC_IRQ_DIRECT_API_MAPPING */
+
+whal_Error whal_Nvic_Init(whal_Irq *irqDev)
+{
+ if (!irqDev) {
+ return WHAL_EINVAL;
+ }
+
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_Nvic_Deinit(whal_Irq *irqDev)
+{
+ if (!irqDev) {
+ return WHAL_EINVAL;
+ }
+
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_Nvic_Enable(whal_Irq *irqDev, size_t irq,
+ const void *irqCfg)
+{
+ if (!irqDev) {
+ return WHAL_EINVAL;
+ }
+
+ size_t base = irqDev->regmap.base;
+
+ /* Set priority if config provided */
+ if (irqCfg) {
+ const whal_Nvic_Cfg *cfg = (const whal_Nvic_Cfg *)irqCfg;
+ size_t shift = NVIC_IPR_SHIFT(irq);
+ size_t mask = (0xFFUL << shift);
+ whal_Reg_Update(base, NVIC_IPR_REG(irq), mask,
+ (size_t)(cfg->priority << 4) << shift);
+ }
+
+ /* Enable the interrupt */
+ whal_Reg_Write(base, NVIC_ISER_REG(irq), (1UL << (irq & 0x1F)));
+
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_Nvic_Disable(whal_Irq *irqDev, size_t irq)
+{
+ if (!irqDev) {
+ return WHAL_EINVAL;
+ }
+
+ size_t base = irqDev->regmap.base;
+
+ whal_Reg_Write(base, NVIC_ICER_REG(irq), (1UL << (irq & 0x1F)));
+
+ return WHAL_SUCCESS;
+}
+
+#ifndef WHAL_CFG_NVIC_IRQ_DIRECT_API_MAPPING
+const whal_IrqDriver whal_Nvic_Driver = {
+ .Init = whal_Nvic_Init,
+ .Deinit = whal_Nvic_Deinit,
+ .Enable = whal_Nvic_Enable,
+ .Disable = whal_Nvic_Disable,
+};
+#endif /* !WHAL_CFG_NVIC_IRQ_DIRECT_API_MAPPING */
diff --git a/src/power/stm32l1_pwr.c b/src/power/stm32l1_pwr.c
new file mode 100644
index 0000000..56befb1
--- /dev/null
+++ b/src/power/stm32l1_pwr.c
@@ -0,0 +1,33 @@
+#include
+#include
+#include
+#include
+
+#define PWR_CR_REG 0x00
+#define PWR_CR_VOS_Pos 11
+#define PWR_CR_VOS_Msk (WHAL_BITMASK(2) << PWR_CR_VOS_Pos)
+#define PWR_CSR_REG 0x04
+#define PWR_CSR_VOSF_Pos 4
+#define PWR_CSR_VOSF_Msk (1UL << PWR_CSR_VOSF_Pos)
+
+whal_Error whal_Stm32l1_Pwr_SetVosRange(whal_Power *powerDev,
+ whal_Stm32l1_Pwr_VosRange range,
+ whal_Timeout *timeout)
+{
+ size_t base;
+ whal_Error err;
+
+ if (!powerDev)
+ return WHAL_EINVAL;
+
+ base = powerDev->regmap.base;
+
+ err = whal_Reg_ReadPoll(base, PWR_CSR_REG, PWR_CSR_VOSF_Msk, 0, timeout);
+ if (err)
+ return err;
+
+ whal_Reg_Update(base, PWR_CR_REG, PWR_CR_VOS_Msk,
+ whal_SetBits(PWR_CR_VOS_Msk, PWR_CR_VOS_Pos, range));
+
+ return whal_Reg_ReadPoll(base, PWR_CSR_REG, PWR_CSR_VOSF_Msk, 0, timeout);
+}
diff --git a/src/timer/systick.c b/src/timer/systick.c
new file mode 100644
index 0000000..03cbd36
--- /dev/null
+++ b/src/timer/systick.c
@@ -0,0 +1,114 @@
+#include
+#include
+#include
+#include
+
+#define SYSTICK_CSR_REG 0x00
+#define SYSTICK_CSR_ENABLE_Pos 0
+#define SYSTICK_CSR_ENABLE_Msk (1UL << SYSTICK_CSR_ENABLE_Pos)
+
+#define SYSTICK_CSR_TICKINT_Pos 1
+#define SYSTICK_CSR_TICKINT_Msk (1UL << SYSTICK_CSR_TICKINT_Pos)
+
+#define SYSTICK_CSR_CLKSOURCE_Pos 2
+#define SYSTICK_CSR_CLKSOURCE_Msk (1UL << SYSTICK_CSR_CLKSOURCE_Pos)
+
+#define SYSTICK_CSR_COUNTFLAG_Pos 16
+#define SYSTICK_CSR_COUNTFLAG_Msk (1UL << SYSTICK_CSR_COUNTFLAG_Pos)
+
+#define SYSTICK_RVR_REG 0x04
+#define SYSTICK_RVR_RELOAD_Pos 0
+#define SYSTICK_RVR_RELOAD_Msk (WHAL_BITMASK(24) << SYSTICK_RVR_RELOAD_Pos)
+
+#ifdef WHAL_CFG_SYSTICK_TIMER_DIRECT_API_MAPPING
+#define whal_SysTick_Init whal_Timer_Init
+#define whal_SysTick_Deinit whal_Timer_Deinit
+#define whal_SysTick_Start whal_Timer_Start
+#define whal_SysTick_Stop whal_Timer_Stop
+#define whal_SysTick_Reset whal_Timer_Reset
+#endif /* WHAL_CFG_SYSTICK_TIMER_DIRECT_API_MAPPING */
+
+whal_Error whal_SysTick_Init(whal_Timer *timerDev)
+{
+ whal_SysTick_Cfg *cfg;
+ const whal_Regmap *reg;
+
+ if (!timerDev || !timerDev->cfg) {
+ return WHAL_EINVAL;
+ }
+
+ reg = &timerDev->regmap;
+
+ cfg = (whal_SysTick_Cfg *)timerDev->cfg;
+
+ whal_Reg_Update(reg->base, SYSTICK_CSR_REG,
+ SYSTICK_CSR_CLKSOURCE_Msk | SYSTICK_CSR_TICKINT_Msk,
+ whal_SetBits(SYSTICK_CSR_CLKSOURCE_Msk, SYSTICK_CSR_CLKSOURCE_Pos, cfg->clkSrc) |
+ whal_SetBits(SYSTICK_CSR_TICKINT_Msk, SYSTICK_CSR_TICKINT_Pos, cfg->tickInt));
+
+ whal_Reg_Update(reg->base, SYSTICK_RVR_REG,
+ SYSTICK_RVR_RELOAD_Msk,
+ whal_SetBits(SYSTICK_RVR_RELOAD_Msk, SYSTICK_RVR_RELOAD_Pos, cfg->cyclesPerTick));
+
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_SysTick_Deinit(whal_Timer *timerDev)
+{
+ if (!timerDev) {
+ return WHAL_EINVAL;
+ }
+
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_SysTick_Start(whal_Timer *timerDev)
+{
+ const whal_Regmap *reg;
+
+ if (!timerDev || !timerDev->cfg) {
+ return WHAL_EINVAL;
+ }
+
+ reg = &timerDev->regmap;
+
+ whal_Reg_Update(reg->base, SYSTICK_CSR_REG, SYSTICK_CSR_ENABLE_Msk,
+ whal_SetBits(SYSTICK_CSR_ENABLE_Msk, SYSTICK_CSR_ENABLE_Pos, 1));
+
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_SysTick_Stop(whal_Timer *timerDev)
+{
+ const whal_Regmap *reg;
+
+ if (!timerDev || !timerDev->cfg) {
+ return WHAL_EINVAL;
+ }
+
+ reg = &timerDev->regmap;
+
+ whal_Reg_Update(reg->base, SYSTICK_CSR_REG, SYSTICK_CSR_ENABLE_Msk,
+ whal_SetBits(SYSTICK_CSR_ENABLE_Msk, SYSTICK_CSR_ENABLE_Pos, 0));
+
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_SysTick_Reset(whal_Timer *timerDev)
+{
+ if (!timerDev) {
+ return WHAL_EINVAL;
+ }
+
+ return WHAL_SUCCESS;
+}
+
+#ifndef WHAL_CFG_SYSTICK_TIMER_DIRECT_API_MAPPING
+const whal_TimerDriver whal_SysTick_Driver = {
+ .Init = whal_SysTick_Init,
+ .Deinit = whal_SysTick_Deinit,
+ .Start = whal_SysTick_Start,
+ .Stop = whal_SysTick_Stop,
+ .Reset = whal_SysTick_Reset,
+};
+#endif /* !WHAL_CFG_SYSTICK_TIMER_DIRECT_API_MAPPING */
diff --git a/src/timer/timer.c b/src/timer/timer.c
new file mode 100644
index 0000000..cafec48
--- /dev/null
+++ b/src/timer/timer.c
@@ -0,0 +1,54 @@
+#include
+#include
+#include
+#include
+
+whal_Error whal_Timer_Init(whal_Timer *timerDev)
+{
+ if (!timerDev)
+ return WHAL_EINVAL;
+ if (!timerDev->driver || !timerDev->driver->Init)
+ return WHAL_ENOTSUP;
+
+ return timerDev->driver->Init(timerDev);
+}
+
+whal_Error whal_Timer_Deinit(whal_Timer *timerDev)
+{
+ if (!timerDev)
+ return WHAL_EINVAL;
+ if (!timerDev->driver || !timerDev->driver->Deinit)
+ return WHAL_ENOTSUP;
+
+ return timerDev->driver->Deinit(timerDev);
+}
+
+whal_Error whal_Timer_Start(whal_Timer *timerDev)
+{
+ if (!timerDev)
+ return WHAL_EINVAL;
+ if (!timerDev->driver || !timerDev->driver->Start)
+ return WHAL_ENOTSUP;
+
+ return timerDev->driver->Start(timerDev);
+}
+
+whal_Error whal_Timer_Stop(whal_Timer *timerDev)
+{
+ if (!timerDev)
+ return WHAL_EINVAL;
+ if (!timerDev->driver || !timerDev->driver->Stop)
+ return WHAL_ENOTSUP;
+
+ return timerDev->driver->Stop(timerDev);
+}
+
+whal_Error whal_Timer_Reset(whal_Timer *timerDev)
+{
+ if (!timerDev)
+ return WHAL_EINVAL;
+ if (!timerDev->driver || !timerDev->driver->Reset)
+ return WHAL_ENOTSUP;
+
+ return timerDev->driver->Reset(timerDev);
+}
diff --git a/src/uart/stm32f4_uart.c b/src/uart/stm32f4_uart.c
new file mode 100644
index 0000000..e59373b
--- /dev/null
+++ b/src/uart/stm32f4_uart.c
@@ -0,0 +1,196 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/*
+ * STM32F4 USART Register Definitions (USARTv1)
+ *
+ * The STM32F4 USART uses SR/DR style registers which differ from the
+ * ISR/TDR/RDR layout on newer STM32 families.
+ */
+
+/* Status Register */
+#define UART_SR_REG 0x00
+#define UART_SR_RXNE_Pos 5 /* Receive data register not empty */
+#define UART_SR_RXNE_Msk (1UL << UART_SR_RXNE_Pos)
+
+#define UART_SR_TC_Pos 6 /* Transmission complete */
+#define UART_SR_TC_Msk (1UL << UART_SR_TC_Pos)
+
+#define UART_SR_TXE_Pos 7 /* Transmit data register empty */
+#define UART_SR_TXE_Msk (1UL << UART_SR_TXE_Pos)
+
+/* Data Register - shared TX/RX */
+#define UART_DR_REG 0x04
+#define UART_DR_Pos 0
+#define UART_DR_Msk (WHAL_BITMASK(9) << UART_DR_Pos)
+
+/* Baud Rate Register */
+#define UART_BRR_REG 0x08
+#define UART_BRR_Pos 0
+#define UART_BRR_Msk (WHAL_BITMASK(16) << UART_BRR_Pos)
+
+/* Control Register 1 */
+#define UART_CR1_REG 0x0C
+#define UART_CR1_RE_Pos 2 /* Receiver enable */
+#define UART_CR1_RE_Msk (1UL << UART_CR1_RE_Pos)
+
+#define UART_CR1_TE_Pos 3 /* Transmitter enable */
+#define UART_CR1_TE_Msk (1UL << UART_CR1_TE_Pos)
+
+#define UART_CR1_UE_Pos 13 /* USART enable */
+#define UART_CR1_UE_Msk (1UL << UART_CR1_UE_Pos)
+
+#if defined(WHAL_CFG_STM32F4_UART_DIRECT_API_MAPPING) || \
+ defined(WHAL_CFG_STM32L1_UART_DIRECT_API_MAPPING)
+#define whal_Stm32f4_Uart_Init whal_Uart_Init
+#define whal_Stm32f4_Uart_Deinit whal_Uart_Deinit
+#define whal_Stm32f4_Uart_Send whal_Uart_Send
+#define whal_Stm32f4_Uart_Recv whal_Uart_Recv
+#define whal_Stm32f4_Uart_SendAsync whal_Uart_SendAsync
+#define whal_Stm32f4_Uart_RecvAsync whal_Uart_RecvAsync
+#endif /* WHAL_CFG_STM32F4_UART_DIRECT_API_MAPPING */
+
+whal_Error whal_Stm32f4_Uart_Init(whal_Uart *uartDev)
+{
+ whal_Stm32f4_Uart_Cfg *cfg;
+ const whal_Regmap *reg;
+ uint32_t brr;
+
+ if (!uartDev || !uartDev->cfg)
+ return WHAL_EINVAL;
+
+ reg = &uartDev->regmap;
+ cfg = (whal_Stm32f4_Uart_Cfg *)uartDev->cfg;
+
+ brr = cfg->brr;
+
+ /* Set baud rate */
+ whal_Reg_Update(reg->base, UART_BRR_REG,
+ UART_BRR_Msk,
+ whal_SetBits(UART_BRR_Msk, UART_BRR_Pos, brr));
+
+ /* Enable USART, transmitter, and receiver */
+ whal_Reg_Update(reg->base, UART_CR1_REG,
+ UART_CR1_UE_Msk | UART_CR1_RE_Msk | UART_CR1_TE_Msk,
+ whal_SetBits(UART_CR1_UE_Msk, UART_CR1_UE_Pos, 1) |
+ whal_SetBits(UART_CR1_RE_Msk, UART_CR1_RE_Pos, 1) |
+ whal_SetBits(UART_CR1_TE_Msk, UART_CR1_TE_Pos, 1));
+
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_Stm32f4_Uart_Deinit(whal_Uart *uartDev)
+{
+ const whal_Regmap *reg;
+
+ if (!uartDev)
+ return WHAL_EINVAL;
+
+ reg = &uartDev->regmap;
+
+ /* Disable USART, transmitter, and receiver */
+ whal_Reg_Update(reg->base, UART_CR1_REG,
+ UART_CR1_UE_Msk | UART_CR1_RE_Msk | UART_CR1_TE_Msk,
+ whal_SetBits(UART_CR1_UE_Msk, UART_CR1_UE_Pos, 0) |
+ whal_SetBits(UART_CR1_RE_Msk, UART_CR1_RE_Pos, 0) |
+ whal_SetBits(UART_CR1_TE_Msk, UART_CR1_TE_Pos, 0));
+
+ /* Clear baud rate */
+ whal_Reg_Update(reg->base, UART_BRR_REG,
+ UART_BRR_Msk,
+ whal_SetBits(UART_BRR_Msk, UART_BRR_Pos, 0));
+
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_Stm32f4_Uart_Send(whal_Uart *uartDev, const void *data, size_t dataSz)
+{
+ const whal_Regmap *reg;
+ whal_Stm32f4_Uart_Cfg *cfg;
+ const uint8_t *buf = data;
+
+ if (!uartDev || !uartDev->cfg || !data)
+ return WHAL_EINVAL;
+
+ reg = &uartDev->regmap;
+ cfg = (whal_Stm32f4_Uart_Cfg *)uartDev->cfg;
+
+ for (size_t i = 0; i < dataSz; ++i) {
+ whal_Error err;
+
+ /* Wait for transmit data register empty */
+ err = whal_Reg_ReadPoll(reg->base, UART_SR_REG, UART_SR_TXE_Msk,
+ UART_SR_TXE_Msk, cfg->timeout);
+ if (err)
+ return err;
+
+ /* Write byte to data register (must not read DR — use pure write) */
+ whal_Reg_Write(reg->base, UART_DR_REG, buf[i]);
+ }
+
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_Stm32f4_Uart_Recv(whal_Uart *uartDev, void *data, size_t dataSz)
+{
+ const whal_Regmap *reg;
+ whal_Stm32f4_Uart_Cfg *cfg;
+ uint8_t *buf = data;
+
+ if (!uartDev || !uartDev->cfg || !data)
+ return WHAL_EINVAL;
+
+ reg = &uartDev->regmap;
+ cfg = (whal_Stm32f4_Uart_Cfg *)uartDev->cfg;
+ size_t d;
+
+ for (size_t i = 0; i < dataSz; ++i) {
+ /* Wait for receive data register not empty */
+ whal_Error err = whal_Reg_ReadPoll(reg->base, UART_SR_REG,
+ UART_SR_RXNE_Msk,
+ UART_SR_RXNE_Msk, cfg->timeout);
+ if (err)
+ return err;
+
+ /* Read received byte */
+ whal_Reg_Get(reg->base, UART_DR_REG,
+ UART_DR_Msk, UART_DR_Pos, &d);
+
+ buf[i] = d;
+ }
+
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_Stm32f4_Uart_SendAsync(whal_Uart *uartDev, const void *data, size_t dataSz)
+{
+ (void)dataSz;
+ if (!uartDev || !data)
+ return WHAL_EINVAL;
+ return WHAL_ENOTSUP;
+}
+
+whal_Error whal_Stm32f4_Uart_RecvAsync(whal_Uart *uartDev, void *data, size_t dataSz)
+{
+ (void)dataSz;
+ if (!uartDev || !data)
+ return WHAL_EINVAL;
+ return WHAL_ENOTSUP;
+}
+
+#if !defined(WHAL_CFG_STM32F4_UART_DIRECT_API_MAPPING) && \
+ !defined(WHAL_CFG_STM32L1_UART_DIRECT_API_MAPPING)
+const whal_UartDriver whal_Stm32f4_Uart_Driver = {
+ .Init = whal_Stm32f4_Uart_Init,
+ .Deinit = whal_Stm32f4_Uart_Deinit,
+ .Send = whal_Stm32f4_Uart_Send,
+ .SendAsync = whal_Stm32f4_Uart_SendAsync,
+ .RecvAsync = whal_Stm32f4_Uart_RecvAsync,
+ .Recv = whal_Stm32f4_Uart_Recv,
+};
+#endif /* !WHAL_CFG_UART_API_MAPPING */
diff --git a/src/uart/stm32l1_uart.c b/src/uart/stm32l1_uart.c
new file mode 100644
index 0000000..545287d
--- /dev/null
+++ b/src/uart/stm32l1_uart.c
@@ -0,0 +1 @@
+#include "stm32f4_uart.c"
diff --git a/src/uart/stm32wb_uart.c b/src/uart/stm32wb_uart.c
new file mode 100644
index 0000000..2072c8c
--- /dev/null
+++ b/src/uart/stm32wb_uart.c
@@ -0,0 +1,200 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define UART_CR1_REG 0x00
+#define UART_CR1_UE_Pos 0
+#define UART_CR1_UE_Msk (1UL << UART_CR1_UE_Pos)
+
+#define UART_CR1_RE_Pos 2
+#define UART_CR1_RE_Msk (1UL << UART_CR1_RE_Pos)
+
+#define UART_CR1_TE_Pos 3
+#define UART_CR1_TE_Msk (1UL << UART_CR1_TE_Pos)
+
+#define UART_CR1_FIFOEN_Pos 29
+#define UART_CR1_FIFOEN_Msk (1UL << UART_CR1_FIFOEN_Pos)
+
+#define UART_BRR_REG 0x0C
+#define UART_BRR_Pos 0
+#define UART_BRR_Msk (WHAL_BITMASK(20) << UART_BRR_Pos)
+
+#define UART_ISR_REG 0x1C
+#define UART_ISR_RXFNE_Pos 5
+#define UART_ISR_RXFNE_Msk (1UL << UART_ISR_RXFNE_Pos)
+
+#define UART_ISR_TC_Pos 6
+#define UART_ISR_TC_Msk (1UL << UART_ISR_TC_Pos)
+
+#define UART_ISR_TXE_Pos 7
+#define UART_ISR_TXE_Msk (1UL << UART_ISR_TXE_Pos)
+
+#define UART_RDR_REG 0x24
+#define UART_RDR_Pos 0
+#define UART_RDR_Msk (WHAL_BITMASK(9) << UART_RDR_Pos)
+
+#define UART_TDR_REG 0x28
+#define UART_TDR_Pos 0
+#define UART_TDR_Msk (WHAL_BITMASK(9) << UART_TDR_Pos)
+
+#if defined(WHAL_CFG_STM32WB_UART_DIRECT_API_MAPPING) || \
+ defined(WHAL_CFG_STM32H5_UART_DIRECT_API_MAPPING) || \
+ defined(WHAL_CFG_STM32C0_UART_DIRECT_API_MAPPING) || \
+ defined(WHAL_CFG_STM32N6_UART_DIRECT_API_MAPPING)
+#define whal_Stm32wb_Uart_Init whal_Uart_Init
+#define whal_Stm32wb_Uart_Deinit whal_Uart_Deinit
+#define whal_Stm32wb_Uart_Send whal_Uart_Send
+#define whal_Stm32wb_Uart_Recv whal_Uart_Recv
+#define whal_Stm32wb_Uart_SendAsync whal_Uart_SendAsync
+#define whal_Stm32wb_Uart_RecvAsync whal_Uart_RecvAsync
+#endif /* WHAL_CFG_UART_API_MAPPING */
+
+#ifdef WHAL_CFG_STM32WB_UART_DMA_DIRECT_API_MAPPING
+#define whal_Stm32wb_Uart_Init whal_Uart_Init
+#define whal_Stm32wb_Uart_Deinit whal_Uart_Deinit
+#endif /* WHAL_CFG_STM32WB_UART_DMA_DIRECT_API_MAPPING */
+
+whal_Error whal_Stm32wb_Uart_Init(whal_Uart *uartDev)
+{
+ whal_Stm32wb_Uart_Cfg *cfg;
+ const whal_Regmap *reg;
+ uint32_t brr;
+
+ if (!uartDev || !uartDev->cfg) {
+ return WHAL_EINVAL;
+ }
+
+ reg = &uartDev->regmap;
+ cfg = (whal_Stm32wb_Uart_Cfg *)uartDev->cfg;
+
+ brr = cfg->brr;
+
+ whal_Reg_Update(reg->base, UART_BRR_REG,
+ UART_BRR_Msk,
+ whal_SetBits(UART_BRR_Msk, UART_BRR_Pos, brr));
+ whal_Reg_Update(reg->base, UART_CR1_REG,
+ UART_CR1_UE_Msk | UART_CR1_RE_Msk | UART_CR1_TE_Msk |
+ UART_CR1_FIFOEN_Msk,
+ whal_SetBits(UART_CR1_UE_Msk, UART_CR1_UE_Pos, 1) |
+ whal_SetBits(UART_CR1_RE_Msk, UART_CR1_RE_Pos, 1) |
+ whal_SetBits(UART_CR1_TE_Msk, UART_CR1_TE_Pos, 1) |
+ whal_SetBits(UART_CR1_FIFOEN_Msk, UART_CR1_FIFOEN_Pos, 1));
+
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_Stm32wb_Uart_Deinit(whal_Uart *uartDev)
+{
+ const whal_Regmap *reg;
+
+ if (!uartDev) {
+ return WHAL_EINVAL;
+ }
+
+ reg = &uartDev->regmap;
+
+ whal_Reg_Update(reg->base, UART_CR1_REG,
+ UART_CR1_UE_Msk | UART_CR1_RE_Msk | UART_CR1_TE_Msk,
+ whal_SetBits(UART_CR1_UE_Msk, UART_CR1_UE_Pos, 0) |
+ whal_SetBits(UART_CR1_RE_Msk, UART_CR1_RE_Pos, 0) |
+ whal_SetBits(UART_CR1_TE_Msk, UART_CR1_TE_Pos, 0));
+
+ whal_Reg_Update(reg->base, UART_BRR_REG,
+ UART_BRR_Msk,
+ whal_SetBits(UART_BRR_Msk, UART_BRR_Pos, 0));
+
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_Stm32wb_Uart_Send(whal_Uart *uartDev, const void *data, size_t dataSz)
+{
+ const whal_Regmap *reg;
+ whal_Stm32wb_Uart_Cfg *cfg;
+ const uint8_t *buf = data;
+
+ if (!uartDev || !uartDev->cfg || !data) {
+ return WHAL_EINVAL;
+ }
+
+ reg = &uartDev->regmap;
+ cfg = (whal_Stm32wb_Uart_Cfg *)uartDev->cfg;
+
+ for (size_t i = 0; i < dataSz; ++i) {
+ whal_Error err;
+ whal_Reg_Update(reg->base, UART_TDR_REG, UART_TDR_Msk,
+ whal_SetBits(UART_TDR_Msk, UART_TDR_Pos, buf[i]));
+
+ err = whal_Reg_ReadPoll(reg->base, UART_ISR_REG, UART_ISR_TC_Msk,
+ UART_ISR_TC_Msk, cfg->timeout);
+ if (err)
+ return err;
+ }
+
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_Stm32wb_Uart_Recv(whal_Uart *uartDev, void *data, size_t dataSz)
+{
+ const whal_Regmap *reg;
+ whal_Stm32wb_Uart_Cfg *cfg;
+ uint8_t *buf = data;
+
+ if (!uartDev || !uartDev->cfg || !data) {
+ return WHAL_EINVAL;
+ }
+
+ reg = &uartDev->regmap;
+ cfg = (whal_Stm32wb_Uart_Cfg *)uartDev->cfg;
+ size_t d;
+
+ for (size_t i = 0; i < dataSz; ++i) {
+ whal_Error err = whal_Reg_ReadPoll(reg->base, UART_ISR_REG,
+ UART_ISR_RXFNE_Msk,
+ UART_ISR_RXFNE_Msk, cfg->timeout);
+ if (err)
+ return err;
+
+ whal_Reg_Get(reg->base, UART_RDR_REG,
+ UART_RDR_Msk, UART_RDR_Pos, &d);
+
+ buf[i] = d;
+ }
+
+ return WHAL_SUCCESS;
+}
+
+whal_Error whal_Stm32wb_Uart_SendAsync(whal_Uart *uartDev, const void *data, size_t dataSz)
+{
+ (void)dataSz;
+ if (!uartDev || !data)
+ return WHAL_EINVAL;
+ return WHAL_ENOTSUP;
+}
+
+whal_Error whal_Stm32wb_Uart_RecvAsync(whal_Uart *uartDev, void *data, size_t dataSz)
+{
+ (void)dataSz;
+ if (!uartDev || !data)
+ return WHAL_EINVAL;
+ return WHAL_ENOTSUP;
+}
+
+#if !defined(WHAL_CFG_STM32WB_UART_DIRECT_API_MAPPING) && \
+ !defined(WHAL_CFG_STM32WB_UART_DMA_DIRECT_API_MAPPING) && \
+ !defined(WHAL_CFG_STM32H5_UART_DIRECT_API_MAPPING) && \
+ !defined(WHAL_CFG_STM32C0_UART_DIRECT_API_MAPPING) && \
+ !defined(WHAL_CFG_STM32N6_UART_DIRECT_API_MAPPING)
+const whal_UartDriver whal_Stm32wb_Uart_Driver = {
+ .Init = whal_Stm32wb_Uart_Init,
+ .Deinit = whal_Stm32wb_Uart_Deinit,
+ .Send = whal_Stm32wb_Uart_Send,
+ .Recv = whal_Stm32wb_Uart_Recv,
+ .SendAsync = whal_Stm32wb_Uart_SendAsync,
+ .RecvAsync = whal_Stm32wb_Uart_RecvAsync,
+};
+#endif /* !WHAL_CFG_UART_API_MAPPING */
+
diff --git a/src/uart/uart.c b/src/uart/uart.c
new file mode 100644
index 0000000..1d06dda
--- /dev/null
+++ b/src/uart/uart.c
@@ -0,0 +1,64 @@
+#include
+#include
+#include
+
+whal_Error whal_Uart_Init(whal_Uart *uartDev)
+{
+ if (!uartDev)
+ return WHAL_EINVAL;
+ if (!uartDev->driver || !uartDev->driver->Init)
+ return WHAL_ENOTSUP;
+
+ return uartDev->driver->Init(uartDev);
+}
+
+whal_Error whal_Uart_Deinit(whal_Uart *uartDev)
+{
+ if (!uartDev)
+ return WHAL_EINVAL;
+ if (!uartDev->driver || !uartDev->driver->Deinit)
+ return WHAL_ENOTSUP;
+
+ return uartDev->driver->Deinit(uartDev);
+}
+
+whal_Error whal_Uart_Send(whal_Uart *uartDev, const void *data, size_t dataSz)
+{
+ if (!uartDev || !data)
+ return WHAL_EINVAL;
+ if (!uartDev->driver || !uartDev->driver->Send)
+ return WHAL_ENOTSUP;
+
+ return uartDev->driver->Send(uartDev, data, dataSz);
+}
+
+whal_Error whal_Uart_Recv(whal_Uart *uartDev, void *data, size_t dataSz)
+{
+ if (!uartDev || !data)
+ return WHAL_EINVAL;
+ if (!uartDev->driver || !uartDev->driver->Recv)
+ return WHAL_ENOTSUP;
+
+ return uartDev->driver->Recv(uartDev, data, dataSz);
+}
+
+whal_Error whal_Uart_SendAsync(whal_Uart *uartDev, const void *data, size_t dataSz)
+{
+ if (!uartDev || !data)
+ return WHAL_EINVAL;
+ if (!uartDev->driver || !uartDev->driver->SendAsync)
+ return WHAL_ENOTSUP;
+
+ return uartDev->driver->SendAsync(uartDev, data, dataSz);
+}
+
+whal_Error whal_Uart_RecvAsync(whal_Uart *uartDev, void *data, size_t dataSz)
+{
+ if (!uartDev || !data)
+ return WHAL_EINVAL;
+ if (!uartDev->driver || !uartDev->driver->RecvAsync)
+ return WHAL_ENOTSUP;
+
+ return uartDev->driver->RecvAsync(uartDev, data, dataSz);
+}
+
diff --git a/tests/Makefile b/tests/Makefile
new file mode 100644
index 0000000..04eefdd
--- /dev/null
+++ b/tests/Makefile
@@ -0,0 +1,43 @@
+WHAL_DIR = $(CURDIR)/..
+
+.DEFAULT_GOAL := all
+
+BOARD ?= stm32wb55xx_nucleo
+BOARD_DIR = $(WHAL_DIR)/boards/$(BOARD)
+BUILD_DIR = build/$(BOARD)
+PERIPHERAL_DIR = $(WHAL_DIR)/boards/peripheral
+INCLUDE = -I$(WHAL_DIR) -I. -I$(PERIPHERAL_DIR)
+
+include $(BOARD_DIR)/board.mk
+
+uc = $(shell echo $(1) | tr a-z A-Z)
+CFLAGS += $(foreach t,$(TESTS),-DWHAL_TEST_ENABLE_$(call uc,$(t)))
+CFLAGS += $(foreach t,$(TESTS),$(if $(shell find . -name 'test_$(PLATFORM)_$(t).c'),-DWHAL_TEST_ENABLE_$(call uc,$(t))_PLATFORM))
+
+SOURCE = main.c
+SOURCE += $(BOARD_SOURCE)
+SOURCE += $(foreach t,$(TESTS),$(shell find . -name 'test_$(t).c'))
+SOURCE += $(foreach t,$(TESTS),$(shell find . -name 'test_$(PLATFORM)_$(t).c'))
+
+OBJECTS = $(patsubst %.c,$(BUILD_DIR)/%.o,$(SOURCE))
+DEPENDS = $(OBJECTS:.o=.d)
+
+all: $(BUILD_DIR)/test_hw.bin
+
+$(BUILD_DIR)/%.o: %.c Makefile
+ @mkdir -p $(dir $@)
+ $(GCC) $(CFLAGS) -c -o $@ $<
+
+.SECONDARY:
+$(BUILD_DIR)/%.elf: $(OBJECTS) $(LINKER_SCRIPT)
+ @mkdir -p $(dir $@)
+ $(LD) $(LDFLAGS) -T $(LINKER_SCRIPT) -o $@ $(OBJECTS)
+
+$(BUILD_DIR)/%.bin: $(BUILD_DIR)/%.elf
+ $(OBJCOPY) $^ -O binary $@
+
+.PHONY: clean
+clean:
+ rm -rf build
+
+-include $(DEPENDS)
diff --git a/tests/README.md b/tests/README.md
new file mode 100644
index 0000000..df66151
--- /dev/null
+++ b/tests/README.md
@@ -0,0 +1,71 @@
+# wolfHAL Test Suite
+
+## Hardware Tests
+
+### Building
+
+```
+make BOARD=
+```
+
+Each board defines its supported tests in `boards//board.mk`. The output binary
+is placed in `build//`.
+
+### Selecting Tests
+
+By default, all supported tests for the specified board will be built. Override
+the `TESTS` variable to build a subset:
+
+```
+make BOARD=stm32wb55xx_nucleo TESTS=gpio
+make BOARD=stm32wb55xx_nucleo TESTS="gpio clock"
+```
+
+### Test Structure
+
+Tests are organized by device type:
+
+```
+tests/
+ main.c # Test runner entry point
+ test.h # Test macros (WHAL_ASSERT_EQ, WHAL_TEST, etc.)
+ Makefile
+ gpio/
+ test_gpio.c # Generic API tests
+ test__gpio.c # Platform-specific tests
+ clock/
+ ...
+```
+
+Each device directory contains:
+
+- `test_.c` — Generic tests that exercise the wolfHAL API.
+- `test__.c` — Platform-specific tests. These are
+ automatically detected and compiled when building for the matching board.
+
+Board support (device instances, linker scripts, etc.) lives in the top-level
+`boards/` directory. See [boards/README.md](../boards/README.md) for details.
+
+### Peripheral Devices
+
+External peripheral drivers (SPI-NOR flash, SD cards, IMUs, etc.) are opt-in.
+Enable them using the `PERIPHERALS` variable when building:
+
+```
+make BOARD=stm32wb55xx_nucleo PERIPHERALS="spi_nor_w25q64"
+make BOARD=stm32wb55xx_nucleo PERIPHERALS="bmi270" TESTS="bmi270"
+make BOARD=stm32wb55xx_nucleo PERIPHERALS="spi_nor_w25q64 bmi270"
+```
+
+Peripheral devices are automatically tested by their matching test suite (e.g.,
+`flash` tests iterate all entries in `g_peripheralFlash[]`, `bmi270` tests
+use `g_peripheralSensor[]`).
+
+## Core Tests
+
+Host-side unit tests (bitops, dispatch, endian) live in `core/` and build with
+the native compiler:
+
+```
+cd core && make && make run
+```
diff --git a/tests/clock/test_clock.c b/tests/clock/test_clock.c
new file mode 100644
index 0000000..b57b4cf
--- /dev/null
+++ b/tests/clock/test_clock.c
@@ -0,0 +1,9 @@
+#include
+#include "board.h"
+#include "test.h"
+
+void whal_Test_Clock(void)
+{
+ /* No generic clock API to test; chip-specific tests live in
+ * test__clock.c. */
+}
diff --git a/tests/clock/test_stm32wb_clock.c b/tests/clock/test_stm32wb_clock.c
new file mode 100644
index 0000000..77e2690
--- /dev/null
+++ b/tests/clock/test_stm32wb_clock.c
@@ -0,0 +1,37 @@
+#include
+#include
+#include
+#include
+#include "board.h"
+#include "test.h"
+
+static void Test_Clock_EnableDisable(void)
+{
+ whal_Stm32wb_Rcc_PeriphClk testClk = { WHAL_STM32WB55_GPIOA_GATE };
+
+ /* Save original state */
+ size_t origVal = 0;
+ whal_Reg_Get(g_whalClock.regmap.base, 0x4C, (1 << 0), 0, &origVal);
+
+ /* Enable and verify */
+ WHAL_ASSERT_EQ(whal_Stm32wb_Rcc_EnablePeriphClk(&g_whalClock, &testClk), WHAL_SUCCESS);
+
+ size_t val = 0;
+ whal_Reg_Get(g_whalClock.regmap.base, 0x4C, (1 << 0), 0, &val);
+ WHAL_ASSERT_EQ(val, 1);
+
+ /* Disable and verify */
+ WHAL_ASSERT_EQ(whal_Stm32wb_Rcc_DisablePeriphClk(&g_whalClock, &testClk), WHAL_SUCCESS);
+
+ whal_Reg_Get(g_whalClock.regmap.base, 0x4C, (1 << 0), 0, &val);
+ WHAL_ASSERT_EQ(val, 0);
+
+ /* Restore original state */
+ if (origVal)
+ whal_Stm32wb_Rcc_EnablePeriphClk(&g_whalClock, &testClk);
+}
+
+void whal_Test_Clock_Platform(void)
+{
+ WHAL_TEST(Test_Clock_EnableDisable);
+}
diff --git a/tests/core/Makefile b/tests/core/Makefile
new file mode 100644
index 0000000..3eb04c4
--- /dev/null
+++ b/tests/core/Makefile
@@ -0,0 +1,32 @@
+WHAL_DIR = $(CURDIR)/../..
+
+INCLUDE = -I$(WHAL_DIR)
+CFLAGS += -Wall -Werror $(INCLUDE) -g3
+
+TEST_SRC = main.c test_bitops.c test_dispatch.c test_endian.c test_timeout.c
+
+WHAL_SRC = $(WHAL_DIR)/src/gpio/gpio.c \
+ $(WHAL_DIR)/src/uart/uart.c \
+ $(WHAL_DIR)/src/flash/flash.c \
+ $(WHAL_DIR)/src/timer/timer.c
+
+SOURCE = $(TEST_SRC) $(WHAL_SRC)
+OBJECTS = $(patsubst %.c,%.o,$(SOURCE))
+
+TARGET = test_core
+
+all: $(TARGET)
+
+%.o: %.c
+ $(CC) $(CFLAGS) -c -o $@ $<
+
+$(TARGET): $(OBJECTS)
+ $(CC) $(CFLAGS) -o $@ $(OBJECTS)
+
+.PHONY: run clean
+
+run: $(TARGET)
+ ./$(TARGET)
+
+clean:
+ rm -f $(OBJECTS) $(TARGET)
diff --git a/tests/core/main.c b/tests/core/main.c
new file mode 100644
index 0000000..d067871
--- /dev/null
+++ b/tests/core/main.c
@@ -0,0 +1,38 @@
+#include
+#include "../test.h"
+
+int g_whalTestPassed;
+int g_whalTestFailed;
+int g_whalTestSkipped;
+int g_whalTestCurFailed;
+int g_whalTestCurSkipped;
+
+void whal_Test_Puts(const char *s)
+{
+ fputs(s, stdout);
+}
+
+void whal_Test_Bitops(void);
+void whal_Test_Dispatch(void);
+void whal_Test_Endian(void);
+#ifndef WHAL_CFG_NO_TIMEOUT
+void whal_Test_Timeout(void);
+#endif
+
+int main(void)
+{
+ g_whalTestPassed = 0;
+ g_whalTestFailed = 0;
+ g_whalTestSkipped = 0;
+
+ whal_Test_Bitops();
+ whal_Test_Dispatch();
+ whal_Test_Endian();
+#ifndef WHAL_CFG_NO_TIMEOUT
+ whal_Test_Timeout();
+#endif
+
+ WHAL_TEST_SUMMARY();
+
+ return g_whalTestFailed ? 1 : 0;
+}
diff --git a/tests/core/test_bitops.c b/tests/core/test_bitops.c
new file mode 100644
index 0000000..5df9440
--- /dev/null
+++ b/tests/core/test_bitops.c
@@ -0,0 +1,77 @@
+#include
+#include "../test.h"
+
+static void Test_Bitops_Bitmask4(void)
+{
+ WHAL_ASSERT_EQ(WHAL_BITMASK(4), 0xFul);
+}
+
+static void Test_Bitops_Bitmask8(void)
+{
+ WHAL_ASSERT_EQ(WHAL_BITMASK(8), 0xFFul);
+}
+
+static void Test_Bitops_Bitmask1(void)
+{
+ WHAL_ASSERT_EQ(WHAL_BITMASK(1), 1ul);
+}
+
+static void Test_Bitops_SetbitsLow(void)
+{
+ size_t msk = WHAL_BITMASK(4);
+ size_t pos = 0;
+ WHAL_ASSERT_EQ(whal_SetBits(msk, pos, 0xA), 0xAul);
+}
+
+static void Test_Bitops_SetbitsShifted(void)
+{
+ size_t msk = (WHAL_BITMASK(4) << 4);
+ size_t pos = 4;
+ WHAL_ASSERT_EQ(whal_SetBits(msk, pos, 0x5), 0x50ul);
+}
+
+static void Test_Bitops_GetbitsLow(void)
+{
+ size_t msk = WHAL_BITMASK(4);
+ size_t pos = 0;
+ WHAL_ASSERT_EQ(whal_GetBits(msk, pos, 0xABul), 0xBul);
+}
+
+static void Test_Bitops_GetbitsShifted(void)
+{
+ size_t msk = (WHAL_BITMASK(4) << 4);
+ size_t pos = 4;
+ WHAL_ASSERT_EQ(whal_GetBits(msk, pos, 0xABul), 0xAul);
+}
+
+static void Test_Bitops_SetbitsGetbitsRoundtrip(void)
+{
+ size_t msk = (WHAL_BITMASK(7) << 8);
+ size_t pos = 8;
+ size_t val = 42;
+ size_t encoded = whal_SetBits(msk, pos, val);
+ WHAL_ASSERT_EQ(whal_GetBits(msk, pos, encoded), val);
+}
+
+static void Test_Bitops_SetbitsSingleBit(void)
+{
+ size_t msk = (1UL << 24);
+ size_t pos = 24;
+ WHAL_ASSERT_EQ(whal_SetBits(msk, pos, 1), (1UL << 24));
+ WHAL_ASSERT_EQ(whal_SetBits(msk, pos, 0), 0ul);
+}
+
+void whal_Test_Bitops(void)
+{
+ WHAL_TEST_SUITE_START("bitops");
+ WHAL_TEST(Test_Bitops_Bitmask4);
+ WHAL_TEST(Test_Bitops_Bitmask8);
+ WHAL_TEST(Test_Bitops_Bitmask1);
+ WHAL_TEST(Test_Bitops_SetbitsLow);
+ WHAL_TEST(Test_Bitops_SetbitsShifted);
+ WHAL_TEST(Test_Bitops_GetbitsLow);
+ WHAL_TEST(Test_Bitops_GetbitsShifted);
+ WHAL_TEST(Test_Bitops_SetbitsGetbitsRoundtrip);
+ WHAL_TEST(Test_Bitops_SetbitsSingleBit);
+ WHAL_TEST_SUITE_END();
+}
diff --git a/tests/core/test_dispatch.c b/tests/core/test_dispatch.c
new file mode 100644
index 0000000..7fd808c
--- /dev/null
+++ b/tests/core/test_dispatch.c
@@ -0,0 +1,354 @@
+#include
+#include
+#include
+#include
+#include "../test.h"
+
+/*
+ * Mock drivers that return SUCCESS for all operations.
+ * Used to verify the generic dispatch layer.
+ *
+ * Clock and power are board-level drivers — they have no generic
+ * whal__* API or vtable, so they are not exercised here.
+ */
+
+static whal_Error MockGpioInit(whal_Gpio *d) { (void)d; return WHAL_SUCCESS; }
+static whal_Error MockGpioDeinit(whal_Gpio *d) { (void)d; return WHAL_SUCCESS; }
+static whal_Error MockGpioGet(whal_Gpio *d, size_t p, size_t *v) { (void)d; (void)p; *v = 1; return WHAL_SUCCESS; }
+static whal_Error MockGpioSet(whal_Gpio *d, size_t p, size_t v) { (void)d; (void)p; (void)v; return WHAL_SUCCESS; }
+
+static const whal_GpioDriver mockGpioDriver = {
+ .Init = MockGpioInit,
+ .Deinit = MockGpioDeinit,
+ .Get = MockGpioGet,
+ .Set = MockGpioSet,
+};
+
+static whal_Error MockUartInit(whal_Uart *d) { (void)d; return WHAL_SUCCESS; }
+static whal_Error MockUartDeinit(whal_Uart *d) { (void)d; return WHAL_SUCCESS; }
+static whal_Error MockUartSend(whal_Uart *d, const void *data, size_t sz) { (void)d; (void)data; (void)sz; return WHAL_SUCCESS; }
+static whal_Error MockUartRecv(whal_Uart *d, void *data, size_t sz) { (void)d; (void)data; (void)sz; return WHAL_SUCCESS; }
+
+static whal_Error MockUartSendAsync(whal_Uart *d, const void *data, size_t sz) { (void)d; (void)data; (void)sz; return WHAL_SUCCESS; }
+static whal_Error MockUartRecvAsync(whal_Uart *d, void *data, size_t sz) { (void)d; (void)data; (void)sz; return WHAL_SUCCESS; }
+
+static const whal_UartDriver mockUartDriver = {
+ .Init = MockUartInit,
+ .Deinit = MockUartDeinit,
+ .Send = MockUartSend,
+ .Recv = MockUartRecv,
+};
+
+static const whal_UartDriver mockUartAsyncDriver = {
+ .Init = MockUartInit,
+ .Deinit = MockUartDeinit,
+ .Send = MockUartSend,
+ .Recv = MockUartRecv,
+ .SendAsync = MockUartSendAsync,
+ .RecvAsync = MockUartRecvAsync,
+};
+
+static whal_Error MockFlashInit(whal_Flash *d) { (void)d; return WHAL_SUCCESS; }
+static whal_Error MockFlashDeinit(whal_Flash *d) { (void)d; return WHAL_SUCCESS; }
+static whal_Error MockFlashLock(whal_Flash *d, size_t a, size_t l) { (void)d; (void)a; (void)l; return WHAL_SUCCESS; }
+static whal_Error MockFlashUnlock(whal_Flash *d, size_t a, size_t l) { (void)d; (void)a; (void)l; return WHAL_SUCCESS; }
+static whal_Error MockFlashRead(whal_Flash *d, size_t a, void *data, size_t sz) { (void)d; (void)a; (void)data; (void)sz; return WHAL_SUCCESS; }
+static whal_Error MockFlashWrite(whal_Flash *d, size_t a, const void *data, size_t sz) { (void)d; (void)a; (void)data; (void)sz; return WHAL_SUCCESS; }
+static whal_Error MockFlashErase(whal_Flash *d, size_t a, size_t sz) { (void)d; (void)a; (void)sz; return WHAL_SUCCESS; }
+
+static const whal_FlashDriver mockFlashDriver = {
+ .Init = MockFlashInit,
+ .Deinit = MockFlashDeinit,
+ .Lock = MockFlashLock,
+ .Unlock = MockFlashUnlock,
+ .Read = MockFlashRead,
+ .Write = MockFlashWrite,
+ .Erase = MockFlashErase,
+};
+
+static whal_Error MockTimerInit(whal_Timer *d) { (void)d; return WHAL_SUCCESS; }
+static whal_Error MockTimerDeinit(whal_Timer *d) { (void)d; return WHAL_SUCCESS; }
+static whal_Error MockTimerStart(whal_Timer *d) { (void)d; return WHAL_SUCCESS; }
+static whal_Error MockTimerStop(whal_Timer *d) { (void)d; return WHAL_SUCCESS; }
+static whal_Error MockTimerReset(whal_Timer *d) { (void)d; return WHAL_SUCCESS; }
+
+static const whal_TimerDriver mockTimerDriver = {
+ .Init = MockTimerInit,
+ .Deinit = MockTimerDeinit,
+ .Start = MockTimerStart,
+ .Stop = MockTimerStop,
+ .Reset = MockTimerReset,
+};
+
+static whal_Error MockRngInit(whal_Rng *d) { (void)d; return WHAL_SUCCESS; }
+static whal_Error MockRngDeinit(whal_Rng *d) { (void)d; return WHAL_SUCCESS; }
+static whal_Error MockRngGenerate(whal_Rng *d, void *data, size_t sz) { (void)d; (void)data; (void)sz; return WHAL_SUCCESS; }
+
+static const whal_RngDriver mockRngDriver = {
+ .Init = MockRngInit,
+ .Deinit = MockRngDeinit,
+ .Generate = MockRngGenerate,
+};
+
+
+/* --- GPIO dispatch tests --- */
+
+static void Test_Gpio_NullDev(void)
+{
+ WHAL_ASSERT_EQ(whal_Gpio_Init(NULL), WHAL_EINVAL);
+ WHAL_ASSERT_EQ(whal_Gpio_Set(NULL, 0, 0), WHAL_EINVAL);
+ size_t val;
+ WHAL_ASSERT_EQ(whal_Gpio_Get(NULL, 0, &val), WHAL_EINVAL);
+}
+
+static void Test_Gpio_NullDriver(void)
+{
+ whal_Gpio dev = { .driver = NULL };
+ WHAL_ASSERT_EQ(whal_Gpio_Init(&dev), WHAL_ENOTSUP);
+}
+
+static void Test_Gpio_ValidDispatch(void)
+{
+ whal_Gpio dev = { .driver = &mockGpioDriver };
+ WHAL_ASSERT_EQ(whal_Gpio_Init(&dev), WHAL_SUCCESS);
+ WHAL_ASSERT_EQ(whal_Gpio_Set(&dev, 0, 1), WHAL_SUCCESS);
+ size_t val;
+ WHAL_ASSERT_EQ(whal_Gpio_Get(&dev, 0, &val), WHAL_SUCCESS);
+ WHAL_ASSERT_EQ(val, 1);
+}
+
+/* --- UART dispatch tests --- */
+
+static void Test_Uart_NullDev(void)
+{
+ WHAL_ASSERT_EQ(whal_Uart_Init(NULL), WHAL_EINVAL);
+ uint8_t buf[1];
+ WHAL_ASSERT_EQ(whal_Uart_Send(NULL, buf, 1), WHAL_EINVAL);
+ WHAL_ASSERT_EQ(whal_Uart_Recv(NULL, buf, 1), WHAL_EINVAL);
+ WHAL_ASSERT_EQ(whal_Uart_SendAsync(NULL, buf, 1), WHAL_EINVAL);
+ WHAL_ASSERT_EQ(whal_Uart_RecvAsync(NULL, buf, 1), WHAL_EINVAL);
+}
+
+static void Test_Uart_NullDriver(void)
+{
+ whal_Uart dev = { .driver = NULL };
+ WHAL_ASSERT_EQ(whal_Uart_Init(&dev), WHAL_ENOTSUP);
+}
+
+static void Test_Uart_NullAsyncVtable(void)
+{
+ whal_Uart dev = { .driver = &mockUartDriver };
+ uint8_t buf[1] = {0};
+ WHAL_ASSERT_EQ(whal_Uart_SendAsync(&dev, buf, 1), WHAL_ENOTSUP);
+ WHAL_ASSERT_EQ(whal_Uart_RecvAsync(&dev, buf, 1), WHAL_ENOTSUP);
+}
+
+static void Test_Uart_ValidDispatch(void)
+{
+ whal_Uart dev = { .driver = &mockUartDriver };
+ WHAL_ASSERT_EQ(whal_Uart_Init(&dev), WHAL_SUCCESS);
+ uint8_t buf[4] = {0};
+ WHAL_ASSERT_EQ(whal_Uart_Send(&dev, buf, sizeof(buf)), WHAL_SUCCESS);
+ WHAL_ASSERT_EQ(whal_Uart_Recv(&dev, buf, sizeof(buf)), WHAL_SUCCESS);
+
+ whal_Uart asyncDev = { .driver = &mockUartAsyncDriver };
+ WHAL_ASSERT_EQ(whal_Uart_SendAsync(&asyncDev, buf, sizeof(buf)), WHAL_SUCCESS);
+ WHAL_ASSERT_EQ(whal_Uart_RecvAsync(&asyncDev, buf, sizeof(buf)), WHAL_SUCCESS);
+}
+
+/* --- Flash dispatch tests --- */
+
+static void Test_Flash_NullDev(void)
+{
+ WHAL_ASSERT_EQ(whal_Flash_Init(NULL), WHAL_EINVAL);
+ WHAL_ASSERT_EQ(whal_Flash_Lock(NULL, 0, 0), WHAL_EINVAL);
+ WHAL_ASSERT_EQ(whal_Flash_Unlock(NULL, 0, 0), WHAL_EINVAL);
+ uint8_t buf[1];
+ WHAL_ASSERT_EQ(whal_Flash_Read(NULL, 0, buf, 1), WHAL_EINVAL);
+ WHAL_ASSERT_EQ(whal_Flash_Write(NULL, 0, buf, 1), WHAL_EINVAL);
+ WHAL_ASSERT_EQ(whal_Flash_Erase(NULL, 0, 1), WHAL_EINVAL);
+}
+
+static void Test_Flash_NullDriver(void)
+{
+ whal_Flash dev = { .driver = NULL };
+ WHAL_ASSERT_EQ(whal_Flash_Init(&dev), WHAL_ENOTSUP);
+}
+
+static void Test_Flash_ValidDispatch(void)
+{
+ whal_Flash dev = { .driver = &mockFlashDriver };
+ WHAL_ASSERT_EQ(whal_Flash_Init(&dev), WHAL_SUCCESS);
+ WHAL_ASSERT_EQ(whal_Flash_Lock(&dev, 0, 0), WHAL_SUCCESS);
+ WHAL_ASSERT_EQ(whal_Flash_Unlock(&dev, 0, 0), WHAL_SUCCESS);
+ uint8_t buf[4] = {0};
+ WHAL_ASSERT_EQ(whal_Flash_Read(&dev, 0, buf, sizeof(buf)), WHAL_SUCCESS);
+ WHAL_ASSERT_EQ(whal_Flash_Write(&dev, 0, buf, sizeof(buf)), WHAL_SUCCESS);
+ WHAL_ASSERT_EQ(whal_Flash_Erase(&dev, 0, sizeof(buf)), WHAL_SUCCESS);
+}
+
+/* --- Timer dispatch tests --- */
+
+static void Test_Timer_NullDev(void)
+{
+ WHAL_ASSERT_EQ(whal_Timer_Init(NULL), WHAL_EINVAL);
+ WHAL_ASSERT_EQ(whal_Timer_Start(NULL), WHAL_EINVAL);
+ WHAL_ASSERT_EQ(whal_Timer_Stop(NULL), WHAL_EINVAL);
+ WHAL_ASSERT_EQ(whal_Timer_Reset(NULL), WHAL_EINVAL);
+}
+
+static void Test_Timer_NullDriver(void)
+{
+ whal_Timer dev = { .driver = NULL };
+ WHAL_ASSERT_EQ(whal_Timer_Init(&dev), WHAL_ENOTSUP);
+}
+
+static void Test_Timer_ValidDispatch(void)
+{
+ whal_Timer dev = { .driver = &mockTimerDriver };
+ WHAL_ASSERT_EQ(whal_Timer_Init(&dev), WHAL_SUCCESS);
+ WHAL_ASSERT_EQ(whal_Timer_Start(&dev), WHAL_SUCCESS);
+ WHAL_ASSERT_EQ(whal_Timer_Stop(&dev), WHAL_SUCCESS);
+ WHAL_ASSERT_EQ(whal_Timer_Reset(&dev), WHAL_SUCCESS);
+}
+
+/* --- RNG dispatch tests --- */
+
+static void Test_Rng_NullDev(void)
+{
+ uint8_t buf[1];
+ WHAL_ASSERT_EQ(whal_Rng_Init(NULL), WHAL_EINVAL);
+ WHAL_ASSERT_EQ(whal_Rng_Deinit(NULL), WHAL_EINVAL);
+ WHAL_ASSERT_EQ(whal_Rng_Generate(NULL, buf, 1), WHAL_EINVAL);
+}
+
+static void Test_Rng_NullDriver(void)
+{
+ whal_Rng dev = { .driver = NULL };
+ WHAL_ASSERT_EQ(whal_Rng_Init(&dev), WHAL_ENOTSUP);
+}
+
+static void Test_Rng_ValidDispatch(void)
+{
+ whal_Rng dev = { .driver = &mockRngDriver };
+ WHAL_ASSERT_EQ(whal_Rng_Init(&dev), WHAL_SUCCESS);
+ uint8_t buf[4] = {0};
+ WHAL_ASSERT_EQ(whal_Rng_Generate(&dev, buf, sizeof(buf)), WHAL_SUCCESS);
+ WHAL_ASSERT_EQ(whal_Rng_Deinit(&dev), WHAL_SUCCESS);
+}
+
+/* --- SPI dispatch tests --- */
+
+static whal_Error MockSpiInit(whal_Spi *d) { (void)d; return WHAL_SUCCESS; }
+static whal_Error MockSpiDeinit(whal_Spi *d) { (void)d; return WHAL_SUCCESS; }
+static whal_Error MockSpiStartCom(whal_Spi *d, whal_Spi_ComCfg *c) { (void)d; (void)c; return WHAL_SUCCESS; }
+static whal_Error MockSpiEndCom(whal_Spi *d) { (void)d; return WHAL_SUCCESS; }
+static whal_Error MockSpiSendRecv(whal_Spi *d, const void *tx, size_t txLen, void *rx, size_t rxLen) { (void)d; (void)tx; (void)txLen; (void)rx; (void)rxLen; return WHAL_SUCCESS; }
+
+static const whal_SpiDriver mockSpiDriver = {
+ .Init = MockSpiInit,
+ .Deinit = MockSpiDeinit,
+ .StartCom = MockSpiStartCom,
+ .EndCom = MockSpiEndCom,
+ .SendRecv = MockSpiSendRecv,
+};
+
+static void Test_Spi_NullDev(void)
+{
+ whal_Spi_ComCfg comCfg = {0};
+ uint8_t buf[1];
+ WHAL_ASSERT_EQ(whal_Spi_Init(NULL), WHAL_EINVAL);
+ WHAL_ASSERT_EQ(whal_Spi_Deinit(NULL), WHAL_EINVAL);
+ WHAL_ASSERT_EQ(whal_Spi_StartCom(NULL, &comCfg), WHAL_EINVAL);
+ WHAL_ASSERT_EQ(whal_Spi_EndCom(NULL), WHAL_EINVAL);
+ WHAL_ASSERT_EQ(whal_Spi_SendRecv(NULL, buf, 1, buf, 1), WHAL_EINVAL);
+}
+
+static void Test_Spi_NullDriver(void)
+{
+ whal_Spi dev = { .driver = NULL };
+ WHAL_ASSERT_EQ(whal_Spi_Init(&dev), WHAL_ENOTSUP);
+}
+
+static void Test_Spi_ValidDispatch(void)
+{
+ whal_Spi dev = { .driver = &mockSpiDriver };
+ whal_Spi_ComCfg comCfg = {0};
+ uint8_t buf[4] = {0};
+ WHAL_ASSERT_EQ(whal_Spi_Init(&dev), WHAL_SUCCESS);
+ WHAL_ASSERT_EQ(whal_Spi_StartCom(&dev, &comCfg), WHAL_SUCCESS);
+ WHAL_ASSERT_EQ(whal_Spi_SendRecv(&dev, buf, sizeof(buf), buf, sizeof(buf)), WHAL_SUCCESS);
+ WHAL_ASSERT_EQ(whal_Spi_EndCom(&dev), WHAL_SUCCESS);
+ WHAL_ASSERT_EQ(whal_Spi_Deinit(&dev), WHAL_SUCCESS);
+}
+
+/* --- Block dispatch tests --- */
+
+static whal_Error MockBlockInit(whal_Block *d) { (void)d; return WHAL_SUCCESS; }
+static whal_Error MockBlockDeinit(whal_Block *d) { (void)d; return WHAL_SUCCESS; }
+static whal_Error MockBlockRead(whal_Block *d, uint32_t b, void *data, uint32_t c) { (void)d; (void)b; (void)data; (void)c; return WHAL_SUCCESS; }
+static whal_Error MockBlockWrite(whal_Block *d, uint32_t b, const void *data, uint32_t c) { (void)d; (void)b; (void)data; (void)c; return WHAL_SUCCESS; }
+static whal_Error MockBlockErase(whal_Block *d, uint32_t b, uint32_t c) { (void)d; (void)b; (void)c; return WHAL_SUCCESS; }
+
+static const whal_BlockDriver mockBlockDriver = {
+ .Init = MockBlockInit,
+ .Deinit = MockBlockDeinit,
+ .Read = MockBlockRead,
+ .Write = MockBlockWrite,
+ .Erase = MockBlockErase,
+};
+
+static void Test_Block_NullDev(void)
+{
+ WHAL_ASSERT_EQ(whal_Block_Init(NULL), WHAL_EINVAL);
+ WHAL_ASSERT_EQ(whal_Block_Deinit(NULL), WHAL_EINVAL);
+ uint8_t buf[1];
+ WHAL_ASSERT_EQ(whal_Block_Read(NULL, 0, buf, 1), WHAL_EINVAL);
+ WHAL_ASSERT_EQ(whal_Block_Write(NULL, 0, buf, 1), WHAL_EINVAL);
+ WHAL_ASSERT_EQ(whal_Block_Erase(NULL, 0, 1), WHAL_EINVAL);
+}
+
+static void Test_Block_NullDriver(void)
+{
+ whal_Block dev = { .driver = NULL };
+ WHAL_ASSERT_EQ(whal_Block_Init(&dev), WHAL_ENOTSUP);
+}
+
+static void Test_Block_ValidDispatch(void)
+{
+ whal_Block dev = { .driver = &mockBlockDriver };
+ WHAL_ASSERT_EQ(whal_Block_Init(&dev), WHAL_SUCCESS);
+ uint8_t buf[4] = {0};
+ WHAL_ASSERT_EQ(whal_Block_Read(&dev, 0, buf, 1), WHAL_SUCCESS);
+ WHAL_ASSERT_EQ(whal_Block_Write(&dev, 0, buf, 1), WHAL_SUCCESS);
+ WHAL_ASSERT_EQ(whal_Block_Erase(&dev, 0, 1), WHAL_SUCCESS);
+ WHAL_ASSERT_EQ(whal_Block_Deinit(&dev), WHAL_SUCCESS);
+}
+
+void whal_Test_Dispatch(void)
+{
+ WHAL_TEST_SUITE_START("dispatch");
+ WHAL_TEST(Test_Gpio_NullDev);
+ WHAL_TEST(Test_Gpio_NullDriver);
+ WHAL_TEST(Test_Gpio_ValidDispatch);
+ WHAL_TEST(Test_Uart_NullDev);
+ WHAL_TEST(Test_Uart_NullDriver);
+ WHAL_TEST(Test_Uart_NullAsyncVtable);
+ WHAL_TEST(Test_Uart_ValidDispatch);
+ WHAL_TEST(Test_Flash_NullDev);
+ WHAL_TEST(Test_Flash_NullDriver);
+ WHAL_TEST(Test_Flash_ValidDispatch);
+ WHAL_TEST(Test_Timer_NullDev);
+ WHAL_TEST(Test_Timer_NullDriver);
+ WHAL_TEST(Test_Timer_ValidDispatch);
+ WHAL_TEST(Test_Rng_NullDev);
+ WHAL_TEST(Test_Rng_NullDriver);
+ WHAL_TEST(Test_Rng_ValidDispatch);
+ WHAL_TEST(Test_Spi_NullDev);
+ WHAL_TEST(Test_Spi_NullDriver);
+ WHAL_TEST(Test_Spi_ValidDispatch);
+ WHAL_TEST(Test_Block_NullDev);
+ WHAL_TEST(Test_Block_NullDriver);
+ WHAL_TEST(Test_Block_ValidDispatch);
+ WHAL_TEST_SUITE_END();
+}
diff --git a/tests/core/test_endian.c b/tests/core/test_endian.c
new file mode 100644
index 0000000..44d5706
--- /dev/null
+++ b/tests/core/test_endian.c
@@ -0,0 +1,84 @@
+#include
+#include "../test.h"
+
+static void Test_Endian_LoadBe32(void)
+{
+ const uint8_t buf[] = { 0xDE, 0xAD, 0xBE, 0xEF };
+ WHAL_ASSERT_EQ(whal_LoadBe32(buf), 0xDEADBEEFul);
+}
+
+static void Test_Endian_LoadBe32_Zero(void)
+{
+ const uint8_t buf[] = { 0x00, 0x00, 0x00, 0x00 };
+ WHAL_ASSERT_EQ(whal_LoadBe32(buf), 0x00000000ul);
+}
+
+static void Test_Endian_LoadBe32_AllOnes(void)
+{
+ const uint8_t buf[] = { 0xFF, 0xFF, 0xFF, 0xFF };
+ WHAL_ASSERT_EQ(whal_LoadBe32(buf), 0xFFFFFFFFul);
+}
+
+static void Test_Endian_LoadBe32_MsbOnly(void)
+{
+ const uint8_t buf[] = { 0x80, 0x00, 0x00, 0x00 };
+ WHAL_ASSERT_EQ(whal_LoadBe32(buf), 0x80000000ul);
+}
+
+static void Test_Endian_LoadBe32_LsbOnly(void)
+{
+ const uint8_t buf[] = { 0x00, 0x00, 0x00, 0x01 };
+ WHAL_ASSERT_EQ(whal_LoadBe32(buf), 0x00000001ul);
+}
+
+static void Test_Endian_StoreBe32(void)
+{
+ uint8_t buf[4] = {0};
+ whal_StoreBe32(buf, 0xDEADBEEF);
+ WHAL_ASSERT_EQ(buf[0], 0xDE);
+ WHAL_ASSERT_EQ(buf[1], 0xAD);
+ WHAL_ASSERT_EQ(buf[2], 0xBE);
+ WHAL_ASSERT_EQ(buf[3], 0xEF);
+}
+
+static void Test_Endian_StoreBe32_Zero(void)
+{
+ uint8_t buf[4] = { 0xFF, 0xFF, 0xFF, 0xFF };
+ whal_StoreBe32(buf, 0x00000000);
+ WHAL_ASSERT_EQ(buf[0], 0x00);
+ WHAL_ASSERT_EQ(buf[1], 0x00);
+ WHAL_ASSERT_EQ(buf[2], 0x00);
+ WHAL_ASSERT_EQ(buf[3], 0x00);
+}
+
+static void Test_Endian_StoreBe32_MsbOnly(void)
+{
+ uint8_t buf[4] = {0};
+ whal_StoreBe32(buf, 0x80000000);
+ WHAL_ASSERT_EQ(buf[0], 0x80);
+ WHAL_ASSERT_EQ(buf[1], 0x00);
+ WHAL_ASSERT_EQ(buf[2], 0x00);
+ WHAL_ASSERT_EQ(buf[3], 0x00);
+}
+
+static void Test_Endian_Roundtrip(void)
+{
+ uint8_t buf[4] = {0};
+ whal_StoreBe32(buf, 0xCAFEBABE);
+ WHAL_ASSERT_EQ(whal_LoadBe32(buf), 0xCAFEBABEul);
+}
+
+void whal_Test_Endian(void)
+{
+ WHAL_TEST_SUITE_START("endian");
+ WHAL_TEST(Test_Endian_LoadBe32);
+ WHAL_TEST(Test_Endian_LoadBe32_Zero);
+ WHAL_TEST(Test_Endian_LoadBe32_AllOnes);
+ WHAL_TEST(Test_Endian_LoadBe32_MsbOnly);
+ WHAL_TEST(Test_Endian_LoadBe32_LsbOnly);
+ WHAL_TEST(Test_Endian_StoreBe32);
+ WHAL_TEST(Test_Endian_StoreBe32_Zero);
+ WHAL_TEST(Test_Endian_StoreBe32_MsbOnly);
+ WHAL_TEST(Test_Endian_Roundtrip);
+ WHAL_TEST_SUITE_END();
+}
diff --git a/tests/core/test_timeout.c b/tests/core/test_timeout.c
new file mode 100644
index 0000000..8275abf
--- /dev/null
+++ b/tests/core/test_timeout.c
@@ -0,0 +1,141 @@
+#include
+#include "../test.h"
+
+#ifndef WHAL_CFG_NO_TIMEOUT
+
+static uint32_t g_fakeTick;
+
+static uint32_t FakeTick(void)
+{
+ return g_fakeTick;
+}
+
+static whal_Timeout g_timeout = {
+ .timeoutTicks = 10,
+ .GetTick = FakeTick,
+};
+
+/* Helper: get a pointer to g_timeout (avoids -Waddress on &global) */
+static whal_Timeout *timeout(void)
+{
+ return &g_timeout;
+}
+
+/* WHAL_TIMEOUT_EXPIRED returns 0 when NULL */
+static void Test_Timeout_NullNotExpired(void)
+{
+ whal_Timeout *t = NULL;
+ int expired = WHAL_TIMEOUT_EXPIRED(t);
+ WHAL_ASSERT_EQ(expired, 0);
+}
+
+/* START snapshots current tick */
+static void Test_Timeout_StartSnapshotsTick(void)
+{
+ g_fakeTick = 42;
+ WHAL_TIMEOUT_START(timeout());
+ WHAL_ASSERT_EQ(g_timeout.startTick, 42);
+}
+
+/* Not expired immediately after start */
+static void Test_Timeout_NotExpiredImmediately(void)
+{
+ g_fakeTick = 0;
+ WHAL_TIMEOUT_START(timeout());
+ int expired = WHAL_TIMEOUT_EXPIRED(timeout());
+ WHAL_ASSERT_EQ(expired, 0);
+}
+
+/* Not expired one tick before deadline */
+static void Test_Timeout_NotExpiredBeforeDeadline(void)
+{
+ g_fakeTick = 0;
+ WHAL_TIMEOUT_START(timeout());
+ g_fakeTick = 9;
+ int expired = WHAL_TIMEOUT_EXPIRED(timeout());
+ WHAL_ASSERT_EQ(expired, 0);
+}
+
+/* Expired exactly at deadline */
+static void Test_Timeout_ExpiredAtDeadline(void)
+{
+ g_fakeTick = 0;
+ WHAL_TIMEOUT_START(timeout());
+ g_fakeTick = 10;
+ int expired = WHAL_TIMEOUT_EXPIRED(timeout());
+ WHAL_ASSERT_NEQ(expired, 0);
+}
+
+/* Expired well past deadline */
+static void Test_Timeout_ExpiredPastDeadline(void)
+{
+ g_fakeTick = 0;
+ WHAL_TIMEOUT_START(timeout());
+ g_fakeTick = 100;
+ int expired = WHAL_TIMEOUT_EXPIRED(timeout());
+ WHAL_ASSERT_NEQ(expired, 0);
+}
+
+/* START resets the window */
+static void Test_Timeout_StartResetsWindow(void)
+{
+ int expired;
+
+ g_fakeTick = 0;
+ WHAL_TIMEOUT_START(timeout());
+ g_fakeTick = 10;
+ expired = WHAL_TIMEOUT_EXPIRED(timeout());
+ WHAL_ASSERT_NEQ(expired, 0);
+
+ /* Restart — should no longer be expired */
+ WHAL_TIMEOUT_START(timeout());
+ expired = WHAL_TIMEOUT_EXPIRED(timeout());
+ WHAL_ASSERT_EQ(expired, 0);
+}
+
+/* Non-zero start tick works correctly */
+static void Test_Timeout_NonZeroStart(void)
+{
+ int expired;
+
+ g_fakeTick = 1000;
+ WHAL_TIMEOUT_START(timeout());
+ g_fakeTick = 1005;
+ expired = WHAL_TIMEOUT_EXPIRED(timeout());
+ WHAL_ASSERT_EQ(expired, 0);
+ g_fakeTick = 1010;
+ expired = WHAL_TIMEOUT_EXPIRED(timeout());
+ WHAL_ASSERT_NEQ(expired, 0);
+}
+
+/* Tick counter wrapping (unsigned subtraction handles this) */
+static void Test_Timeout_TickWrap(void)
+{
+ int expired;
+
+ g_fakeTick = UINT32_MAX - 3;
+ WHAL_TIMEOUT_START(timeout());
+ g_fakeTick = UINT32_MAX;
+ expired = WHAL_TIMEOUT_EXPIRED(timeout());
+ WHAL_ASSERT_EQ(expired, 0);
+ g_fakeTick = UINT32_MAX - 3 + 10;
+ expired = WHAL_TIMEOUT_EXPIRED(timeout());
+ WHAL_ASSERT_NEQ(expired, 0);
+}
+
+void whal_Test_Timeout(void)
+{
+ WHAL_TEST_SUITE_START("timeout");
+ WHAL_TEST(Test_Timeout_NullNotExpired);
+ WHAL_TEST(Test_Timeout_StartSnapshotsTick);
+ WHAL_TEST(Test_Timeout_NotExpiredImmediately);
+ WHAL_TEST(Test_Timeout_NotExpiredBeforeDeadline);
+ WHAL_TEST(Test_Timeout_ExpiredAtDeadline);
+ WHAL_TEST(Test_Timeout_ExpiredPastDeadline);
+ WHAL_TEST(Test_Timeout_StartResetsWindow);
+ WHAL_TEST(Test_Timeout_NonZeroStart);
+ WHAL_TEST(Test_Timeout_TickWrap);
+ WHAL_TEST_SUITE_END();
+}
+
+#endif /* !WHAL_CFG_NO_TIMEOUT */
diff --git a/tests/gpio/test_gpio.c b/tests/gpio/test_gpio.c
new file mode 100644
index 0000000..839f497
--- /dev/null
+++ b/tests/gpio/test_gpio.c
@@ -0,0 +1,37 @@
+#include
+#include "board.h"
+#include "test.h"
+
+static void Test_Gpio_Api(void)
+{
+ size_t val;
+
+ WHAL_ASSERT_EQ(whal_Gpio_Init(NULL), WHAL_EINVAL);
+ WHAL_ASSERT_EQ(whal_Gpio_Deinit(NULL), WHAL_EINVAL);
+ WHAL_ASSERT_EQ(whal_Gpio_Get(NULL, 0, &val), WHAL_EINVAL);
+ WHAL_ASSERT_EQ(whal_Gpio_Set(NULL, 0, 0), WHAL_EINVAL);
+ WHAL_ASSERT_EQ(whal_Gpio_Get(&g_whalGpio, 0, NULL), WHAL_EINVAL);
+}
+
+static void Test_Gpio_SetGetHighLow(void)
+{
+ size_t val = 0;
+
+ /* Set high and verify */
+ WHAL_ASSERT_EQ(whal_Gpio_Set(&g_whalGpio, BOARD_LED_PIN, 1), WHAL_SUCCESS);
+ WHAL_ASSERT_EQ(whal_Gpio_Get(&g_whalGpio, BOARD_LED_PIN, &val), WHAL_SUCCESS);
+ WHAL_ASSERT_EQ(val, 1);
+
+ /* Set low and verify */
+ WHAL_ASSERT_EQ(whal_Gpio_Set(&g_whalGpio, BOARD_LED_PIN, 0), WHAL_SUCCESS);
+ WHAL_ASSERT_EQ(whal_Gpio_Get(&g_whalGpio, BOARD_LED_PIN, &val), WHAL_SUCCESS);
+ WHAL_ASSERT_EQ(val, 0);
+}
+
+void whal_Test_Gpio(void)
+{
+ WHAL_TEST_SUITE_START("gpio");
+ WHAL_TEST(Test_Gpio_Api);
+ WHAL_TEST(Test_Gpio_SetGetHighLow);
+ WHAL_TEST_SUITE_END();
+}
diff --git a/tests/gpio/test_stm32l1_gpio.c b/tests/gpio/test_stm32l1_gpio.c
new file mode 100644
index 0000000..c287cb9
--- /dev/null
+++ b/tests/gpio/test_stm32l1_gpio.c
@@ -0,0 +1 @@
+#include "test_stm32wb_gpio.c"
diff --git a/tests/gpio/test_stm32wb_gpio.c b/tests/gpio/test_stm32wb_gpio.c
new file mode 100644
index 0000000..3596e4b
--- /dev/null
+++ b/tests/gpio/test_stm32wb_gpio.c
@@ -0,0 +1,102 @@
+#include
+#include
+#include
+#include "board.h"
+#include "test.h"
+
+/*
+ * GPIO register offsets. Port and pin are derived from the LED entry in
+ * g_whalGpio.cfg->pinCfg[BOARD_LED_PIN]. Port stride is 0x400 on all
+ * supported STM32 families.
+ */
+#define GPIOx_MODE_REG 0x00
+#define GPIOx_ODR_REG 0x14
+#define GPIOx_STRIDE 0x400
+
+static inline size_t Board_LedPortBase(void)
+{
+ whal_Stm32wb_Gpio_Cfg *cfg = (whal_Stm32wb_Gpio_Cfg *)g_whalGpio.cfg;
+ whal_Stm32wb_Gpio_PinCfg led = cfg->pinCfg[BOARD_LED_PIN];
+ return g_whalGpio.regmap.base + WHAL_STM32WB_GPIO_GET_PORT(led) * GPIOx_STRIDE;
+}
+
+static inline size_t Board_LedPinNum(void)
+{
+ whal_Stm32wb_Gpio_Cfg *cfg = (whal_Stm32wb_Gpio_Cfg *)g_whalGpio.cfg;
+ whal_Stm32wb_Gpio_PinCfg led = cfg->pinCfg[BOARD_LED_PIN];
+ return WHAL_STM32WB_GPIO_GET_PIN(led);
+}
+
+static void Test_Gpio_PinCfgRoundTrip(void)
+{
+ whal_Stm32wb_Gpio_PinCfg cfg = WHAL_STM32WB_GPIO_PIN(
+ WHAL_STM32WB_GPIO_PORT_C, 13, WHAL_STM32WB_GPIO_MODE_ALTFN,
+ WHAL_STM32WB_GPIO_OUTTYPE_OPENDRAIN, WHAL_STM32WB_GPIO_SPEED_HIGH,
+ WHAL_STM32WB_GPIO_PULL_DOWN, 9);
+
+ WHAL_ASSERT_EQ(WHAL_STM32WB_GPIO_GET_PORT(cfg), WHAL_STM32WB_GPIO_PORT_C);
+ WHAL_ASSERT_EQ(WHAL_STM32WB_GPIO_GET_PIN(cfg), 13);
+ WHAL_ASSERT_EQ(WHAL_STM32WB_GPIO_GET_MODE(cfg), WHAL_STM32WB_GPIO_MODE_ALTFN);
+ WHAL_ASSERT_EQ(WHAL_STM32WB_GPIO_GET_OUTTYPE(cfg), WHAL_STM32WB_GPIO_OUTTYPE_OPENDRAIN);
+ WHAL_ASSERT_EQ(WHAL_STM32WB_GPIO_GET_SPEED(cfg), WHAL_STM32WB_GPIO_SPEED_HIGH);
+ WHAL_ASSERT_EQ(WHAL_STM32WB_GPIO_GET_PULL(cfg), WHAL_STM32WB_GPIO_PULL_DOWN);
+ WHAL_ASSERT_EQ(WHAL_STM32WB_GPIO_GET_ALTFN(cfg), 9);
+}
+
+static void Test_Gpio_NoDuplicatePins(void)
+{
+ whal_Stm32wb_Gpio_Cfg *cfg = (whal_Stm32wb_Gpio_Cfg *)g_whalGpio.cfg;
+ whal_Stm32wb_Gpio_PinCfg *pins = cfg->pinCfg;
+
+ for (size_t i = 0; i < cfg->pinCount; i++) {
+ for (size_t j = i + 1; j < cfg->pinCount; j++) {
+ if (WHAL_STM32WB_GPIO_GET_PORT(pins[i]) == WHAL_STM32WB_GPIO_GET_PORT(pins[j]) &&
+ WHAL_STM32WB_GPIO_GET_PIN(pins[i]) == WHAL_STM32WB_GPIO_GET_PIN(pins[j])) {
+ WHAL_ASSERT_NEQ(WHAL_STM32WB_GPIO_GET_PORT(pins[i]),
+ WHAL_STM32WB_GPIO_GET_PORT(pins[j]));
+ }
+ }
+ }
+}
+
+static void Test_Gpio_ModeRegister(void)
+{
+ size_t pinNum = Board_LedPinNum();
+ size_t bitPos = pinNum << 1;
+ size_t mask = (WHAL_BITMASK(2) << bitPos);
+ size_t val = 0;
+
+ whal_Reg_Get(Board_LedPortBase(), GPIOx_MODE_REG, mask, bitPos, &val);
+ WHAL_ASSERT_EQ(val, WHAL_STM32WB_GPIO_MODE_OUT);
+}
+
+static void Test_Gpio_SetHighReg(void)
+{
+ WHAL_ASSERT_EQ(whal_Gpio_Set(&g_whalGpio, BOARD_LED_PIN, 1), WHAL_SUCCESS);
+
+ size_t pinNum = Board_LedPinNum();
+ size_t val = 0;
+ whal_Reg_Get(Board_LedPortBase(), GPIOx_ODR_REG, (1UL << pinNum),
+ pinNum, &val);
+ WHAL_ASSERT_EQ(val, 1);
+}
+
+static void Test_Gpio_SetLowReg(void)
+{
+ WHAL_ASSERT_EQ(whal_Gpio_Set(&g_whalGpio, BOARD_LED_PIN, 0), WHAL_SUCCESS);
+
+ size_t pinNum = Board_LedPinNum();
+ size_t val = 0;
+ whal_Reg_Get(Board_LedPortBase(), GPIOx_ODR_REG, (1UL << pinNum),
+ pinNum, &val);
+ WHAL_ASSERT_EQ(val, 0);
+}
+
+void whal_Test_Gpio_Platform(void)
+{
+ WHAL_TEST(Test_Gpio_PinCfgRoundTrip);
+ WHAL_TEST(Test_Gpio_NoDuplicatePins);
+ WHAL_TEST(Test_Gpio_ModeRegister);
+ WHAL_TEST(Test_Gpio_SetHighReg);
+ WHAL_TEST(Test_Gpio_SetLowReg);
+}
diff --git a/tests/main.c b/tests/main.c
new file mode 100644
index 0000000..c463c30
--- /dev/null
+++ b/tests/main.c
@@ -0,0 +1,277 @@
+#include
+#include
+#include "board.h"
+#include "test.h"
+
+#ifdef WHAL_TEST_ENABLE_CLOCK
+void whal_Test_Clock(void);
+#ifdef WHAL_TEST_ENABLE_CLOCK_PLATFORM
+void whal_Test_Clock_Platform(void);
+#endif
+#endif
+
+#ifdef WHAL_TEST_ENABLE_GPIO
+void whal_Test_Gpio(void);
+#ifdef WHAL_TEST_ENABLE_GPIO_PLATFORM
+void whal_Test_Gpio_Platform(void);
+#endif
+#endif
+
+#ifdef WHAL_TEST_ENABLE_FLASH
+void whal_Test_Flash(void);
+#ifdef WHAL_TEST_ENABLE_FLASH_PLATFORM
+void whal_Test_Flash_Platform(void);
+#endif
+#endif
+
+#ifdef WHAL_TEST_ENABLE_TIMER
+void whal_Test_Timer(void);
+#ifdef WHAL_TEST_ENABLE_TIMER_PLATFORM
+void whal_Test_Timer_Platform(void);
+#endif
+#endif
+
+#ifdef WHAL_TEST_ENABLE_RNG
+void whal_Test_Rng(void);
+#ifdef WHAL_TEST_ENABLE_RNG_PLATFORM
+void whal_Test_Rng_Platform(void);
+#endif
+#endif
+
+#ifdef WHAL_TEST_ENABLE_IPC
+void whal_Test_Ipc(void);
+#ifdef WHAL_TEST_ENABLE_IPC_PLATFORM
+void whal_Test_Ipc_Platform(void);
+#endif
+#endif
+
+#ifdef WHAL_TEST_ENABLE_SPI_LOOPBACK
+void whal_Test_Spi_Loopback(void);
+#ifdef WHAL_TEST_ENABLE_SPI_LOOPBACK_PLATFORM
+void whal_Test_Spi_Loopback_Platform(void);
+#endif
+#endif
+
+#ifdef WHAL_TEST_ENABLE_CRYPTO
+void whal_Test_Crypto(void);
+#ifdef WHAL_TEST_ENABLE_CRYPTO_PLATFORM
+void whal_Test_Crypto_Platform(void);
+#endif
+#endif
+
+#ifdef WHAL_TEST_ENABLE_BLOCK
+void whal_Test_Block(void);
+#endif
+
+#ifdef WHAL_TEST_ENABLE_ETH
+void whal_Test_Eth(void);
+#ifdef WHAL_TEST_ENABLE_ETH_PLATFORM
+void whal_Test_Eth_Platform(void);
+#endif
+#endif
+
+#ifdef WHAL_TEST_ENABLE_BMI270_SENSOR
+void whal_Test_Bmi270_Sensor(void);
+#endif
+
+#ifdef WHAL_TEST_ENABLE_WATCHDOG
+void whal_Test_Watchdog(void);
+#ifdef WHAL_TEST_ENABLE_WATCHDOG_PLATFORM
+void whal_Test_Watchdog_Platform(void);
+#endif
+#endif
+
+#ifdef WHAL_TEST_ENABLE_UART
+void whal_Test_Uart(void);
+#ifdef WHAL_TEST_ENABLE_UART_PLATFORM
+void whal_Test_Uart_Platform(void);
+#endif
+#endif
+
+#ifdef WHAL_TEST_ENABLE_I2C
+void whal_Test_I2c(void);
+#ifdef WHAL_TEST_ENABLE_I2C_PLATFORM
+void whal_Test_I2c_Platform(void);
+#endif
+#endif
+
+#ifdef WHAL_TEST_ENABLE_DMA
+void whal_Test_Dma(void);
+#ifdef WHAL_TEST_ENABLE_DMA_PLATFORM
+void whal_Test_Dma_Platform(void);
+#endif
+#endif
+
+#ifdef WHAL_TEST_ENABLE_IRQ
+void whal_Test_Irq(void);
+#ifdef WHAL_TEST_ENABLE_IRQ_PLATFORM
+void whal_Test_Irq_Platform(void);
+#endif
+#endif
+
+#ifdef WHAL_TEST_ENABLE_SENSOR
+void whal_Test_Sensor(void);
+#endif
+
+int g_whalTestPassed;
+int g_whalTestFailed;
+int g_whalTestSkipped;
+int g_whalTestCurFailed;
+int g_whalTestCurSkipped;
+
+void whal_Test_Puts(const char *s)
+{
+ size_t len = 0;
+ while (s[len])
+ len++;
+
+ if (len == 0)
+ return;
+
+ if (s[len - 1] == '\n') {
+ if (len > 1)
+ whal_Uart_Send(&g_whalUart, s, len - 1);
+ whal_Uart_Send(&g_whalUart, "\r\n", 2);
+ } else {
+ whal_Uart_Send(&g_whalUart, s, len);
+ }
+}
+
+void main(void)
+{
+ g_whalTestPassed = 0;
+ g_whalTestFailed = 0;
+ g_whalTestSkipped = 0;
+
+ if (Board_Init() != WHAL_SUCCESS)
+ while (1);
+
+ whal_Gpio_Set(&g_whalGpio, BOARD_LED_PIN, 1);
+
+ whal_Test_Printf("wolfHAL HW Test Suite\n");
+ whal_Test_Printf("=====================\n");
+
+#ifdef WHAL_TEST_ENABLE_CLOCK
+ whal_Test_Clock();
+#ifdef WHAL_TEST_ENABLE_CLOCK_PLATFORM
+ whal_Test_Clock_Platform();
+#endif
+#endif
+
+#ifdef WHAL_TEST_ENABLE_GPIO
+ whal_Test_Gpio();
+#ifdef WHAL_TEST_ENABLE_GPIO_PLATFORM
+ whal_Test_Gpio_Platform();
+#endif
+#endif
+
+#ifdef WHAL_TEST_ENABLE_FLASH
+ whal_Test_Flash();
+#ifdef WHAL_TEST_ENABLE_FLASH_PLATFORM
+ whal_Test_Flash_Platform();
+#endif
+#endif
+
+#ifdef WHAL_TEST_ENABLE_TIMER
+ whal_Test_Timer();
+#ifdef WHAL_TEST_ENABLE_TIMER_PLATFORM
+ whal_Test_Timer_Platform();
+#endif
+#endif
+
+#ifdef WHAL_TEST_ENABLE_RNG
+ whal_Test_Rng();
+#ifdef WHAL_TEST_ENABLE_RNG_PLATFORM
+ whal_Test_Rng_Platform();
+#endif
+#endif
+
+#ifdef WHAL_TEST_ENABLE_IPC
+ whal_Test_Ipc();
+#ifdef WHAL_TEST_ENABLE_IPC_PLATFORM
+ whal_Test_Ipc_Platform();
+#endif
+#endif
+
+#ifdef WHAL_TEST_ENABLE_SPI_LOOPBACK
+ whal_Test_Spi_Loopback();
+#ifdef WHAL_TEST_ENABLE_SPI_LOOPBACK_PLATFORM
+ whal_Test_Spi_Loopback_Platform();
+#endif
+#endif
+
+#ifdef WHAL_TEST_ENABLE_CRYPTO
+ whal_Test_Crypto();
+#ifdef WHAL_TEST_ENABLE_CRYPTO_PLATFORM
+ whal_Test_Crypto_Platform();
+#endif
+#endif
+
+#ifdef WHAL_TEST_ENABLE_BLOCK
+ whal_Test_Block();
+#endif
+
+#ifdef WHAL_TEST_ENABLE_ETH
+ whal_Test_Eth();
+#ifdef WHAL_TEST_ENABLE_ETH_PLATFORM
+ whal_Test_Eth_Platform();
+#endif
+#endif
+
+#ifdef WHAL_TEST_ENABLE_BMI270_SENSOR
+ whal_Test_Bmi270_Sensor();
+#endif
+
+#ifdef WHAL_TEST_ENABLE_WATCHDOG
+ whal_Test_Watchdog();
+#ifdef WHAL_TEST_ENABLE_WATCHDOG_PLATFORM
+ whal_Test_Watchdog_Platform();
+#endif
+#endif
+
+#ifdef WHAL_TEST_ENABLE_UART
+ whal_Test_Uart();
+#ifdef WHAL_TEST_ENABLE_UART_PLATFORM
+ whal_Test_Uart_Platform();
+#endif
+#endif
+
+#ifdef WHAL_TEST_ENABLE_I2C
+ whal_Test_I2c();
+#ifdef WHAL_TEST_ENABLE_I2C_PLATFORM
+ whal_Test_I2c_Platform();
+#endif
+#endif
+
+#ifdef WHAL_TEST_ENABLE_DMA
+ whal_Test_Dma();
+#ifdef WHAL_TEST_ENABLE_DMA_PLATFORM
+ whal_Test_Dma_Platform();
+#endif
+#endif
+
+#ifdef WHAL_TEST_ENABLE_IRQ
+ whal_Test_Irq();
+#ifdef WHAL_TEST_ENABLE_IRQ_PLATFORM
+ whal_Test_Irq_Platform();
+#endif
+#endif
+
+#ifdef WHAL_TEST_ENABLE_SENSOR
+ whal_Test_Sensor();
+#endif
+
+ WHAL_TEST_SUMMARY();
+
+ if (g_whalTestFailed == 0) {
+ whal_Gpio_Set(&g_whalGpio, BOARD_LED_PIN, 1);
+ while (1);
+ }
+
+ while (1) {
+ whal_Gpio_Set(&g_whalGpio, BOARD_LED_PIN, 1);
+ Board_WaitMs(100);
+ whal_Gpio_Set(&g_whalGpio, BOARD_LED_PIN, 0);
+ Board_WaitMs(100);
+ }
+}
diff --git a/tests/test.h b/tests/test.h
new file mode 100644
index 0000000..fe9baa3
--- /dev/null
+++ b/tests/test.h
@@ -0,0 +1,182 @@
+#ifndef WHAL_TEST_H
+#define WHAL_TEST_H
+
+#include
+#include
+
+/*
+ * Minimal test framework for wolfHAL.
+ *
+ * Provides test runner macros, assertions, and pass/fail tracking.
+ * The print backend (whal_Test_Puts) is implemented per-harness:
+ * - core: libc puts
+ * - hw: UART send
+ */
+
+/* Provided by each harness */
+void whal_Test_Puts(const char *s);
+
+/* Convert an integer to decimal in a static buffer (no libc dependency) */
+static inline const char *whal_Test_Itoa(int val)
+{
+ static char buf[16];
+ char *p = buf + sizeof(buf) - 1;
+ int neg = 0;
+
+ *p = '\0';
+ if (val < 0) {
+ neg = 1;
+ val = -val;
+ }
+
+ do {
+ *--p = '0' + (val % 10);
+ val /= 10;
+ } while (val);
+
+ if (neg)
+ *--p = '-';
+
+ return p;
+}
+
+/* Minimal printf: supports %d, %s, and %%. Calls whal_Test_Puts once. */
+static inline void whal_Test_Printf(const char *fmt, ...)
+{
+ static char buf[128];
+ char *out = buf;
+ char *end = buf + sizeof(buf) - 1;
+ va_list ap;
+
+ va_start(ap, fmt);
+ while (*fmt && out < end) {
+ if (*fmt != '%') {
+ *out++ = *fmt++;
+ continue;
+ }
+ fmt++;
+ if (*fmt == 'd') {
+ const char *s = whal_Test_Itoa(va_arg(ap, int));
+ while (*s && out < end)
+ *out++ = *s++;
+ }
+ else if (*fmt == 's') {
+ const char *s = va_arg(ap, const char *);
+ while (*s && out < end)
+ *out++ = *s++;
+ }
+ else if (*fmt == '%') {
+ *out++ = '%';
+ }
+ fmt++;
+ }
+ va_end(ap);
+
+ *out = '\0';
+ whal_Test_Puts(buf);
+}
+
+extern int g_whalTestPassed;
+extern int g_whalTestFailed;
+extern int g_whalTestSkipped;
+extern int g_whalTestCurFailed;
+extern int g_whalTestCurSkipped;
+
+#define WHAL_TEST_SUITE_START(name) \
+ do { \
+ whal_Test_Printf("\n=== " name " ===\n"); \
+ } while (0)
+
+#define WHAL_TEST_SUITE_END() \
+ do { } while (0)
+
+#define WHAL_SKIP() \
+ do { \
+ g_whalTestCurSkipped = 1; \
+ return; \
+ } while (0)
+
+#define WHAL_TEST(fn) \
+ do { \
+ g_whalTestCurFailed = 0; \
+ g_whalTestCurSkipped = 0; \
+ fn(); \
+ if (g_whalTestCurSkipped) { \
+ whal_Test_Printf(#fn ": SKIP\n"); \
+ g_whalTestSkipped++; \
+ } \
+ else if (g_whalTestCurFailed) { \
+ whal_Test_Printf(#fn ": FAIL\n"); \
+ g_whalTestFailed++; \
+ } \
+ else { \
+ whal_Test_Printf(#fn ": PASS\n"); \
+ g_whalTestPassed++; \
+ } \
+ } while (0)
+
+#define WHAL_ASSERT_EQ(a, b) \
+ do { \
+ int _a = (int)(a); \
+ int _b = (int)(b); \
+ if (_a != _b) { \
+ whal_Test_Printf(" ASSERT_EQ failed at %s:%d\n", \
+ __FILE__, __LINE__); \
+ whal_Test_Printf(" got: %d, expected: %d\n", \
+ _a, _b); \
+ g_whalTestCurFailed = 1; \
+ return; \
+ } \
+ } while (0)
+
+#define WHAL_ASSERT_NEQ(a, b) \
+ do { \
+ if ((a) == (b)) { \
+ whal_Test_Printf(" ASSERT_NEQ failed at %s:%d\n", \
+ __FILE__, __LINE__); \
+ g_whalTestCurFailed = 1; \
+ return; \
+ } \
+ } while (0)
+
+#define WHAL_ASSERT_MEM_EQ(a, b, len) \
+ do { \
+ const unsigned char *_a = (const unsigned char *)(a); \
+ const unsigned char *_b = (const unsigned char *)(b); \
+ for (size_t _i = 0; _i < (len); _i++) { \
+ if (_a[_i] != _b[_i]) { \
+ whal_Test_Printf(" ASSERT_MEM_EQ failed at " \
+ "%s:%d, byte offset: %d\n", \
+ __FILE__, __LINE__, (int)_i); \
+ g_whalTestCurFailed = 1; \
+ return; \
+ } \
+ } \
+ } while (0)
+
+#define WHAL_ASSERT_MEM_NEQ(a, b, len) \
+ do { \
+ const unsigned char *_a = (const unsigned char *)(a); \
+ const unsigned char *_b = (const unsigned char *)(b); \
+ int _differ = 0; \
+ for (size_t _i = 0; _i < (len); _i++) { \
+ if (_a[_i] != _b[_i]) { _differ = 1; break; } \
+ } \
+ if (!_differ) { \
+ whal_Test_Printf(" ASSERT_MEM_NEQ failed at %s:%d\n", \
+ __FILE__, __LINE__); \
+ g_whalTestCurFailed = 1; \
+ return; \
+ } \
+ } while (0)
+
+#define WHAL_TEST_SUMMARY() \
+ do { \
+ whal_Test_Printf("\n"); \
+ whal_Test_Printf("--- Results ---\n"); \
+ whal_Test_Printf("Skipped: %d\n", g_whalTestSkipped); \
+ whal_Test_Printf("Passed: %d\n", g_whalTestPassed); \
+ whal_Test_Printf("Failed: %d\n", g_whalTestFailed); \
+ } while (0)
+
+#endif /* WHAL_TEST_H */
diff --git a/tests/uart/test_uart.c b/tests/uart/test_uart.c
new file mode 100644
index 0000000..2cd2eb7
--- /dev/null
+++ b/tests/uart/test_uart.c
@@ -0,0 +1,24 @@
+#include
+#include "board.h"
+#include "test.h"
+
+static void Test_Uart_Api(void)
+{
+ uint8_t buf[8];
+
+ WHAL_ASSERT_EQ(whal_Uart_Init(NULL), WHAL_EINVAL);
+ WHAL_ASSERT_EQ(whal_Uart_Deinit(NULL), WHAL_EINVAL);
+ WHAL_ASSERT_EQ(whal_Uart_Send(NULL, buf, sizeof(buf)), WHAL_EINVAL);
+ WHAL_ASSERT_EQ(whal_Uart_Recv(NULL, buf, sizeof(buf)), WHAL_EINVAL);
+ WHAL_ASSERT_EQ(whal_Uart_SendAsync(NULL, buf, sizeof(buf)), WHAL_EINVAL);
+ WHAL_ASSERT_EQ(whal_Uart_RecvAsync(NULL, buf, sizeof(buf)), WHAL_EINVAL);
+ WHAL_ASSERT_EQ(whal_Uart_Send(&g_whalUart, NULL, 8), WHAL_EINVAL);
+ WHAL_ASSERT_EQ(whal_Uart_Recv(&g_whalUart, NULL, 8), WHAL_EINVAL);
+}
+
+void whal_Test_Uart(void)
+{
+ WHAL_TEST_SUITE_START("uart");
+ WHAL_TEST(Test_Uart_Api);
+ WHAL_TEST_SUITE_END();
+}
diff --git a/wolfHAL/bitops.h b/wolfHAL/bitops.h
new file mode 100644
index 0000000..240d31e
--- /dev/null
+++ b/wolfHAL/bitops.h
@@ -0,0 +1,31 @@
+#ifndef WHAL_BITOPS_H
+#define WHAL_BITOPS_H
+
+/*
+ * @file bitops.h
+ * @brief Bit manipulation helpers for register fields.
+ */
+
+#include
+
+/*
+ * @brief Create a bitmask of @p width bits starting at bit 0.
+ *
+ * Example: WHAL_BITMASK(4) = 0xF
+ */
+#define WHAL_BITMASK(width) \
+ ((1UL << (width)) - 1)
+
+/*
+ * @brief Encode a value into a bit field described by @p msk and @p pos.
+ */
+#define whal_SetBits(msk, pos, val) \
+ (((val) << (pos)) & (msk))
+
+/*
+ * @brief Extract a bit field value from a register using @p msk and @p pos.
+ */
+#define whal_GetBits(msk, pos, reg) \
+ (((reg) & (msk)) >> (pos))
+
+#endif /* WHAL_BITOPS_H */
diff --git a/wolfHAL/clock/clock.h b/wolfHAL/clock/clock.h
new file mode 100644
index 0000000..4b28dfe
--- /dev/null
+++ b/wolfHAL/clock/clock.h
@@ -0,0 +1,20 @@
+#ifndef WHAL_CLOCK_H
+#define WHAL_CLOCK_H
+
+#include
+
+/*
+ * @file clock.h
+ * @brief Clock device handle.
+ *
+ * wolfHAL doesn't provide a generic clock API — clock-tree shape and
+ * peripheral-gate semantics vary too much across vendors. Each chip's
+ * clock driver header (e.g. ) defines its
+ * own imperative bring-up and gate-toggle helpers. This file just
+ * provides a typed handle so those helpers have something to take.
+ */
+typedef struct {
+ const whal_Regmap regmap;
+} whal_Clock;
+
+#endif /* WHAL_CLOCK_H */
diff --git a/wolfHAL/clock/stm32l1_rcc.h b/wolfHAL/clock/stm32l1_rcc.h
new file mode 100644
index 0000000..f43d69b
--- /dev/null
+++ b/wolfHAL/clock/stm32l1_rcc.h
@@ -0,0 +1,138 @@
+#ifndef WHAL_STM32L1_RCC_H
+#define WHAL_STM32L1_RCC_H
+
+#include
+#include
+#include
+
+/*
+ * @file stm32l1_rcc.h
+ * @brief STM32L1 RCC (Reset and Clock Control) driver.
+ *
+ * Boards bring up the clock tree imperatively from Board_Init.
+ *
+ * Clock sources:
+ * MSI = multispeed internal (default after reset)
+ * HSI = 16 MHz internal RC
+ * HSE = 1-24 MHz external
+ * PLL = HSI or HSE * PLLMUL / PLLDIV (max 32 MHz)
+ */
+
+/*
+ * @brief System clock source selection (RCC_CFGR.SW).
+ */
+typedef enum {
+ WHAL_STM32L1_RCC_SYSCLK_SRC_MSI,
+ WHAL_STM32L1_RCC_SYSCLK_SRC_HSI,
+ WHAL_STM32L1_RCC_SYSCLK_SRC_HSE,
+ WHAL_STM32L1_RCC_SYSCLK_SRC_PLL,
+} whal_Stm32l1_Rcc_SysClockSrc;
+
+/*
+ * @brief PLL input selection.
+ */
+typedef enum {
+ WHAL_STM32L1_RCC_PLLSRC_HSI,
+ WHAL_STM32L1_RCC_PLLSRC_HSE,
+} whal_Stm32l1_Rcc_PllClockSrc;
+
+/*
+ * @brief PLL multiplication factor (RCC_CFGR.PLLMUL).
+ */
+typedef enum {
+ WHAL_STM32L1_RCC_PLLMUL_3 = 0,
+ WHAL_STM32L1_RCC_PLLMUL_4 = 1,
+ WHAL_STM32L1_RCC_PLLMUL_6 = 2,
+ WHAL_STM32L1_RCC_PLLMUL_8 = 3,
+ WHAL_STM32L1_RCC_PLLMUL_12 = 4,
+ WHAL_STM32L1_RCC_PLLMUL_16 = 5,
+ WHAL_STM32L1_RCC_PLLMUL_24 = 6,
+ WHAL_STM32L1_RCC_PLLMUL_32 = 7,
+ WHAL_STM32L1_RCC_PLLMUL_48 = 8,
+} whal_Stm32l1_Rcc_PllMul;
+
+/*
+ * @brief PLL output division factor (RCC_CFGR.PLLDIV).
+ */
+typedef enum {
+ WHAL_STM32L1_RCC_PLLDIV_2 = 1,
+ WHAL_STM32L1_RCC_PLLDIV_3 = 2,
+ WHAL_STM32L1_RCC_PLLDIV_4 = 3,
+} whal_Stm32l1_Rcc_PllDiv;
+
+/*
+ * @brief PLL configuration parameters.
+ */
+typedef struct {
+ whal_Stm32l1_Rcc_PllClockSrc clkSrc;
+ whal_Stm32l1_Rcc_PllMul pllmul;
+ whal_Stm32l1_Rcc_PllDiv plldiv;
+} whal_Stm32l1_Rcc_PllCfg;
+
+/*
+ * @brief Peripheral clock descriptor.
+ */
+typedef struct {
+ size_t regOffset;
+ size_t enableMask;
+ size_t enablePos;
+} whal_Stm32l1_Rcc_PeriphClk;
+
+/*
+ * @brief Cfg for EnableOsc/DisableOsc — on bit + ready bit.
+ */
+typedef struct {
+ size_t onReg;
+ size_t onMsk;
+ size_t rdyReg;
+ size_t rdyMsk;
+ size_t rdyPos;
+} whal_Stm32l1_Rcc_OscCfg;
+
+#define WHAL_STM32L1_RCC_HSI_CFG \
+ .onReg = 0x000, .onMsk = (1UL << 0), \
+ .rdyReg = 0x000, .rdyMsk = (1UL << 1), .rdyPos = 1
+#define WHAL_STM32L1_RCC_HSE_CFG \
+ .onReg = 0x000, .onMsk = (1UL << 16), \
+ .rdyReg = 0x000, .rdyMsk = (1UL << 17), .rdyPos = 17
+
+/*
+ * @brief Enable an oscillator (HSI/HSE). Blocks until ready.
+ */
+whal_Error whal_Stm32l1_Rcc_EnableOsc(whal_Clock *clkDev,
+ const whal_Stm32l1_Rcc_OscCfg *cfg);
+/*
+ * @brief Disable an oscillator.
+ */
+whal_Error whal_Stm32l1_Rcc_DisableOsc(whal_Clock *clkDev,
+ const whal_Stm32l1_Rcc_OscCfg *cfg);
+
+/*
+ * @brief Configure and enable the PLL. Caller must have the PLL source
+ * oscillator already enabled. Blocks until PLL is ready.
+ */
+whal_Error whal_Stm32l1_Rcc_EnablePll(whal_Clock *clkDev,
+ const whal_Stm32l1_Rcc_PllCfg *cfg);
+/*
+ * @brief Disable the PLL.
+ */
+whal_Error whal_Stm32l1_Rcc_DisablePll(whal_Clock *clkDev);
+
+/*
+ * @brief Switch SYSCLK to the given source. Blocks until SWS confirms.
+ */
+whal_Error whal_Stm32l1_Rcc_SetSysClock(whal_Clock *clkDev,
+ whal_Stm32l1_Rcc_SysClockSrc src);
+
+/*
+ * @brief Enable a peripheral clock.
+ */
+whal_Error whal_Stm32l1_Rcc_EnablePeriphClk(whal_Clock *clkDev,
+ const whal_Stm32l1_Rcc_PeriphClk *clk);
+/*
+ * @brief Disable a peripheral clock.
+ */
+whal_Error whal_Stm32l1_Rcc_DisablePeriphClk(whal_Clock *clkDev,
+ const whal_Stm32l1_Rcc_PeriphClk *clk);
+
+#endif /* WHAL_STM32L1_RCC_H */
diff --git a/wolfHAL/clock/stm32wb_rcc.h b/wolfHAL/clock/stm32wb_rcc.h
new file mode 100644
index 0000000..7a597af
--- /dev/null
+++ b/wolfHAL/clock/stm32wb_rcc.h
@@ -0,0 +1,174 @@
+#ifndef WHAL_STM32WB_RCC_H
+#define WHAL_STM32WB_RCC_H
+
+#include
+#include
+#include
+
+/*
+ * @file stm32wb_rcc.h
+ * @brief STM32WB RCC (Reset and Clock Control) driver.
+ *
+ * Boards bring up the clock tree imperatively from Board_Init by calling
+ * the helpers below in order:
+ *
+ * whal_Stm32wb_Rcc_EnableMsi(...);
+ * whal_Stm32wb_Rcc_EnablePll(..., &pllCfg);
+ * whal_Stm32wb_Rcc_EnableOsc(..., &hsi48Cfg);
+ * whal_Stm32wb_Rcc_SetSysClock(..., WHAL_STM32WB_RCC_SYSCLK_SRC_PLL);
+ * for each peripheral clock: whal_Stm32wb_Rcc_EnablePeriphClk(...);
+ *
+ * The driver does not provide a declarative tree or an Init/Deinit
+ * walker — boards know their own bring-up order.
+ */
+
+/*
+ * @brief System clock source selection (RCC_CFGR.SW).
+ */
+typedef enum {
+ WHAL_STM32WB_RCC_SYSCLK_SRC_MSI,
+ WHAL_STM32WB_RCC_SYSCLK_SRC_HSI16,
+ WHAL_STM32WB_RCC_SYSCLK_SRC_HSE,
+ WHAL_STM32WB_RCC_SYSCLK_SRC_PLL,
+} whal_Stm32wb_Rcc_SysClockSrc;
+
+/*
+ * @brief PLL input clock source selection (RCC_PLLCFGR.PLLSRC).
+ */
+typedef enum {
+ WHAL_STM32WB_RCC_PLLCLK_SRC_NONE,
+ WHAL_STM32WB_RCC_PLLCLK_SRC_MSI,
+ WHAL_STM32WB_RCC_PLLCLK_SRC_HSI16,
+ WHAL_STM32WB_RCC_PLLCLK_SRC_HSE,
+} whal_Stm32wb_Rcc_PllClockSrc;
+
+/*
+ * @brief MSI oscillator frequency range (RCC_CR.MSIRANGE).
+ */
+typedef enum {
+ WHAL_STM32WB_RCC_MSIRANGE_100kHz,
+ WHAL_STM32WB_RCC_MSIRANGE_200kHz,
+ WHAL_STM32WB_RCC_MSIRANGE_400kHz,
+ WHAL_STM32WB_RCC_MSIRANGE_800kHz,
+ WHAL_STM32WB_RCC_MSIRANGE_1MHz,
+ WHAL_STM32WB_RCC_MSIRANGE_2MHz,
+ WHAL_STM32WB_RCC_MSIRANGE_4MHz,
+ WHAL_STM32WB_RCC_MSIRANGE_8MHz,
+ WHAL_STM32WB_RCC_MSIRANGE_16MHz,
+ WHAL_STM32WB_RCC_MSIRANGE_24MHz,
+ WHAL_STM32WB_RCC_MSIRANGE_32MHz,
+ WHAL_STM32WB_RCC_MSIRANGE_48MHz,
+} whal_Stm32wb_Rcc_MsiRange;
+
+/*
+ * @brief Peripheral clock descriptor (RCC *ENR enable bit).
+ */
+typedef struct {
+ size_t regOffset;
+ size_t enableMask;
+ size_t enablePos;
+} whal_Stm32wb_Rcc_PeriphClk;
+
+/*
+ * @brief Cfg for EnableOsc/DisableOsc — on bit + ready bit. Boards
+ * construct one with the WHAL_STM32WB_RCC_*_CFG macros below.
+ * NOTE: LSE assumes PWR_CR1.DBP has been set by the caller.
+ */
+typedef struct {
+ size_t onReg;
+ size_t onMsk;
+ size_t rdyReg;
+ size_t rdyMsk;
+ size_t rdyPos;
+} whal_Stm32wb_Rcc_OscCfg;
+
+#define WHAL_STM32WB_RCC_HSI_CFG \
+ .onReg = 0x000, .onMsk = (1UL << 8), \
+ .rdyReg = 0x000, .rdyMsk = (1UL << 10), .rdyPos = 10
+#define WHAL_STM32WB_RCC_HSE_CFG \
+ .onReg = 0x000, .onMsk = (1UL << 16), \
+ .rdyReg = 0x000, .rdyMsk = (1UL << 17), .rdyPos = 17
+#define WHAL_STM32WB_RCC_HSI48_CFG \
+ .onReg = 0x098, .onMsk = (1UL << 0), \
+ .rdyReg = 0x098, .rdyMsk = (1UL << 1), .rdyPos = 1
+#define WHAL_STM32WB_RCC_LSI_CFG \
+ .onReg = 0x094, .onMsk = (1UL << 0), \
+ .rdyReg = 0x094, .rdyMsk = (1UL << 1), .rdyPos = 1
+#define WHAL_STM32WB_RCC_LSE_CFG \
+ .onReg = 0x090, .onMsk = (1UL << 0), \
+ .rdyReg = 0x090, .rdyMsk = (1UL << 1), .rdyPos = 1
+
+/*
+ * @brief PLL configuration parameters.
+ *
+ * The PLL output frequency is calculated as:
+ * f_vco = (f_input / m) * n
+ * f_pllr = f_vco / r (main PLL output, used for SYSCLK)
+ * f_pllq = f_vco / q (used for USB, RNG, etc.)
+ * f_pllp = f_vco / p (used for SAI, etc.)
+ *
+ * Constraints:
+ * - VCO frequency must be 96-344 MHz
+ * - PLL input (f_input / m) must be 2.66-16 MHz
+ * - n: 8-127
+ * - m: 1-8 (register value 0-7)
+ * - r, q: 2, 4, 6, 8 (register value 0-3 maps to div by 2/4/6/8)
+ * - p: 2-32 (register value 1-31, 0 reserved)
+ */
+typedef struct {
+ whal_Stm32wb_Rcc_PllClockSrc clkSrc;
+ uint8_t r;
+ uint8_t q;
+ uint8_t p;
+ uint8_t n;
+ uint8_t m;
+} whal_Stm32wb_Rcc_PllCfg;
+
+/*
+ * @brief Enable an oscillator (HSI/HSE/HSI48/LSI/LSE) and block until
+ * the ready bit is set.
+ */
+whal_Error whal_Stm32wb_Rcc_EnableOsc(whal_Clock *clkDev,
+ const whal_Stm32wb_Rcc_OscCfg *cfg);
+/*
+ * @brief Disable an oscillator.
+ */
+whal_Error whal_Stm32wb_Rcc_DisableOsc(whal_Clock *clkDev,
+ const whal_Stm32wb_Rcc_OscCfg *cfg);
+
+/*
+ * @brief Enable the MSI oscillator at the given range. Blocks until ready.
+ */
+whal_Error whal_Stm32wb_Rcc_EnableMsi(whal_Clock *clkDev,
+ whal_Stm32wb_Rcc_MsiRange range);
+
+/*
+ * @brief Configure the PLL dividers/source and enable it. Blocks until
+ * the PLL ready bit is set.
+ */
+whal_Error whal_Stm32wb_Rcc_EnablePll(whal_Clock *clkDev,
+ const whal_Stm32wb_Rcc_PllCfg *cfg);
+/*
+ * @brief Disable the PLL.
+ */
+whal_Error whal_Stm32wb_Rcc_DisablePll(whal_Clock *clkDev);
+
+/*
+ * @brief Switch SYSCLK to the given source. Blocks until RCC_CFGR.SWS
+ * reflects the new source.
+ */
+whal_Error whal_Stm32wb_Rcc_SetSysClock(whal_Clock *clkDev,
+ whal_Stm32wb_Rcc_SysClockSrc src);
+
+/*
+ * @brief Enable a peripheral clock.
+ */
+whal_Error whal_Stm32wb_Rcc_EnablePeriphClk(whal_Clock *clkDev,
+ const whal_Stm32wb_Rcc_PeriphClk *clk);
+/*
+ * @brief Disable a peripheral clock.
+ */
+whal_Error whal_Stm32wb_Rcc_DisablePeriphClk(whal_Clock *clkDev,
+ const whal_Stm32wb_Rcc_PeriphClk *clk);
+
+#endif /* WHAL_STM32WB_RCC_H */
diff --git a/wolfHAL/endian.h b/wolfHAL/endian.h
new file mode 100644
index 0000000..2b06de3
--- /dev/null
+++ b/wolfHAL/endian.h
@@ -0,0 +1,67 @@
+#ifndef WHAL_ENDIAN_H
+#define WHAL_ENDIAN_H
+
+/*
+ * @file endian.h
+ * @brief Byte-order conversion helpers.
+ */
+
+#include
+#include
+
+/*
+ * @brief Load a 32-bit value from a big-endian byte array.
+ */
+static inline uint32_t whal_LoadBe32(const uint8_t *p)
+{
+ return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) |
+ ((uint32_t)p[2] << 8) | p[3];
+}
+
+/*
+ * @brief Store a 32-bit value into a big-endian byte array.
+ */
+static inline void whal_StoreBe32(uint8_t *p, uint32_t v)
+{
+ p[0] = (uint8_t)(v >> 24);
+ p[1] = (uint8_t)(v >> 16);
+ p[2] = (uint8_t)(v >> 8);
+ p[3] = (uint8_t)v;
+}
+
+/*
+ * @brief Load n bytes from a byte array into a 32-bit big-endian value.
+ *
+ * The first byte becomes the MSB and the remaining (4-n) bytes are zero.
+ * Equivalent to whal_LoadBe32 when n == 4.
+ *
+ * @param p Source byte array.
+ * @param n Number of bytes to load (must be 0..4).
+ */
+static inline uint32_t whal_LoadBe32Partial(const uint8_t *p, size_t n)
+{
+ uint32_t v = 0;
+ size_t i;
+ for (i = 0; i < n; i++)
+ v |= (uint32_t)p[i] << (24 - i * 8);
+ return v;
+}
+
+/*
+ * @brief Load n bytes from a byte array into a 32-bit little-endian value.
+ *
+ * The first byte becomes the LSB and the remaining (4-n) bytes are zero.
+ *
+ * @param p Source byte array.
+ * @param n Number of bytes to load (must be 0..4).
+ */
+static inline uint32_t whal_LoadLe32Partial(const uint8_t *p, size_t n)
+{
+ uint32_t v = 0;
+ size_t i;
+ for (i = 0; i < n; i++)
+ v |= (uint32_t)p[i] << (i * 8);
+ return v;
+}
+
+#endif /* WHAL_ENDIAN_H */
diff --git a/wolfHAL/error.h b/wolfHAL/error.h
new file mode 100644
index 0000000..49acb67
--- /dev/null
+++ b/wolfHAL/error.h
@@ -0,0 +1,32 @@
+#ifndef WHAL_ERROR_H
+#define WHAL_ERROR_H
+
+
+/*
+ * @file error.h
+ * @brief Shared error codes for wolfHAL APIs.
+ */
+
+/* Signed status code type used by wolfHAL. */
+typedef int whal_Error;
+
+enum {
+ /* Operation completed successfully. */
+ WHAL_SUCCESS = 0,
+ /* Invalid argument (null pointer, bad configuration). */
+ WHAL_EINVAL = -4000,
+ /* Resource not ready or busy. */
+ WHAL_ENOTREADY = -4001,
+ /* Hardware device error. */
+ WHAL_EHARDWARE = -4002,
+ /* Operation timed out. */
+ WHAL_ETIMEOUT = -4003,
+ /* Operation or argument not supported by the selected driver/hardware
+ * (use for requests that are valid in general but this implementation
+ * cannot fulfill — e.g., hardware lacks the feature or the specific
+ * parameter combination isn't supported). For universally invalid
+ * arguments (null pointer, out-of-range enum) return WHAL_EINVAL. */
+ WHAL_ENOTSUP = -4004,
+};
+
+#endif /* WHAL_ERROR_H */
diff --git a/wolfHAL/flash/flash.h b/wolfHAL/flash/flash.h
new file mode 100644
index 0000000..42c4a32
--- /dev/null
+++ b/wolfHAL/flash/flash.h
@@ -0,0 +1,128 @@
+#ifndef WHAL_FLASH_H
+#define WHAL_FLASH_H
+
+#include
+#include
+#include
+#include
+
+/*
+ * @file flash.h
+ * @brief Generic flash abstraction and driver interface.
+ */
+
+typedef struct whal_Flash whal_Flash;
+
+/*
+ * @brief Driver vtable for flash devices.
+ */
+typedef struct {
+ /* Bring the flash peripheral into a usable state. */
+ whal_Error (*Init)(whal_Flash *flashDev);
+ /* Release any resources owned by the flash driver. */
+ whal_Error (*Deinit)(whal_Flash *flashDev);
+ /* Lock a flash region to prevent modification. */
+ whal_Error (*Lock)(whal_Flash *flashDev, size_t addr, size_t len);
+ /* Unlock a flash region to allow modification. */
+ whal_Error (*Unlock)(whal_Flash *flashDev, size_t addr, size_t len);
+ /* Read data from flash into a buffer. */
+ whal_Error (*Read)(whal_Flash *flashDev, size_t addr, void *data, size_t dataSz);
+ /* Program a region of flash starting at @p addr. */
+ whal_Error (*Write)(whal_Flash *flashDev, size_t addr, const void *data, size_t dataSz);
+ /* Erase a flash range starting at @p addr. */
+ whal_Error (*Erase)(whal_Flash *flashDev, size_t addr, size_t dataSz);
+} whal_FlashDriver;
+
+/*
+ * @brief Flash device instance tying configuration to a driver implementation.
+ */
+struct whal_Flash {
+ const whal_Regmap regmap;
+ const whal_FlashDriver *driver;
+ void *cfg;
+};
+
+/*
+ * @brief Initialize a flash device and its driver.
+ *
+ * @param flashDev Flash instance to initialize.
+ *
+ * @retval WHAL_SUCCESS Driver-specific init completed.
+ * @retval WHAL_EINVAL Null pointer.
+ * @retval WHAL_ENOTSUP Operation not implemented by this driver.
+ */
+whal_Error whal_Flash_Init(whal_Flash *flashDev);
+/*
+ * @brief Deinitialize a flash device.
+ *
+ * @param flashDev Flash instance to deinitialize.
+ *
+ * @retval WHAL_SUCCESS Driver-specific deinit completed.
+ * @retval WHAL_EINVAL Null pointer.
+ * @retval WHAL_ENOTSUP Operation not implemented by this driver.
+ */
+whal_Error whal_Flash_Deinit(whal_Flash *flashDev);
+/*
+ * @brief Lock a region of flash to prevent modification.
+ *
+ * @param flashDev Flash instance to lock.
+ * @param addr Byte address in flash to lock.
+ * @param len Number of bytes to lock.
+ *
+ * @retval WHAL_SUCCESS Lock applied.
+ * @retval WHAL_EINVAL Null pointer.
+ * @retval WHAL_ENOTSUP Operation not implemented by this driver.
+ */
+whal_Error whal_Flash_Lock(whal_Flash *flashDev, size_t addr, size_t len);
+/*
+ * @brief Unlock a region of flash to allow modification.
+ *
+ * @param flashDev Flash instance to unlock.
+ * @param addr Byte address in flash to unlock.
+ * @param len Number of bytes to unlock.
+ *
+ * @retval WHAL_SUCCESS Unlock applied.
+ * @retval WHAL_EINVAL Null pointer.
+ * @retval WHAL_ENOTSUP Operation not implemented by this driver.
+ */
+whal_Error whal_Flash_Unlock(whal_Flash *flashDev, size_t addr, size_t len);
+/*
+ * @brief Read data from flash into a buffer.
+ *
+ * @param flashDev Flash instance to read from.
+ * @param addr Byte address in flash to read.
+ * @param data Destination buffer.
+ * @param dataSz Number of bytes to read.
+ *
+ * @retval WHAL_SUCCESS Read completed.
+ * @retval WHAL_EINVAL Null pointer.
+ * @retval WHAL_ENOTSUP Operation not implemented by this driver.
+ */
+whal_Error whal_Flash_Read(whal_Flash *flashDev, size_t addr, void *data, size_t dataSz);
+/*
+ * @brief Write data into flash.
+ *
+ * @param flashDev Flash instance to program.
+ * @param addr Byte address in flash to start writing.
+ * @param data Buffer containing data to write.
+ * @param dataSz Number of bytes to write.
+ *
+ * @retval WHAL_SUCCESS Write accepted or completed.
+ * @retval WHAL_EINVAL Null pointer or bad arguments.
+ * @retval WHAL_ENOTSUP Operation not implemented by this driver.
+ */
+whal_Error whal_Flash_Write(whal_Flash *flashDev, size_t addr, const void *data, size_t dataSz);
+/*
+ * @brief Erase a region of flash.
+ *
+ * @param flashDev Flash instance to erase.
+ * @param addr Byte address in flash where erasure starts.
+ * @param dataSz Number of bytes (or sector-aligned size) to erase.
+ *
+ * @retval WHAL_SUCCESS Erase accepted or completed.
+ * @retval WHAL_EINVAL Null pointer.
+ * @retval WHAL_ENOTSUP Operation not implemented by this driver.
+ */
+whal_Error whal_Flash_Erase(whal_Flash *flashDev, size_t addr, size_t dataSz);
+
+#endif /* WHAL_FLASH_H */
diff --git a/wolfHAL/flash/stm32l1_flash.h b/wolfHAL/flash/stm32l1_flash.h
new file mode 100644
index 0000000..4486c45
--- /dev/null
+++ b/wolfHAL/flash/stm32l1_flash.h
@@ -0,0 +1,167 @@
+#ifndef WHAL_STM32L1_FLASH_H
+#define WHAL_STM32L1_FLASH_H
+
+#include
+#include
+#include
+#include
+
+/*
+ * @file stm32l1_flash.h
+ * @brief STM32L1 flash driver configuration.
+ *
+ * The STM32L1 flash uses a PECR-based unlock model:
+ * 1. Unlock FLASH_PECR via FLASH_PEKEYR (PEKEY1=0x89ABCDEF, PEKEY2=0x02030405)
+ * 2. Unlock program memory via FLASH_PRGKEYR (PRGKEY1=0x8C9DAEBF, PRGKEY2=0x13141516)
+ *
+ * Register layout:
+ * FLASH_ACR = 0x00 (LATENCY, ACC64, PRFTEN, RUN_PD, SLEEP_PD)
+ * FLASH_PECR = 0x04 (PELOCK, PRGLOCK, OPTLOCK, PROG, ERASE, FPRG, DATA, FTDW)
+ * FLASH_PDKEYR = 0x08
+ * FLASH_PEKEYR = 0x0C
+ * FLASH_PRGKEYR = 0x10
+ * FLASH_OPTKEYR = 0x14
+ * FLASH_SR = 0x18 (BSY, EOP, ENDHV, READY, WRPERR, PGAERR, SIZERR, OPTVERR)
+ * FLASH_OBR = 0x1C
+ * FLASH_WRPR1 = 0x20
+ *
+ * Programming:
+ * - Write alignment: 4 bytes (word)
+ * - Erase granularity: 256 bytes (page)
+ * - Fast word write: unlock PECR + PRGKEYR, write 32-bit word to flash address
+ * - Page erase: set ERASE+PROG in PECR, write 0x00000000 to first word of page
+ */
+
+/*
+ * @brief STM32L1 flash driver configuration.
+ */
+typedef struct whal_Stm32l1_Flash_Cfg {
+ size_t startAddr; /* Flash region start address */
+ size_t size; /* Flash region size in bytes */
+ whal_Timeout *timeout; /* Optional timeout for poll loops */
+} whal_Stm32l1_Flash_Cfg;
+
+/*
+ * @brief Flash access latency values for STM32L1.
+ *
+ * 0 WS: HCLK <= 16 MHz (voltage range 1 / 2), HCLK <= 8 MHz (range 3)
+ * 1 WS: 16 < HCLK <= 32 MHz (range 1), 8 < HCLK <= 16 MHz (range 2/3)
+ */
+typedef enum {
+ WHAL_STM32L1_FLASH_LATENCY_0 = 0,
+ WHAL_STM32L1_FLASH_LATENCY_1 = 1,
+} whal_Stm32l1_Flash_Latency;
+
+#ifndef WHAL_CFG_STM32L1_FLASH_DIRECT_API_MAPPING
+/*
+ * @brief Driver instance for the STM32L1 embedded flash controller.
+ */
+extern const whal_FlashDriver whal_Stm32l1_Flash_Driver;
+
+/*
+ * @brief Initialize the STM32L1 flash driver. Validates cfg.
+ *
+ * @param flashDev Flash device instance.
+ *
+ * @retval WHAL_SUCCESS Driver is ready.
+ * @retval WHAL_EINVAL Null pointer or missing cfg.
+ */
+whal_Error whal_Stm32l1_Flash_Init(whal_Flash *flashDev);
+
+/*
+ * @brief Deinitialize the STM32L1 flash driver.
+ *
+ * @param flashDev Flash device instance.
+ *
+ * @retval WHAL_SUCCESS Driver is deinitialized.
+ * @retval WHAL_EINVAL Null pointer.
+ */
+whal_Error whal_Stm32l1_Flash_Deinit(whal_Flash *flashDev);
+
+/*
+ * @brief Re-lock program memory (sets PRGLOCK in FLASH_PECR).
+ *
+ * @param flashDev Flash device instance.
+ * @param addr Range start (ignored — flash is globally locked).
+ * @param len Range length (ignored).
+ *
+ * @retval WHAL_SUCCESS Flash is locked.
+ * @retval WHAL_EINVAL Null pointer.
+ */
+whal_Error whal_Stm32l1_Flash_Lock(whal_Flash *flashDev, size_t addr, size_t len);
+
+/*
+ * @brief Unlock program memory via PEKEYR + PRGKEYR sequences.
+ *
+ * @param flashDev Flash device instance.
+ * @param addr Range start (ignored — flash is globally unlocked).
+ * @param len Range length (ignored).
+ *
+ * @retval WHAL_SUCCESS Flash is unlocked.
+ * @retval WHAL_EINVAL Null pointer.
+ * @retval WHAL_EHARDWARE Key sequence rejected.
+ */
+whal_Error whal_Stm32l1_Flash_Unlock(whal_Flash *flashDev, size_t addr, size_t len);
+
+/*
+ * @brief Read `dataSz` bytes from flash at `addr` into `data`.
+ *
+ * @param flashDev Flash device instance.
+ * @param addr Source address within the flash region.
+ * @param data Destination buffer.
+ * @param dataSz Number of bytes to read.
+ *
+ * @retval WHAL_SUCCESS Read completed.
+ * @retval WHAL_EINVAL Null pointer or address out of region.
+ */
+whal_Error whal_Stm32l1_Flash_Read(whal_Flash *flashDev, size_t addr, void *data,
+ size_t dataSz);
+
+/*
+ * @brief Program `dataSz` bytes from `data` to flash at `addr`. Address and
+ * size must be 4-byte (word) aligned.
+ *
+ * @param flashDev Flash device instance.
+ * @param addr Destination address within the flash region.
+ * @param data Source buffer.
+ * @param dataSz Number of bytes to write.
+ *
+ * @retval WHAL_SUCCESS Write completed.
+ * @retval WHAL_EINVAL Null pointer, misaligned address/size, or address out of region.
+ * @retval WHAL_ETIMEOUT BSY did not clear within the configured timeout.
+ * @retval WHAL_EHARDWARE Programming error reported in FLASH_SR.
+ */
+whal_Error whal_Stm32l1_Flash_Write(whal_Flash *flashDev, size_t addr,
+ const void *data, size_t dataSz);
+
+/*
+ * @brief Erase one or more 256-byte pages covering [addr, addr+dataSz).
+ *
+ * @param flashDev Flash device instance.
+ * @param addr First page address.
+ * @param dataSz Number of bytes to erase (rounded up to whole pages).
+ *
+ * @retval WHAL_SUCCESS Erase completed.
+ * @retval WHAL_EINVAL Null pointer or address out of region.
+ * @retval WHAL_ETIMEOUT BSY did not clear within the configured timeout.
+ * @retval WHAL_EHARDWARE Erase error reported in FLASH_SR.
+ */
+whal_Error whal_Stm32l1_Flash_Erase(whal_Flash *flashDev, size_t addr,
+ size_t dataSz);
+#endif /* !WHAL_CFG_STM32L1_FLASH_DIRECT_API_MAPPING */
+
+/*
+ * @brief Set flash access latency (FLASH_ACR.LATENCY).
+ *
+ * Enables 64-bit access (ACC64) and prefetch (PRFTEN) as required by RM0038
+ * §3.9.1 before programming the LATENCY bit, and polls until LATENCY matches
+ * the requested value. Must be called by the board before increasing SYSCLK
+ * beyond 16 MHz.
+ *
+ * @param latency Desired latency (LATENCY_0 or LATENCY_1).
+ *
+ * @retval WHAL_SUCCESS Latency updated.
+ */
+whal_Error whal_Stm32l1_Flash_Ext_SetLatency(whal_Stm32l1_Flash_Latency latency);
+
+#endif /* WHAL_STM32L1_FLASH_H */
diff --git a/wolfHAL/flash/stm32wb_flash.h b/wolfHAL/flash/stm32wb_flash.h
new file mode 100644
index 0000000..a018afe
--- /dev/null
+++ b/wolfHAL/flash/stm32wb_flash.h
@@ -0,0 +1,146 @@
+#ifndef WHAL_STM32WB_FLASH_H
+#define WHAL_STM32WB_FLASH_H
+
+#include
+#include
+
+/*
+ * @file stm32wb_flash.h
+ * @brief STM32WB flash driver configuration and helpers.
+ *
+ * The STM32WB embedded flash provides:
+ * - Up to 1 MB of flash memory organized in 4 KB pages
+ * - Double-word (64-bit) programming
+ * - Page erase and mass erase operations
+ * - Read-while-write capability on different banks
+ * - Configurable wait states based on CPU frequency
+ *
+ * Flash must be unlocked before write/erase operations and locked
+ * afterward for protection. Wait states must be configured appropriately
+ * for the system clock frequency.
+ */
+
+/*
+ * @brief Flash device configuration.
+ */
+typedef struct whal_Stm32wb_Flash_Cfg {
+ size_t startAddr; /* Flash base address (typically 0x08000000) */
+ size_t size; /* Flash size in bytes */
+ whal_Timeout *timeout;
+} whal_Stm32wb_Flash_Cfg;
+
+/*
+ * @brief Flash access latency (wait states).
+ *
+ * The number of wait states must be configured based on the CPU frequency
+ * and supply voltage. Insufficient wait states will cause flash read errors.
+ *
+ * Typical settings at VOS1 (1.2V):
+ * - 0 WS: up to 18 MHz
+ * - 1 WS: up to 36 MHz
+ * - 2 WS: up to 54 MHz
+ * - 3 WS: up to 64 MHz
+ */
+typedef enum whal_Stm32wb_Flash_Latency {
+ WHAL_STM32WB_FLASH_LATENCY_0, /* 0 wait states */
+ WHAL_STM32WB_FLASH_LATENCY_1, /* 1 wait state */
+ WHAL_STM32WB_FLASH_LATENCY_2, /* 2 wait states */
+ WHAL_STM32WB_FLASH_LATENCY_3, /* 3 wait states */
+} whal_Stm32wb_Flash_Latency;
+
+#ifndef WHAL_CFG_STM32WB_FLASH_DIRECT_API_MAPPING
+/*
+ * @brief Driver instance for STM32 flash.
+ */
+extern const whal_FlashDriver whal_Stm32wb_Flash_Driver;
+
+/*
+ * @brief Initialize the STM32 flash interface.
+ *
+ * @param flashDev Flash device instance to initialize.
+ *
+ * @retval WHAL_SUCCESS Initialization completed.
+ * @retval WHAL_EINVAL Invalid arguments.
+ */
+whal_Error whal_Stm32wb_Flash_Init(whal_Flash *flashDev);
+/*
+ * @brief Deinitialize the STM32 flash interface.
+ *
+ * @param flashDev Flash device instance to deinitialize.
+ *
+ * @retval WHAL_SUCCESS Deinit completed.
+ * @retval WHAL_EINVAL Invalid arguments.
+ */
+whal_Error whal_Stm32wb_Flash_Deinit(whal_Flash *flashDev);
+/*
+ * @brief Lock a flash range.
+ *
+ * @param flashDev Flash device instance.
+ * @param addr Flash address to lock.
+ * @param len Number of bytes to lock.
+ *
+ * @retval WHAL_SUCCESS Lock applied.
+ * @retval WHAL_EINVAL Invalid arguments.
+ */
+whal_Error whal_Stm32wb_Flash_Lock(whal_Flash *flashDev, size_t addr, size_t len);
+/*
+ * @brief Unlock a flash range.
+ *
+ * @param flashDev Flash device instance.
+ * @param addr Flash address to unlock.
+ * @param len Number of bytes to unlock.
+ *
+ * @retval WHAL_SUCCESS Unlock applied.
+ * @retval WHAL_EINVAL Invalid arguments.
+ */
+whal_Error whal_Stm32wb_Flash_Unlock(whal_Flash *flashDev, size_t addr, size_t len);
+/*
+ * @brief Read data from flash into a buffer.
+ *
+ * @param flashDev Flash device instance.
+ * @param addr Flash address to read.
+ * @param data Destination buffer.
+ * @param dataSz Number of bytes to read.
+ *
+ * @retval WHAL_SUCCESS Read completed.
+ * @retval WHAL_EINVAL Invalid arguments.
+ */
+whal_Error whal_Stm32wb_Flash_Read(whal_Flash *flashDev, size_t addr, void *data,
+ size_t dataSz);
+/*
+ * @brief Write a block of data to flash.
+ *
+ * @param flashDev Flash device instance.
+ * @param addr Flash address to program.
+ * @param data Buffer to program.
+ * @param dataSz Number of bytes to program.
+ *
+ * @retval WHAL_SUCCESS Program completed.
+ * @retval WHAL_EINVAL Invalid arguments.
+ */
+whal_Error whal_Stm32wb_Flash_Write(whal_Flash *flashDev, size_t addr, const void *data,
+ size_t dataSz);
+/*
+ * @brief Erase a flash range.
+ *
+ * @param flashDev Flash device instance.
+ * @param addr Flash address to start erasing.
+ * @param dataSz Number of bytes to erase.
+ *
+ * @retval WHAL_SUCCESS Erase completed.
+ * @retval WHAL_EINVAL Invalid arguments.
+ */
+whal_Error whal_Stm32wb_Flash_Erase(whal_Flash *flashDev, size_t addr, size_t dataSz);
+#endif /* !WHAL_CFG_STM32WB_FLASH_DIRECT_API_MAPPING */
+/*
+ * @brief Update flash latency wait states.
+ *
+ * @param flashDev Flash device instance.
+ * @param latency Latency setting to apply.
+ *
+ * @retval WHAL_SUCCESS Latency updated.
+ * @retval WHAL_EINVAL Invalid arguments.
+ */
+whal_Error whal_Stm32wb_Flash_Ext_SetLatency(whal_Flash *flashDev, enum whal_Stm32wb_Flash_Latency latency);
+
+#endif /* WHAL_STM32WB_FLASH_H */
diff --git a/wolfHAL/gpio/gpio.h b/wolfHAL/gpio/gpio.h
new file mode 100644
index 0000000..94d789c
--- /dev/null
+++ b/wolfHAL/gpio/gpio.h
@@ -0,0 +1,83 @@
+#ifndef WHAL_GPIO_H
+#define WHAL_GPIO_H
+
+#include
+#include
+#include
+
+/*
+ * @file gpio.h
+ * @brief Generic GPIO abstraction and driver interface.
+ */
+
+typedef struct whal_Gpio whal_Gpio;
+
+/*
+ * @brief Driver vtable for GPIO devices.
+ */
+typedef struct {
+ /* Initialize GPIO hardware and configured pins. */
+ whal_Error (*Init)(whal_Gpio *gpioDev);
+ /* Deinitialize GPIO hardware. */
+ whal_Error (*Deinit)(whal_Gpio *gpioDev);
+ /* Read a pin value. */
+ whal_Error (*Get)(whal_Gpio *gpioDev, size_t pin, size_t *value);
+ /* Write a pin value. */
+ whal_Error (*Set)(whal_Gpio *gpioDev, size_t pin, size_t value);
+} whal_GpioDriver;
+
+/*
+ * @brief GPIO device instance containing driver and configuration data.
+ */
+struct whal_Gpio {
+ const whal_Regmap regmap;
+ const whal_GpioDriver *driver;
+ const void *cfg;
+};
+
+/*
+ * @brief Initialize a GPIO device and its pins.
+ *
+ * @param gpioDev GPIO instance to initialize.
+ *
+ * @retval WHAL_SUCCESS Driver-specific init completed.
+ * @retval WHAL_EINVAL Null pointer.
+ * @retval WHAL_ENOTSUP Operation not implemented by this driver.
+ */
+whal_Error whal_Gpio_Init(whal_Gpio *gpioDev);
+/*
+ * @brief Deinitialize a GPIO device.
+ *
+ * @param gpioDev GPIO instance to deinitialize.
+ *
+ * @retval WHAL_SUCCESS Driver-specific deinit completed.
+ * @retval WHAL_EINVAL Null pointer.
+ * @retval WHAL_ENOTSUP Operation not implemented by this driver.
+ */
+whal_Error whal_Gpio_Deinit(whal_Gpio *gpioDev);
+/*
+ * @brief Read the state of a pin.
+ *
+ * @param gpioDev GPIO instance containing the pin.
+ * @param pin Pin index into the configured pin table.
+ * @param value Storage for the sampled pin value.
+ *
+ * @retval WHAL_SUCCESS Pin value stored in @p value.
+ * @retval WHAL_EINVAL Null pointer or bad pin.
+ * @retval WHAL_ENOTSUP Operation not implemented by this driver.
+ */
+whal_Error whal_Gpio_Get(whal_Gpio *gpioDev, size_t pin, size_t *value);
+/*
+ * @brief Set the state of a pin.
+ *
+ * @param gpioDev GPIO instance containing the pin.
+ * @param pin Pin index into the configured pin table.
+ * @param value Output value to drive (typically 0 or 1).
+ *
+ * @retval WHAL_SUCCESS Pin updated.
+ * @retval WHAL_EINVAL Null pointer or bad pin.
+ * @retval WHAL_ENOTSUP Operation not implemented by this driver.
+ */
+whal_Error whal_Gpio_Set(whal_Gpio *gpioDev, size_t pin, size_t value);
+
+#endif /* WHAL_GPIO_H */
diff --git a/wolfHAL/gpio/stm32l1_gpio.h b/wolfHAL/gpio/stm32l1_gpio.h
new file mode 100644
index 0000000..5462a0d
--- /dev/null
+++ b/wolfHAL/gpio/stm32l1_gpio.h
@@ -0,0 +1,54 @@
+#ifndef WHAL_STM32L1_GPIO_H
+#define WHAL_STM32L1_GPIO_H
+
+/*
+ * @file stm32l1_gpio.h
+ * @brief STM32L1 GPIO driver (alias for STM32WB GPIO).
+ *
+ * The STM32L1 GPIO peripheral is register-compatible with the STM32WB GPIO.
+ * This header re-exports the STM32WB GPIO driver types and symbols under
+ * STM32L1-specific names. The underlying implementation is shared.
+ */
+
+#include