From 052afa86e0ee5637425f49148d8b3dea3fa69d60 Mon Sep 17 00:00:00 2001 From: Terry Bai Date: Fri, 1 Nov 2024 14:47:03 +1100 Subject: [PATCH 1/9] Clock driver for odroidc4 This implementation includes all clock definitions, basic operations of clock compoents, as well as interfaces to interact with clicnets via PPC. Client should be able to enable/disable a specific gate clock or query the current clock rate by passing the corresponding clock identifier, which is listed in g12a-bindings.h. set_rate() assumes that the rate requst is valid and compatible with the current configurations. Complete implementation that considers more incompatible requests will come in the future when dynamic clock configuration is necessary in lionsos Signed-off-by: Terry Bai add Makefile Signed-off-by: Terry Bai Clock driver for maaxboard Add clock driver for maaxboard. This commit includes all clock component definitions, basic operations, and a simple test on maaxboard. Scripts are added in build systems. A few files are reorganised for reuse Signed-off-by: Terry Bai Clk: fix style Signed-off-by: Terry Bai clk: modify interfaces and fix issues during initialisation This commit includes modifiation to the interfaces, primarily for set/get rate, which return error state as well as final rate. More tests are conducted to verify the basic operations of types of clock components. A few bugs are fixed as well. Signed-off-by: Terry Bai clk: fix style and add licenses This commit fixed style and license issues Signed-off-by: Terry Bai clk: fix style and data types This commit replaces all platform-dependent data types with fix-sized types, and unifies the return values of interfaces as signed integers. The return value 0 indicates the success on the request to clock driver, and negative values indicate the failure reasons. Signed-off-by: Terry Bai clk: combine regmap_* and regmap_mux_* operations Abstract a set of regmap_* operations by combining mux-specific ones and ordinary ones Signed-off-by: Terry Bai clk: add get/set parent interfaces Add clk_get_parent() and clk_set_parent() interfaces for the clients. Fixed several logic issues in related implementations. More clock component definition checkings and fixup. Signed-off-by: Terry Bai clk: set_rate for maaxboard clocks This commit adds set_rate() implementations for maaxboard. Now clocks can be set to target rate at initialisation time and configured dynamically. Signed-off-by: Terry Bai clk: reorganise files This commit reorgnises source files to allow more code to be shared for multiple boards. Refactor makefiles: - move objectives to clk_driver directory - use built-in CFLAGS in rules Temporary fixup for a zig build issue: create_clk_config.py cannot find the lazypath `zig-out/bin` when executing. The current solution is to create an install directory in advance, but it would be a bit of trouble to find the generated header file if someone want to read or modify it. Moved clock specifier binding files to `include/sddf/clk`, so the clients will be able to configure clocks with macro constants Signed-off-by: Terry Bai Update clock driver to current sddf build style, added meta.py Signed-off-by: Lesley Rossouw Replace setvar constants, move all memory region setup to meta.py Signed-off-by: Lesley Rossouw Clock driver for maaxboard Add clock driver for maaxboard. This commit includes all clock component definitions, basic operations, and a simple test on maaxboard. Scripts are added in build systems. A few files are reorganised for reuse Signed-off-by: Terry Bai examples/clk: remove timer for client Signed-off-by: Terry Bai examples/clk: remove dts dir Signed-off-by: Terry Bai drivers/clk: add sddf_clk_set_cpu_freq() interface Signed-off-by: Terry Bai drivers/clk: add ARM_CLK and remove sddf_clk_set_cpu_freq() Signed-off-by: Terry Bai clock driver hotfixes Signed-off-by: Julia Vassiliki clk driver flake dep Signed-off-by: Julia Vassiliki --- LICENSES/GPL-2.0-only.txt | 384 +-- LICENSES/GPL-2.0-or-later.txt | 117 + build.zig | 73 + drivers/clk/clk-operations.c | 261 ++ drivers/clk/clk-operations.h | 187 ++ drivers/clk/clk.c | 344 +++ drivers/clk/clk.h | 406 ++++ drivers/clk/clk_utils.h | 35 + drivers/clk/create_clk_config.py | 130 + drivers/clk/imx/clk-imx.c | 504 ++++ drivers/clk/imx/clk-imx8mq.c | 2954 +++++++++++++++++++++++ drivers/clk/imx/clk_driver.mk | 37 + drivers/clk/imx/include/clk-imx.h | 397 +++ drivers/clk/imx/include/clk-imx8mq.h | 11 + drivers/clk/meson/clk-measure.c | 230 ++ drivers/clk/meson/clk-meson.c | 471 ++++ drivers/clk/meson/clk_driver.mk | 37 + drivers/clk/meson/include/clk-measure.h | 9 + drivers/clk/meson/include/clk-meson.h | 174 ++ drivers/clk/meson/include/clk-sm1.h | 11 + drivers/clk/meson/include/g12a-regs.h | 131 + drivers/clk/meson/sm1-clk.c | 1592 ++++++++++++ examples/clk/Makefile | 35 + examples/clk/board/maaxboard/clk.system | 39 + examples/clk/board/odroidc4/clk.system | 38 + examples/clk/build.zig | 147 ++ examples/clk/build.zig.zon | 18 + examples/clk/client.c | 155 ++ examples/clk/clk.mk | 97 + examples/clk/meta.py | 121 + flake.nix | 12 + include/sddf/clk/client.h | 116 + include/sddf/clk/g12a-bindings.h | 292 +++ include/sddf/clk/imx8mq-bindings.h | 430 ++++ include/sddf/clk/protocol.h | 31 + tools/make/board/maaxboard.mk | 1 + tools/make/board/odroidc4.mk | 1 + 37 files changed, 9735 insertions(+), 293 deletions(-) create mode 100644 LICENSES/GPL-2.0-or-later.txt create mode 100644 drivers/clk/clk-operations.c create mode 100644 drivers/clk/clk-operations.h create mode 100644 drivers/clk/clk.c create mode 100644 drivers/clk/clk.h create mode 100644 drivers/clk/clk_utils.h create mode 100644 drivers/clk/create_clk_config.py create mode 100644 drivers/clk/imx/clk-imx.c create mode 100644 drivers/clk/imx/clk-imx8mq.c create mode 100644 drivers/clk/imx/clk_driver.mk create mode 100644 drivers/clk/imx/include/clk-imx.h create mode 100644 drivers/clk/imx/include/clk-imx8mq.h create mode 100644 drivers/clk/meson/clk-measure.c create mode 100644 drivers/clk/meson/clk-meson.c create mode 100644 drivers/clk/meson/clk_driver.mk create mode 100644 drivers/clk/meson/include/clk-measure.h create mode 100644 drivers/clk/meson/include/clk-meson.h create mode 100644 drivers/clk/meson/include/clk-sm1.h create mode 100644 drivers/clk/meson/include/g12a-regs.h create mode 100644 drivers/clk/meson/sm1-clk.c create mode 100644 examples/clk/Makefile create mode 100644 examples/clk/board/maaxboard/clk.system create mode 100644 examples/clk/board/odroidc4/clk.system create mode 100644 examples/clk/build.zig create mode 100644 examples/clk/build.zig.zon create mode 100644 examples/clk/client.c create mode 100644 examples/clk/clk.mk create mode 100644 examples/clk/meta.py create mode 100644 include/sddf/clk/client.h create mode 100644 include/sddf/clk/g12a-bindings.h create mode 100644 include/sddf/clk/imx8mq-bindings.h create mode 100644 include/sddf/clk/protocol.h diff --git a/LICENSES/GPL-2.0-only.txt b/LICENSES/GPL-2.0-only.txt index 5effc0590..17cb28643 100644 --- a/LICENSES/GPL-2.0-only.txt +++ b/LICENSES/GPL-2.0-only.txt @@ -1,319 +1,117 @@ GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - -Everyone is permitted to copy and distribute verbatim copies of this license -document, but changing it is not allowed. +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble -The licenses for most software are designed to take away your freedom to share -and change it. By contrast, the GNU General Public License is intended to -guarantee your freedom to share and change free software--to make sure the -software is free for all its users. This General Public License applies to -most of the Free Software Foundation's software and to any other program whose -authors commit to using it. (Some other Free Software Foundation software -is covered by the GNU Lesser General Public License instead.) You can apply -it to your programs, too. - -When we speak of free software, we are referring to freedom, not price. Our -General Public Licenses are designed to make sure that you have the freedom -to distribute copies of free software (and charge for this service if you -wish), that you receive source code or can get it if you want it, that you -can change the software or use pieces of it in new free programs; and that -you know you can do these things. - -To protect your rights, we need to make restrictions that forbid anyone to -deny you these rights or to ask you to surrender the rights. These restrictions -translate to certain responsibilities for you if you distribute copies of -the software, or if you modify it. - -For example, if you distribute copies of such a program, whether gratis or -for a fee, you must give the recipients all the rights that you have. You -must make sure that they, too, receive or can get the source code. And you -must show them these terms so they know their rights. - -We protect your rights with two steps: (1) copyright the software, and (2) -offer you this license which gives you legal permission to copy, distribute -and/or modify the software. - -Also, for each author's protection and ours, we want to make certain that -everyone understands that there is no warranty for this free software. If -the software is modified by someone else and passed on, we want its recipients -to know that what they have is not the original, so that any problems introduced -by others will not reflect on the original authors' reputations. - -Finally, any free program is threatened constantly by software patents. We -wish to avoid the danger that redistributors of a free program will individually -obtain patent licenses, in effect making the program proprietary. To prevent -this, we have made it clear that any patent must be licensed for everyone's -free use or not licensed at all. - -The precise terms and conditions for copying, distribution and modification -follow. +The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. + +To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. + +Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION -0. This License applies to any program or other work which contains a notice -placed by the copyright holder saying it may be distributed under the terms -of this General Public License. The "Program", below, refers to any such program -or work, and a "work based on the Program" means either the Program or any -derivative work under copyright law: that is to say, a work containing the -Program or a portion of it, either verbatim or with modifications and/or translated -into another language. (Hereinafter, translation is included without limitation -in the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not covered -by this License; they are outside its scope. The act of running the Program -is not restricted, and the output from the Program is covered only if its -contents constitute a work based on the Program (independent of having been -made by running the Program). Whether that is true depends on what the Program -does. - -1. You may copy and distribute verbatim copies of the Program's source code -as you receive it, in any medium, provided that you conspicuously and appropriately -publish on each copy an appropriate copyright notice and disclaimer of warranty; -keep intact all the notices that refer to this License and to the absence -of any warranty; and give any other recipients of the Program a copy of this -License along with the Program. - -You may charge a fee for the physical act of transferring a copy, and you -may at your option offer warranty protection in exchange for a fee. - -2. You may modify your copy or copies of the Program or any portion of it, -thus forming a work based on the Program, and copy and distribute such modifications -or work under the terms of Section 1 above, provided that you also meet all -of these conditions: - -a) You must cause the modified files to carry prominent notices stating that -you changed the files and the date of any change. - -b) You must cause any work that you distribute or publish, that in whole or -in part contains or is derived from the Program or any part thereof, to be -licensed as a whole at no charge to all third parties under the terms of this -License. - -c) If the modified program normally reads commands interactively when run, -you must cause it, when started running for such interactive use in the most -ordinary way, to print or display an announcement including an appropriate -copyright notice and a notice that there is no warranty (or else, saying that -you provide a warranty) and that users may redistribute the program under -these conditions, and telling the user how to view a copy of this License. -(Exception: if the Program itself is interactive but does not normally print -such an announcement, your work based on the Program is not required to print -an announcement.) - -These requirements apply to the modified work as a whole. If identifiable -sections of that work are not derived from the Program, and can be reasonably -considered independent and separate works in themselves, then this License, -and its terms, do not apply to those sections when you distribute them as -separate works. But when you distribute the same sections as part of a whole -which is a work based on the Program, the distribution of the whole must be -on the terms of this License, whose permissions for other licensees extend -to the entire whole, and thus to each and every part regardless of who wrote -it. - -Thus, it is not the intent of this section to claim rights or contest your -rights to work written entirely by you; rather, the intent is to exercise -the right to control the distribution of derivative or collective works based -on the Program. - -In addition, mere aggregation of another work not based on the Program with -the Program (or with a work based on the Program) on a volume of a storage -or distribution medium does not bring the other work under the scope of this -License. - -3. You may copy and distribute the Program (or a work based on it, under Section -2) in object code or executable form under the terms of Sections 1 and 2 above -provided that you also do one of the following: - -a) Accompany it with the complete corresponding machine-readable source code, -which must be distributed under the terms of Sections 1 and 2 above on a medium -customarily used for software interchange; or, - -b) Accompany it with a written offer, valid for at least three years, to give -any third party, for a charge no more than your cost of physically performing -source distribution, a complete machine-readable copy of the corresponding -source code, to be distributed under the terms of Sections 1 and 2 above on -a medium customarily used for software interchange; or, - -c) Accompany it with the information you received as to the offer to distribute -corresponding source code. (This alternative is allowed only for noncommercial -distribution and only if you received the program in object code or executable -form with such an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for making -modifications to it. For an executable work, complete source code means all -the source code for all modules it contains, plus any associated interface -definition files, plus the scripts used to control compilation and installation -of the executable. However, as a special exception, the source code distributed -need not include anything that is normally distributed (in either source or -binary form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component itself -accompanies the executable. - -If distribution of executable or object code is made by offering access to -copy from a designated place, then offering equivalent access to copy the -source code from the same place counts as distribution of the source code, -even though third parties are not compelled to copy the source along with -the object code. - -4. You may not copy, modify, sublicense, or distribute the Program except -as expressly provided under this License. Any attempt otherwise to copy, modify, -sublicense or distribute the Program is void, and will automatically terminate -your rights under this License. However, parties who have received copies, -or rights, from you under this License will not have their licenses terminated -so long as such parties remain in full compliance. - -5. You are not required to accept this License, since you have not signed -it. However, nothing else grants you permission to modify or distribute the -Program or its derivative works. These actions are prohibited by law if you -do not accept this License. Therefore, by modifying or distributing the Program -(or any work based on the Program), you indicate your acceptance of this License -to do so, and all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - -6. Each time you redistribute the Program (or any work based on the Program), -the recipient automatically receives a license from the original licensor -to copy, distribute or modify the Program subject to these terms and conditions. -You may not impose any further restrictions on the recipients' exercise of -the rights granted herein. You are not responsible for enforcing compliance -by third parties to this License. - -7. If, as a consequence of a court judgment or allegation of patent infringement -or for any other reason (not limited to patent issues), conditions are imposed -on you (whether by court order, agreement or otherwise) that contradict the -conditions of this License, they do not excuse you from the conditions of -this License. If you cannot distribute so as to satisfy simultaneously your -obligations under this License and any other pertinent obligations, then as -a consequence you may not distribute the Program at all. For example, if a -patent license would not permit royalty-free redistribution of the Program -by all those who receive copies directly or indirectly through you, then the -only way you could satisfy both it and this License would be to refrain entirely -from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under any -particular circumstance, the balance of the section is intended to apply and -the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any patents -or other property right claims or to contest validity of any such claims; -this section has the sole purpose of protecting the integrity of the free -software distribution system, which is implemented by public license practices. -Many people have made generous contributions to the wide range of software -distributed through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing to -distribute software through any other system and a licensee cannot impose -that choice. - -This section is intended to make thoroughly clear what is believed to be a -consequence of the rest of this License. - -8. If the distribution and/or use of the Program is restricted in certain -countries either by patents or by copyrighted interfaces, the original copyright -holder who places the Program under this License may add an explicit geographical -distribution limitation excluding those countries, so that distribution is -permitted only in or among countries not thus excluded. In such case, this -License incorporates the limitation as if written in the body of this License. - -9. The Free Software Foundation may publish revised and/or new versions of -the General Public License from time to time. Such new versions will be similar -in spirit to the present version, but may differ in detail to address new -problems or concerns. - -Each version is given a distinguishing version number. If the Program specifies -a version number of this License which applies to it and "any later version", -you have the option of following the terms and conditions either of that version -or of any later version published by the Free Software Foundation. If the -Program does not specify a version number of this License, you may choose -any version ever published by the Free Software Foundation. - -10. If you wish to incorporate parts of the Program into other free programs -whose distribution conditions are different, write to the author to ask for -permission. For software which is copyrighted by the Free Software Foundation, -write to the Free Software Foundation; we sometimes make exceptions for this. -Our decision will be guided by the two goals of preserving the free status -of all derivatives of our free software and of promoting the sharing and reuse -of software generally. - - NO WARRANTY - -11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR -THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE -STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM -"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, -BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE -OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - -12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE -OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA -OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES -OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH -HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. -END OF TERMS AND CONDITIONS +0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". -How to Apply These Terms to Your New Programs +Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. + +1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. + + c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. + +3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: -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. + a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, -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 convey the exclusion -of warranty; and each file should have at least the "copyright" line and a -pointer to where the full notice is found. + b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. + +If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. + +4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. + +This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. + +9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. + +NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +END OF TERMS AND CONDITIONS + +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. -Copyright (C)< yyyy> +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 convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. -This program is free software; you can redistribute it and/or modify it under -the terms of the GNU General Public License as published by the Free Software -Foundation; either version 2 of the License, or (at your option) any later -version. + one line to give the program's name and an idea of what it does. Copyright (C) yyyy name of author -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. + This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. -You should have received a copy of the GNU General Public License along with -this program; if not, write to the Free Software Foundation, Inc., 51 Franklin -Street, Fifth Floor, Boston, MA 02110-1301, USA. + 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. -Also add information on how to contact you by electronic and paper mail. + You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Also add information on how to contact you by electronic and paper mail. -If the program is interactive, make it output a short notice like this when -it starts in an interactive mode: +If the program is interactive, make it output a short notice like this when it starts in an interactive mode: -Gnomovision version 69, Copyright (C) year name of author Gnomovision 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. + Gnomovision version 69, Copyright (C) year name of author Gnomovision 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, the commands you use may be -called something other than `show w' and `show c'; they could even be mouse-clicks -or menu items--whatever suits your program. +The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. -You should also get your employer (if you work as a programmer) or your school, -if any, to sign a "copyright disclaimer" for the program, if necessary. Here -is a sample; alter the names: +You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: -Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' -(which makes passes at compilers) written by James Hacker. + Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. -, 1 April 1989 Ty Coon, President of Vice This 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. +signature of Ty Coon, 1 April 1989 Ty Coon, President of Vice diff --git a/LICENSES/GPL-2.0-or-later.txt b/LICENSES/GPL-2.0-or-later.txt new file mode 100644 index 000000000..17cb28643 --- /dev/null +++ b/LICENSES/GPL-2.0-or-later.txt @@ -0,0 +1,117 @@ +GNU GENERAL PUBLIC LICENSE +Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + +Preamble + +The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. + +To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. + +Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification follow. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. + +1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. + + c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. + +3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. + +If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. + +4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. + +This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. + +9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. + +NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +END OF TERMS AND CONDITIONS + +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 convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. + + one line to give the program's name and an idea of what it does. Copyright (C) yyyy name of author + + This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author Gnomovision 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, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. + +signature of Ty Coon, 1 April 1989 Ty Coon, President of Vice diff --git a/build.zig b/build.zig index 24761b47a..e30de29b2 100644 --- a/build.zig +++ b/build.zig @@ -39,6 +39,11 @@ const DriverClass = struct { }; }; + const Clock = enum { + meson, + imx, + }; + const I2cHost = enum { meson, opentitan, @@ -337,6 +342,58 @@ fn addNvmeBlockDriver( return driver; } +fn addClockDriver( + b: *std.Build, + clk_config_include: LazyPath, + util: *std.Build.Step.Compile, + class: DriverClass.Clock, + target: std.Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, +) *std.Build.Step.Compile { + const driver = addPd(b, .{ + .name = b.fmt("driver_clk_{s}.elf", .{@tagName(class)}), + .target = target, + .optimize = optimize, + .strip = false, + }); + + switch (class) { + .meson => { + const files: []const []const u8 = &.{ + "drivers/clk/meson/clk-meson.c", + "drivers/clk/meson/clk-measure.c", + "drivers/clk/meson/sm1-clk.c", + }; + driver.addCSourceFiles(.{ .files = files }); + }, + .imx => { + const files: []const []const u8 = &.{ + "drivers/clk/imx/clk-imx.c", + "drivers/clk/imx/clk-imx8mq.c", + }; + driver.addCSourceFiles(.{ .files = files }); + }, + } + + const common_src_files = .{ + "clk-operations.c", + "clk.c", + }; + + inline for (common_src_files) |f| { + driver.addCSourceFile(.{ .file = b.path(b.fmt("drivers/clk/{s}", .{f})) }); + } + + driver.defineCMacro(b.fmt("BOARD_CLASS_{s}", .{@tagName(class)}), "1"); + driver.addIncludePath(clk_config_include); + driver.addIncludePath(b.path("include")); + driver.addIncludePath(b.path("drivers/clk")); + driver.addIncludePath(b.path(b.fmt("drivers/clk/{s}/include", .{@tagName(class)}))); + driver.linkLibrary(util); + + return driver; +} + fn addMmcDriver( b: *std.Build, util: *std.Build.Step.Compile, @@ -798,4 +855,20 @@ pub fn build(b: *std.Build) !void { net_copy.linkLibrary(util_putchar_debug); b.installArtifact(net_copy); } + + // Clock drivers + inline for (std.meta.fields(DriverClass.Clock)) |class| { + const driver = addClockDriver(b, clk_client_include, util, @enumFromInt(class.value), target, optimize); + driver.linkLibrary(util_putchar_debug); + + const clk_config = b.addSystemCommand(&.{ + "python", + "drivers/clk/create_clk_config.py", + dtb_path, + }); // Creates a system command which runs the python interpreter + const clk_config_include = clk_config.addOutputDirectoryArg("test"); + driver.addIncludePath(clk_config_include); + + b.installArtifact(driver); + } } diff --git a/drivers/clk/clk-operations.c b/drivers/clk/clk-operations.c new file mode 100644 index 000000000..626689eae --- /dev/null +++ b/drivers/clk/clk-operations.c @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +/* + * Operations for MPLL clocks are derived from: + * https://github.com/torvalds/linux/blob/ + * befe87380e21f0d37633273e1068c9318f8135ff/drivers/clk/meson/clk-mpll.c + * + * Copyright (c) 2016 AmLogic, Inc. + * Author: Michael Turquette + */ + +// SPDX-License-Identifier: GPL-2.0-only +/* + * Operations for PLL clocks are derived from: + * https://github.com/torvalds/linux/blob/ + * befe87380e21f0d37633273e1068c9318f8135ff/drivers/clk/meson/clk-pll.c + * + * Copyright (c) 2015 Endless Mobile, Inc. + * Author: Carlo Caione + * + * Copyright (c) 2018 Baylibre, SAS. + * Author: Jerome Brunet + */ + +// SPDX-License-Identifier: GPL-2.0-only +/* + * Operations for gate clocks are derived from: + * https://github.com/torvalds/linux/blob/ + * befe87380e21f0d37633273e1068c9318f8135ff/drivers/clk/meson/clk-regmap.c + * + * Copyright (c) 2018 BayLibre, SAS. + * Author: Jerome Brunet + */ + +// SPDX-License-Identifier: GPL-2.0-only +/* + * Operations for fixed factor clocks are derived from: + * https://github.com/torvalds/linux/blob/ + * befe87380e21f0d37633273e1068c9318f8135ff/drivers/clk/clk-fixed-factor.c + * + * Copyright (C) 2011 Sascha Hauer, Pengutronix + */ + +// SPDX-License-Identifier: GPL-2.0-only +/* + * Operations for divider clocks are derived from: + * https://github.com/torvalds/linux/blob/ + * befe87380e21f0d37633273e1068c9318f8135ff/drivers/clk/clk-divider.c + * + * Copyright (C) 2011 Sascha Hauer, Pengutronix + * Copyright (C) 2011 Richard Zhao, Linaro + * Copyright (C) 2011-2012 Mike Turquette, Linaro Ltd + * + * Adjustable divider clock implementation + */ + +// SPDX-License-Identifier: GPL-2.0-only +/* + * Operations for multiplexer clocks are derived from: + * https://github.com/torvalds/linux/blob/ + * befe87380e21f0d37633273e1068c9318f8135ff/drivers/clk/clk-mux.c + * + * Copyright (C) 2011 Sascha Hauer, Pengutronix + * Copyright (C) 2011 Richard Zhao, Linaro + * Copyright (C) 2011-2012 Mike Turquette, Linaro Ltd + * + * Simple multiplexer clock implementation + */ + +// SPDX-License-Identifier: GPL-2.0-only +/* + * Operations for meson_vid_pll_div are derived from: + * https://github.com/torvalds/linux/blob/ + * 7ec462100ef9142344ddbf86f2c3008b97acddbe/drivers/clk/meson/vid-pll-div.c + * https://github.com/torvalds/linux/blob/ + * 7ec462100ef9142344ddbf86f2c3008b97acddbe/drivers/clk/meson/vclk.c + * + * Copyright (c) 2018 BayLibre, SAS. + * Author: Neil Armstrong + */ + +#include +#include +#include +#include +#include + +static inline int clk_gate_enable(struct clk *clk) +{ + struct clk_gate_data *data = (struct clk_gate_data *)(clk->data); + + return regmap_update_bits(clk->base, data->offset, data->bit_idx, MASK(1), 1); +} + +static inline int clk_gate_disable(struct clk *clk) +{ + struct clk_gate_data *data = (struct clk_gate_data *)(clk->data); + + regmap_update_bits(clk->base, data->offset, data->bit_idx, MASK(1), 0); + return 0; +} + +static inline int clk_gate_is_enabled(struct clk *clk) +{ + struct clk_gate_data *data = (struct clk_gate_data *)(clk->data); + + /* TODO: to be checked */ + /* if (gate->flags & CLK_GATE_SET_TO_DISABLE) */ + /* val ^= BIT(gate->bit_idx); */ + + /* val &= BIT(gate->bit_idx); */ + /* return val ? 1 : 0; */ + + return regmap_read_bits(clk->base, data->offset, data->bit_idx, MASK(1)); +} + +const struct clk_ops clk_gate_ops = { + .enable = clk_gate_enable, + .disable = clk_gate_disable, + .is_enabled = clk_gate_is_enabled, +}; + +const struct clk_ops clk_gate_ro_ops = { + .is_enabled = clk_gate_is_enabled, +}; + +static inline uint64_t clk_div_recalc_rate(const struct clk *clk, uint64_t prate) +{ + + struct clk_div_data *data = (struct clk_div_data *)(clk->data); + uint32_t div = regmap_read_bits(clk->base, data->offset, data->shift, MASK(data->width)); + + /* TODO: Need to verify the following cases */ + if (data->flags & CLK_DIVIDER_ONE_BASED) { + ; + } else if (data->flags & CLK_DIVIDER_POWER_OF_TWO) { + div = 1 << div; + } else if (data->flags & CLK_DIVIDER_MAX_AT_ZERO) { + div = div ? div : MASK(data->width) + 1; + } else { + div += 1; + } + + return DIV_ROUND_UP_ULL((uint64_t)prate, div); +} + +static inline int clk_div_set_rate(const struct clk *clk, uint64_t rate, uint64_t parent_rate) +{ + struct clk_div_data *data = (struct clk_div_data *)(clk->data); + uint32_t div = DIV_ROUND_UP(parent_rate, rate); + + if (data->flags & CLK_DIVIDER_ONE_BASED) { + ; + } else if (data->flags & CLK_DIVIDER_POWER_OF_TWO) { + /* div = __ffs(div); */ + } else if (data->flags & CLK_DIVIDER_MAX_AT_ZERO) { + div = (div == MASK(data->width) + 1) ? 0 : div; + } else { + div -= 1; + } + return regmap_update_bits(clk->base, data->offset, data->shift, MASK(data->width), div); +} + +const struct clk_ops clk_divider_ops = { + .enable = NULL, + .recalc_rate = clk_div_recalc_rate, + /* .determine_rate = clk_div_determine_rate, */ + .set_rate = clk_div_set_rate, +}; + +const struct clk_ops clk_divider_ro_ops = { + .recalc_rate = clk_div_recalc_rate, + /* .determine_rate = clk_div_determine_rate, */ +}; + +static inline int clk_mux_get_parent(const struct clk *clk, uint8_t *index) +{ + struct clk_mux_data *data = (struct clk_mux_data *)(clk->data); + uint32_t num_parents = clk->hw.init->num_parents; + uint32_t val = regmap_read_bits(clk->base, data->offset, data->shift, data->mask); + + if (data->table) { + int i; + for (i = 0; i < num_parents; i++) { + if (data->table[i] == val) { + *index = i; + return 0; + } + } + return CLK_UNKNOWN_TARGET; + } + + /* if (val && (flags & CLK_MUX_INDEX_BIT)) */ + /* val = ffs(val) - 1; */ + + /* if (val && (flags & CLK_MUX_INDEX_ONE)) */ + /* val--; */ + + if (val >= num_parents) + return CLK_UNKNOWN_TARGET; + + *index = val; + return 0; +} + +static inline int clk_mux_set_parent(struct clk *clk, uint8_t index) +{ + struct clk_mux_data *data = (struct clk_mux_data *)(clk->data); + + if (data->table) { + uint32_t val = data->table[index]; + return regmap_update_bits(clk->base, data->offset, data->shift, data->mask, val); + } + + return regmap_update_bits(clk->base, data->offset, data->shift, data->mask, index); +} + +const struct clk_ops clk_mux_ops = { + .get_parent = clk_mux_get_parent, .set_parent = clk_mux_set_parent, + /* .determine_rate = clk_mux_determine_rate, */ +}; + +const struct clk_ops clk_mux_ro_ops = { + .get_parent = clk_mux_get_parent, +}; + +static inline uint64_t clk_factor_recalc_rate(const struct clk *clk, uint64_t parent_rate) +{ + struct clk_fixed_factor_data *data = (struct clk_fixed_factor_data *)(clk->data); + uint64_t rate; + + rate = (uint64_t)parent_rate * data->mult; + do_div(rate, data->div); + return (uint64_t)rate; +} + +const struct clk_ops clk_fixed_factor_ops = { + /* .round_rate = clk_factor_round_rate, */ + /* .set_rate = clk_factor_set_rate, */ + .recalc_rate = clk_factor_recalc_rate, + /* .recalc_accuracy = clk_factor_recalc_accuracy, */ +}; + +static inline int clk_source_set_rate(const struct clk *clk, uint64_t rate, uint64_t parent_rate) +{ + struct clk_source_data *data = (struct clk_source_data *)(clk->data); + data->rate = rate; + + return 0; +} + +static inline uint64_t clk_source_get_rate(const struct clk *clk, uint64_t prate) +{ + struct clk_source_data *data = (struct clk_source_data *)(clk->data); + + return data->rate; +} + +const struct clk_ops clk_source_ops = { + .recalc_rate = clk_source_get_rate, + .set_rate = clk_source_set_rate, +}; diff --git a/drivers/clk/clk-operations.h b/drivers/clk/clk-operations.h new file mode 100644 index 000000000..0962474e7 --- /dev/null +++ b/drivers/clk/clk-operations.h @@ -0,0 +1,187 @@ +/* + * Copyright 2024, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +#define CLK_INCORRECT_ARGS -1 +#define CLK_INVALID_OP -2 +#define CLK_INVALID_ID -3 +#define CLK_UNKNOWN_REQ -4 +#define CLK_UNKNOWN_TARGET -5 +#define CLK_FAILED_OP -6 + +/* TODO: Replace this doggy delay() with a standard interface */ +static inline void delay_us(uint32_t us) +{ + uint64_t start_time = sddf_timer_time_now(TIMER_CH); + uint64_t now_time = start_time; + while (now_time - start_time < us) { + now_time = sddf_timer_time_now(TIMER_CH); + } +} + +static inline int reg_write(uint64_t base, uint32_t offset, uint32_t val) +{ + volatile uint32_t *clk_reg = ((void *)base + offset); + *clk_reg = val; + + return 0; +} + +static inline int regmap_update_bits(uint64_t base, uint32_t offset, uint8_t shift, uint32_t mask, uint32_t val) +{ + volatile uint32_t *clk_reg = ((void *)base + offset); + uint32_t reg_val = *clk_reg; + + reg_val &= ~(mask << shift); + reg_val |= ((mask & val) << shift); + + *clk_reg = reg_val; + + return 0; +} + +static inline uint32_t regmap_read_bits(uint64_t base, uint32_t offset, uint8_t shift, uint32_t mask) +{ + volatile uint32_t *clk_reg = ((void *)base + offset); + uint32_t reg_val = *clk_reg; + + reg_val >>= shift; + reg_val &= mask; + + return reg_val; +} + +extern const struct clk_ops clk_source_ops; +extern const struct clk_ops clk_fixed_factor_ops; +extern const struct clk_ops clk_divider_ops; +extern const struct clk_ops clk_divider_ro_ops; +extern const struct clk_ops clk_mux_ops; +extern const struct clk_ops clk_mux_ro_ops; +extern const struct clk_ops clk_gate_ops; +extern const struct clk_ops clk_gate_ro_ops; + +#define CLK_FIXED_FACTOR(_name, _mult, _div, _data_flags, _parent_clks, _num_parents, _init_flags) \ +struct clk _name = { \ + .data = &(struct clk_fixed_factor_data) { \ + .mult = (_mult), \ + .div = (_div), \ + .flags = (_data_flags), \ + }, \ + .hw.init = &(struct clk_init_data) { \ + .name = #_name, \ + .ops = &clk_fixed_factor_ops, \ + .parent_clks = (const struct clk *[]) _parent_clks, \ + .num_parents = (_num_parents), \ + .flags = (_init_flags), \ + }, \ +} + +#define CLK_GATE(_name, _offset, _bit, _data_flags, _parent_clks, _num_parents, _init_flags) \ +struct clk _name = { \ + .data = &(struct clk_gate_data) { \ + .offset = (_offset), \ + .bit_idx = (_bit), \ + .flags = (_data_flags), \ + }, \ + .hw.init = &(struct clk_init_data) { \ + .name = #_name, \ + .ops = &clk_gate_ops, \ + .parent_clks = (const struct clk *[]) _parent_clks, \ + .num_parents = _num_parents, \ + .flags = (_init_flags), \ + }, \ +} + +#define CLK_GATE_RO(_name, _offset, _bit, _data_flags, _parent_clks, _num_parents, _init_flags) \ +struct clk _name = { \ + .data = &(struct clk_gate_data) { \ + .offset = (_offset), \ + .bit_idx = (_bit), \ + .flags = (_data_flags), \ + }, \ + .hw.init = &(struct clk_init_data) { \ + .name = #_name, \ + .ops = &clk_gate_ro_ops, \ + .parent_clks = (const struct clk *[]) _parent_clks, \ + .num_parents = _num_parents, \ + .flags = (_init_flags), \ + }, \ +} + +#define CLK_MUX(_name, _offset, _mask, _shift, _table, _data_flags, _parent_data, _num_parents, _init_flags) \ +struct clk _name = { \ + .data = &(struct clk_mux_data) { \ + .offset = (_offset), \ + .mask = (_mask), \ + .shift = (_shift), \ + .table = (_table), \ + .flags = (_data_flags), \ + }, \ + .hw.init = &(struct clk_init_data) { \ + .name = #_name, \ + .ops = &clk_mux_ops, \ + .parent_data = (_parent_data), \ + .num_parents = (_num_parents), \ + .flags = (_init_flags), \ + }, \ +} + +#define CLK_MUX_RO(_name, _offset, _mask, _shift, _table, _data_flags, _parent_data, _num_parents, _init_flags) \ +struct clk _name = { \ + .data = &(struct clk_mux_data) { \ + .offset = (_offset), \ + .mask = (_mask), \ + .shift = (_shift), \ + .table = (_table), \ + .flags = (_data_flags), \ + }, \ + .hw.init = &(struct clk_init_data) { \ + .name = #_name, \ + .ops = &clk_mux_ro_ops, \ + .parent_data = (_parent_data), \ + .num_parents = (_num_parents), \ + .flags = (_init_flags), \ + }, \ +} + +#define CLK_DIV(_name, _offset, _shift, _width, _data_flags, _parent_clks, _num_parents, _init_flags) \ +struct clk _name = { \ + .data = &(struct clk_div_data) { \ + .offset = (_offset), \ + .shift = (_shift), \ + .width = (_width), \ + .flags = (_data_flags), \ + }, \ + .hw.init = &(struct clk_init_data) { \ + .name = #_name, \ + .ops = &clk_divider_ops, \ + .parent_clks = (const struct clk *[]) _parent_clks, \ + .num_parents = (_num_parents), \ + .flags = (_init_flags), \ + }, \ +} + +#define CLK_DIV_RO(_name, _offset, _shift, _width, _data_flags, _parent_clks, _num_parents, _init_flags) \ +struct clk _name = { \ + .data = &(struct clk_div_data) { \ + .offset = (_offset), \ + .shift = (_shift), \ + .width = (_width), \ + .flags = (_data_flags), \ + }, \ + .hw.init = &(struct clk_init_data) { \ + .name = #_name, \ + .ops = &clk_divider_ro_ops, \ + .parent_clks = (const struct clk *[]) _parent_clks, \ + .num_parents = (_num_parents), \ + .flags = (_init_flags), \ + }, \ +} diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c new file mode 100644 index 000000000..f5eafd7b5 --- /dev/null +++ b/drivers/clk/clk.c @@ -0,0 +1,344 @@ +/* + * Copyright 2024, UNSW + * + * SPDX-License-Identifier: BSD-2-Clause + * + * Terry Bai: tianyi.bai@unsw.edu.au + */ + +#include +#include +#include +#include +#include +#include /* common definitions and interfaces */ +#include /* ops of common clocks e.g., div, mux, fixed factor, and gate*/ +#include /* configuration parsed from device tree */ + +#ifdef CONFIG_PLAT_ODROIDC4 +#include /* operations for meson-specific clocks */ +#include /* implementation of clock measurements */ +#include /* g12a-specific definitions */ +#include /* offsets of control registers */ +#elif CONFIG_PLAT_MAAXBOARD +#include /* operations for imx-specific clocks */ +#include /* imx8mq-specific definitions */ +#else +#error "The platform is not supported!\n" +#endif + +#ifndef NUM_CLK_LIST +#error "Constant \"NUM_CLK_LIST\" should be defined\n") +#endif + +#define CLIENT_CH 0 + +struct clk **clk_list; + +struct clk *get_clk_by_name(const char *name) +{ + for (int i = 0; i < NUM_CLK_LIST; i++) { + if (clk_list[i] && strcmp(clk_list[i]->hw.init->name, name) == 0) { + return clk_list[i]; + } + } + return NULL; +} + +const struct clk *get_parent(const struct clk *clk) +{ + if (!clk) + return NULL; + + const struct clk_init_data *init = (struct clk_init_data *)clk->hw.init; + uint32_t num_parents = init->num_parents; + + if (init->parent_data) { + uint8_t parent_idx = 0; + int err = num_parents > 1 ? init->ops->get_parent(clk, &parent_idx) : 0; + if (err) + return NULL; + + struct clk_parent_data parent_data = init->parent_data[parent_idx]; + + if (parent_data.clk) { + return parent_data.clk; + } + if (parent_data.name) { + return get_clk_by_name(parent_data.name); + } + } + + if (num_parents > 0) { + return init->parent_clks[0]; + } + + return NULL; +} + +int clk_get_parent_id(const struct clk *clk, uint32_t *parent_clk_id) +{ + if (!clk) + return CLK_UNKNOWN_TARGET; + + const struct clk *pclk = get_parent(clk); + + if (pclk == NULL) + return CLK_INVALID_OP; + + for (int i = 0; i < NUM_CLK_LIST; i++) { + if (pclk == clk_list[i]) { + *parent_clk_id = i; + return 0; + } + } + + return CLK_UNKNOWN_TARGET; +} + +/* The user needs to know which parent clock the value represents, and check + * if the target parent clock is configured correctly with clk_get_parent() */ +int clk_set_parent_by_val(struct clk *clk, uint32_t parent_idx) +{ + if (!clk) + return CLK_UNKNOWN_TARGET; + + const struct clk_init_data *init = (struct clk_init_data *)clk->hw.init; + if (init->ops->set_parent) { + init->ops->set_parent(clk, parent_idx); + return 0; + } + LOG_DRIVER_ERR("clock \"%s\" has no set_parent() interface\n", init->name); + return CLK_INVALID_OP; +} + +int clk_set_parent_by_id(struct clk *clk, uint32_t pclk_id) +{ + const struct clk *pclk = clk_list[pclk_id]; + const struct clk_init_data *init = (struct clk_init_data *)clk->hw.init; + uint32_t num_parents = init->num_parents; + for (int i = 0; i < num_parents; i++) { + if (init->parent_clks && init->parent_clks[i] == pclk) { + return clk_set_parent_by_val(clk, i); + } else if (get_clk_by_name(init->parent_data[i].name) == pclk) { + return clk_set_parent_by_val(clk, i); + } + } + + return CLK_UNKNOWN_TARGET; +} + +/* TODO: Should be just read from the structure, but need to update everytime when */ +/* related clocks are modified */ +int clk_get_rate(const struct clk *clk, uint64_t *rate) +{ + if (!clk) + return CLK_UNKNOWN_TARGET; + + const struct clk_init_data *init = (struct clk_init_data *)clk->hw.init; + uint64_t parent_rate = 0; + int err = 0; + + const struct clk *parent_clk = get_parent(clk); + + if (parent_clk) { + err = clk_get_rate(parent_clk, &parent_rate); + } + + if (err) + return err; + + *rate = parent_rate; + if (init->ops->recalc_rate) { + *rate = init->ops->recalc_rate(clk, parent_rate); + } + + return 0; +} + +int clk_enable(struct clk *clk) +{ + if (!clk) + return CLK_UNKNOWN_TARGET; + + if (clk->hw.init->ops->enable != NULL) { + return clk->hw.init->ops->enable(clk); + } + + return CLK_INVALID_OP; +} + +int clk_disable(struct clk *clk) +{ + if (!clk) + return CLK_UNKNOWN_TARGET; + + if (clk->hw.init->ops->disable != NULL) { + return clk->hw.init->ops->disable(clk); + } + + return CLK_INVALID_OP; +} + +int clk_set_rate(struct clk *clk, uint64_t req_rate, uint64_t *rate) +{ + if (!clk) + return CLK_UNKNOWN_TARGET; + + /* TODO: we only propagate request to one level up. More operations need to be + * invoked by clients until a dynamic configration algorithm implemented */ + if (clk->hw.init->ops->init) { + clk->hw.init->ops->init(clk); + } + + const struct clk *pclk = get_parent(clk); + uint64_t prate = 0; + int err = clk_get_rate(pclk, &prate); + if (err) { + LOG_DRIVER_ERR("Failed to get parent clock's rate\n"); + return err; + } + + if (clk->hw.init->ops == &clk_source_ops) { + // source clock cannot be adjusted + return CLK_INVALID_OP; + } + + if (clk->hw.init->ops->set_rate) { + *rate = clk->hw.init->ops->set_rate(clk, req_rate, prate); + return 0; + } + + return clk_set_rate((struct clk *)pclk, req_rate, rate); +} + +void notified(microkit_channel ch) +{ +} + +void init(void) +{ + int err = 0; + clk_list = get_clk_list(); + + clk_probe(clk_list); + clk_msr_stat(clk_list); + + for (int i = 0; i < NUM_DEVICE_CLKS; i++) { + struct clk *clk = clk_list[clk_configs[i].clk_id]; + + /* Enable the clock */ + clk_enable(clk); + + /* Set parent for clocks as configured in device tree */ + if (clk_configs[i].pclk_id) { + err = clk_set_parent_by_id(clk, clk_configs[i].pclk_id); + if (err) { + LOG_DRIVER_ERR("Failed to set parent %u for clock %u: err - %d\n", clk_configs[i].pclk_id, + clk_configs[i].clk_id, err); + } + } + + if (clk_configs[i].frequency > 0) { + LOG_DRIVER("set rate for %s\n", clk->hw.init->name); + uint64_t rate = 0; + uint32_t err = clk_set_rate(clk, clk_configs[i].frequency, &rate); + if (err) { + LOG_DRIVER_ERR("Failed to set rate [%d] for clk_id: %d\n", clk_configs[i].frequency, + clk_configs[i].clk_id); + } + } + } +} + +microkit_msginfo protected(microkit_channel ch, microkit_msginfo msginfo) +{ + int err = 0; + uint32_t ret_num = 0; + uint32_t argc = microkit_msginfo_get_count(msginfo); + + /* TODO: Check if the channel is valid */ + switch (microkit_msginfo_get_label(msginfo)) { + + case SDDF_CLK_ENABLE: { + if (argc != 1) { + LOG_DRIVER_ERR("Incorrect number of arguments %u != 1\n", argc); + err = CLK_INCORRECT_ARGS; + break; + } + uint32_t clk_id = (uint32_t)microkit_mr_get(SDDF_CLK_PARAM_ID); + LOG_DRIVER("get request clk_enable(%d)\n", clk_id); + err = clk_enable(clk_list[clk_id]); + break; + } + case SDDF_CLK_DISABLE: { + if (argc != 1) { + LOG_DRIVER_ERR("Incorrect number of arguments %u != 1\n", argc); + err = CLK_INCORRECT_ARGS; + break; + } + uint32_t clk_id = (uint32_t)microkit_mr_get(SDDF_CLK_PARAM_ID); + LOG_DRIVER("get request clk_disable(%d)\n", clk_id); + err = clk_disable(clk_list[clk_id]); + break; + } + case SDDF_CLK_GET_RATE: { + if (argc != 1) { + LOG_DRIVER_ERR("Incorrect number of arguments %u != 1\n", argc); + err = CLK_INCORRECT_ARGS; + break; + } + uint32_t clk_id = (uint32_t)microkit_mr_get(SDDF_CLK_PARAM_ID); + uint64_t rate = 0; + err = clk_get_rate(clk_list[clk_id], &rate); + microkit_mr_set(0, rate); + ret_num = 1; + break; + } + case SDDF_CLK_SET_RATE: { + if (argc != 2) { + LOG_DRIVER_ERR("Incorrect number of arguments %u != 1\n", argc); + err = CLK_INCORRECT_ARGS; + break; + } + uint32_t clk_id = (uint32_t)microkit_mr_get(SDDF_CLK_PARAM_ID); + uint64_t req_rate = (uint32_t)microkit_mr_get(SDDF_CLK_PARAM_RATE); + uint64_t rate = 0; + err = clk_set_rate(clk_list[clk_id], req_rate, &rate); + microkit_mr_set(0, rate); + ret_num = 1; + break; + } + case SDDF_CLK_GET_PARENT: { + if (argc != 1) { + LOG_DRIVER_ERR("Incorrect number of arguments %u != 1\n", argc); + err = CLK_INCORRECT_ARGS; + break; + } + uint32_t clk_id = (uint32_t)microkit_mr_get(SDDF_CLK_PARAM_ID); + uint32_t pclk_id = 0; + err = clk_get_parent_id(clk_list[clk_id], &pclk_id); + microkit_mr_set(0, pclk_id); + ret_num = 1; + break; + } + case SDDF_CLK_SET_PARENT: { + if (argc != 2) { + LOG_DRIVER_ERR("Incorrect number of arguments %u != 1\n", argc); + err = CLK_INCORRECT_ARGS; + break; + } + uint32_t clk_id = (uint32_t)microkit_mr_get(SDDF_CLK_PARAM_ID); + uint32_t pclk_idx = (uint32_t)microkit_mr_get(SDDF_CLK_PARAM_PCLK_IDX); + err = clk_set_parent_by_val(clk_list[clk_id], pclk_idx); + microkit_mr_set(0, pclk_idx); + ret_num = 1; + break; + } + default: + LOG_DRIVER_ERR("Unknown request %lu to clock driver from channel %u\n", microkit_msginfo_get_label(msginfo), + ch); + err = CLK_UNKNOWN_REQ; + } + return microkit_msginfo_new(err, ret_num); +} diff --git a/drivers/clk/clk.h b/drivers/clk/clk.h new file mode 100644 index 000000000..f6993c806 --- /dev/null +++ b/drivers/clk/clk.h @@ -0,0 +1,406 @@ + +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2010-2011 Jeremy Kerr + * Copyright (C) 2011-2012 Linaro Ltd + * + * This file is derived from: + * https://github.com/torvalds/linux/blob/ + * 6485cf5ea253d40d507cd71253c9568c5470cd27/include/linux/clk-provider.h + */ + +#pragma once + +#include + +#define TIMER_CH 1 + +// Logging +/* #define DEBUG_DRIVER */ + +#ifdef DEBUG_DRIVER +#define LOG_DRIVER(...) do{ sddf_dprintf("CLK DRIVER|INFO: "); sddf_dprintf(__VA_ARGS__); }while(0) +#else +#define LOG_DRIVER(...) do{}while(0) +#endif + +#define LOG_DRIVER_ERR(...) do{ sddf_printf("CLK DRIVER|ERROR: "); sddf_printf(__VA_ARGS__); }while(0) + +/* + * flags used across common struct clk. these flags should only affect the + * top-level framework. custom flags for dealing with hardware specifics + * belong in struct clk_foo + */ +#define CLK_SET_RATE_GATE BIT(0) /* must be gated across rate change */ +#define CLK_SET_PARENT_GATE BIT(1) /* must be gated across re-parent */ +#define CLK_SET_RATE_PARENT BIT(2) /* propagate rate change up one level */ +#define CLK_IGNORE_UNUSED BIT(3) /* do not gate even if unused */ + /* unused */ + /* unused */ +#define CLK_GET_RATE_NOCACHE BIT(6) /* do not use the cached clk rate */ +#define CLK_SET_RATE_NO_REPARENT BIT(7) /* don't re-parent on rate change */ +#define CLK_GET_ACCURACY_NOCACHE BIT(8) /* do not use the cached clk accuracy */ +#define CLK_RECALC_NEW_RATES BIT(9) /* recalc rates after notifications */ +#define CLK_SET_RATE_UNGATE BIT(10) /* clock needs to run to set rate */ +#define CLK_IS_CRITICAL BIT(11) /* do not gate, ever */ +/* parents need enable during gate/ungate, set rate and re-parent */ +#define CLK_OPS_PARENT_ENABLE BIT(12) +/* duty cycle call may be forwarded to the parent clock */ +#define CLK_DUTY_CYCLE_PARENT BIT(13) + +#define CLK_GATE_SET_TO_DISABLE BIT(0) +#define CLK_GATE_HIWORD_MASK BIT(1) +#define CLK_GATE_BIG_ENDIAN BIT(2) + +#define CLK_DIVIDER_ONE_BASED BIT(0) +#define CLK_DIVIDER_POWER_OF_TWO BIT(1) +#define CLK_DIVIDER_ALLOW_ZERO BIT(2) +#define CLK_DIVIDER_HIWORD_MASK BIT(3) +#define CLK_DIVIDER_ROUND_CLOSEST BIT(4) +#define CLK_DIVIDER_READ_ONLY BIT(5) +#define CLK_DIVIDER_MAX_AT_ZERO BIT(6) +#define CLK_DIVIDER_BIG_ENDIAN BIT(7) + +#define CLK_MUX_INDEX_ONE BIT(0) +#define CLK_MUX_INDEX_BIT BIT(1) +#define CLK_MUX_HIWORD_MASK BIT(2) +#define CLK_MUX_READ_ONLY BIT(3) /* mux can't be changed */ +#define CLK_MUX_ROUND_CLOSEST BIT(4) +#define CLK_MUX_BIG_ENDIAN BIT(5) + +#define CLK_FRAC_DIVIDER_ZERO_BASED BIT(0) +#define CLK_FRAC_DIVIDER_BIG_ENDIAN BIT(1) +#define CLK_FRAC_DIVIDER_POWER_OF_TWO_PS BIT(2) + +struct clk; +struct clk_hw; +struct clk_init_data; + +/** + * struct clk_hw - handle for traversing from a struct clk to its corresponding + * hardware-specific structure. struct clk_hw should be declared within struct + * clk_foo and then referenced by the struct clk instance that uses struct + * clk_foo's clk_ops + * + * @init: pointer to struct clk_init_data that contains the init data shared + * with the common clock framework. + */ +struct clk_hw { + struct clk *clk; + struct clk_init_data *init; +}; + +/** + * struct clk_ops - Callback operations for hardware clocks; these are to + * be provided by the clock implementation, and will be called by drivers + * through the clk_* api. + * + * @enable: Enable the clock atomically. This must not return until the + * clock is generating a valid clock signal, usable by consumer + * devices. Called with enable_lock held. This function must not + * sleep. + * + * @disable: Disable the clock atomically. Called with enable_lock held. + * This function must not sleep. + * + * @is_enabled: Queries the hardware to determine if the clock is enabled. + * This function must not sleep. Optional, if this op is not + * set then the enable count will be used. + * + * @recalc_rate: Recalculate the rate of this clock, by querying hardware. The + * parent rate is an input parameter. It is up to the caller to + * ensure that the prepare_mutex is held across this call. If the + * driver cannot figure out a rate for this clock, it must return + * 0. Returns the calculated rate. Optional, but recommended - if + * this op is not set then clock rate will be initialized to 0. + * + * @round_rate: Given a target rate as input, returns the closest rate actually + * supported by the clock. The parent rate is an input/output + * parameter. + * + * @determine_rate: Given a target rate as input, returns the closest rate + * actually supported by the clock, and optionally the parent clock + * that should be used to provide the clock rate. + * + * @set_parent: Change the input source of this clock; for clocks with multiple + * possible parents specify a new parent by passing in the index + * as a u8 corresponding to the parent in either the .parent_names + * or .parents arrays. This function in affect translates an + * array index into the value programmed into the hardware. + * Returns 0 on success, -EERROR otherwise. + * + * @get_parent: Queries the hardware to determine the parent of a clock. The + * return value is a u8 which specifies the index corresponding to + * the parent clock. This index can be applied to either the + * .parent_names or .parents arrays. In short, this function + * translates the parent value read from hardware into an array + * index. Currently only called when the clock is initialized by + * __clk_init. This callback is mandatory for clocks with + * multiple parents. It is optional (and unnecessary) for clocks + * with 0 or 1 parents. + * + * @set_rate: Change the rate of this clock. The requested rate is specified + * by the second argument, which should typically be the return + * of .round_rate call. The third argument gives the parent rate + * which is likely helpful for most .set_rate implementation. + * Returns 0 on success, -EERROR otherwise. + * + * @init: Perform platform-specific initialization magic. + * This is not used by any of the basic clock types. + * This callback exist for HW which needs to perform some + * initialisation magic for CCF to get an accurate view of the + * clock. It may also be used dynamic resource allocation is + * required. It shall not used to deal with clock parameters, + * such as rate or parents. + * Returns 0 on success, -EERROR otherwise. + * + */ +struct clk_ops { + int (*get_parent)(const struct clk *clk, uint8_t *index); + int (*set_parent)(struct clk *clk, uint8_t index); + uint64_t (*recalc_rate)(const struct clk *clk, uint64_t parent_rate); + int (*set_rate)(const struct clk *clk, uint64_t rate, uint64_t parent_rate); + void (*init)(struct clk *clk); + int (*enable)(struct clk *clk); + int (*disable)(struct clk *clk); + int (*is_enabled)(struct clk *clk); +}; + +struct clk { + struct clk_hw hw; + void *data; + uint64_t base; +}; + +/** + * struct clk_parent_data - clk parent information + * @hw: parent clk_hw pointer (used for clk providers with internal clks) + * @name: globally unique parent name (used as a fallback) + */ +struct clk_parent_data { + const struct clk *clk; + const char *name; +}; + +/** + * struct clk_init_data - holds init data that's common to all clocks and is + * shared between the clock provider and the common clock framework. + * + * @name: clock name + * @ops: operations this clock supports + * @parent_names: array of string names for all possible parents + * @parent_data: array of parent data for all possible parents (when some + * parents are external to the clk controller) + * @parent_clks: array of pointers to all possible parents (when all parents + * are internal to the clk controller) + * @num_parents: number of possible parents + * @flags: framework-level hints and quirks + */ +struct clk_init_data { + uint32_t num_parents; + uint32_t flags; + const char *name; + const struct clk_ops *ops; + const struct clk **parent_clks; + const struct clk_parent_data *parent_data; +}; + +/** + * struct clk_source_data - clock source data + * + * @rate: ouput rate of clock hardware + * + */ +struct clk_source_data { + uint64_t rate; +}; + +/** + * struct clk_gate_data - gate specific data + * + * @offset: offset of the register controlling gate + * @bit_idx: single bit controlling gate + * @flags: hardware-specific flags + * + * Flags: + * CLK_GATE_SET_TO_DISABLE - by default this clock sets the bit at bit_idx to + * enable the clock. Setting this flag does the opposite: setting the bit + * disable the clock and clearing it enables the clock + * CLK_GATE_HIWORD_MASK - The gate settings are only in lower 16-bit + * of this register, and mask of gate bits are in higher 16-bit of this + * register. While setting the gate bits, higher 16-bit should also be + * updated to indicate changing gate bits. + */ +struct clk_gate_data { + uint32_t offset; + uint8_t bit_idx; + uint8_t flags; +}; + +/** + * struct clk_div_data - adjustable divider clock + * + * @reg: register containing the divider + * @shift: shift to the divider bit field + * @width: width of the divider bit field + * @table: array of value/divider pairs, last entry should have div = 0 + * @lock: register lock + * + * Clock with an adjustable divider affecting its output frequency. Implements + * .recalc_rate, .set_rate and .round_rate + * + * @flags: + * CLK_DIVIDER_ONE_BASED - by default the divisor is the value read from the + * register plus one. If CLK_DIVIDER_ONE_BASED is set then the divider is + * the raw value read from the register, with the value of zero considered + * invalid, unless CLK_DIVIDER_ALLOW_ZERO is set. + * CLK_DIVIDER_POWER_OF_TWO - clock divisor is 2 raised to the value read from + * the hardware register + * CLK_DIVIDER_ALLOW_ZERO - Allow zero divisors. For dividers which have + * CLK_DIVIDER_ONE_BASED set, it is possible to end up with a zero divisor. + * Some hardware implementations gracefully handle this case and allow a + * zero divisor by not modifying their input clock + * (divide by one / bypass). + * CLK_DIVIDER_HIWORD_MASK - The divider settings are only in lower 16-bit + * of this register, and mask of divider bits are in higher 16-bit of this + * register. While setting the divider bits, higher 16-bit should also be + * updated to indicate changing divider bits. + * CLK_DIVIDER_ROUND_CLOSEST - Makes the best calculated divider to be rounded + * to the closest integer instead of the up one. + * CLK_DIVIDER_READ_ONLY - The divider settings are preconfigured and should + * not be changed by the clock framework. + * CLK_DIVIDER_MAX_AT_ZERO - For dividers which are like CLK_DIVIDER_ONE_BASED + * except when the value read from the register is zero, the divisor is + * 2^width of the field. + */ +struct clk_div_data { + uint32_t offset; + uint8_t shift; + uint8_t width; + uint8_t flags; + /* const struct clk_div_table *table; */ +}; + +/** + * struct clk_fixed_factor_data - fixed multiplier and divider clock + * + * @mult: multiplier + * @div: divider + * @flags: behavior modifying flags + * + * Clock with a fixed multiplier and divider. The output frequency is the + * parent clock rate divided by div and multiplied by mult. + * Implements .recalc_rate, .set_rate, .round_rate and .recalc_accuracy + * + * Flags: + * * CLK_FIXED_FACTOR_FIXED_ACCURACY - Use the value in @acc instead of the + * parent clk accuracy. + */ +struct clk_fixed_factor_data { + uint32_t mult; + uint32_t div; + uint8_t flags; +}; + +/** + * struct clk_mux_data - multiplexer clock + * + * @reg: register controlling multiplexer + * @table: array of register values corresponding to the parent index + * @shift: shift to multiplexer bit field + * @mask: mask of mutliplexer bit field + * @flags: hardware-specific flags + * @lock: register lock + * + * Clock with multiple selectable parents. Implements .get_parent, .set_parent + * and .recalc_rate + * + * Flags: + * CLK_MUX_INDEX_ONE - register index starts at 1, not 0 + * CLK_MUX_INDEX_BIT - register index is a single bit (power of two) + * CLK_MUX_HIWORD_MASK - The mux settings are only in lower 16-bit of this + * register, and mask of mux bits are in higher 16-bit of this register. + * While setting the mux bits, higher 16-bit should also be updated to + * indicate changing mux bits. + * CLK_MUX_READ_ONLY - The mux registers can't be written, only read in the + * .get_parent clk_op. + * CLK_MUX_ROUND_CLOSEST - Use the parent rate that is closest to the desired + * frequency. + */ +struct clk_mux_data { + uint32_t offset; + uint32_t *table; + uint32_t mask; + uint8_t shift; + uint8_t flags; +}; + +/** + * function get_clk_by_name() - get the pointer to clk struction by id + * + */ +struct clk *get_clk_by_name(const char *name); + +/** + * function clk_msr_stat() - measure clock rates + * + * @clk_list: array of pointers to the clocks on SoC + * + * All parent clocks will be parsed by name and bound to the struct clk + */ +int clk_msr_stat(struct clk *clk_list[]); + +/** + * function clk_probe() - initialise all clocks + * + * @clk_list: array of pointers to the clocks on SoC + * + * All parent clocks will be parsed by name and bound to the struct clk + */ +void clk_probe(struct clk *clk_list[]); + +/** + * function get_clk_list() - initialise all clocks + * + * get a list of clock components for specific board + */ +struct clk **get_clk_list(); + +/** + * function get_parent() - get the current parent clk + * + * @clk: pointer to the current clk + */ +const struct clk *get_parent(const struct clk *clk); + +/** + * function clk_get_rate() - get the rate of target clock + * + * @clk: pointer to the current clk + * + */ +int clk_get_rate(const struct clk *clk, uint64_t *rate); + +/** + * function clk_enable() - enable the target clock signal + * + * @clk: pointer to the current clk + */ +int clk_enable(struct clk *clk); + +/** + * function clk_disable() - disable the target clock signal + * + * @clk: pointer to the current clk + */ +int clk_disable(struct clk *clk); + +/** + * function clk_set_rate() - set the nearest rate to the requested rate for + * the target clock + * + * @clk: pointer to the current clk + * @req_rate: request rate + * @rate: pointer to result variable + */ +int clk_set_rate(struct clk *clk, uint64_t req_rate, uint64_t *rate); diff --git a/drivers/clk/clk_utils.h b/drivers/clk/clk_utils.h new file mode 100644 index 000000000..d22d451e7 --- /dev/null +++ b/drivers/clk/clk_utils.h @@ -0,0 +1,35 @@ +/* + * Copyright 2024, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#define MASK(width) ((1UL << width) - 1) + +#define abs(x) ( ( (x) < 0) ? -(x) : (x) ) + +#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) + +#define do_div(n, base) ({ \ + uint32_t __base = (base); \ + uint32_t __rem; \ + __rem = ((uint64_t)(n)) % __base; \ + (n) = ((uint64_t)(n)) / __base; \ + __rem; \ + }) + +#define DIV_ROUND_DOWN_ULL(ll, d) \ + ({ uint64_t _tmp = (ll); do_div(_tmp, d); _tmp; }) + +#define DIV_ROUND_UP_ULL(ll, d) \ + DIV_ROUND_DOWN_ULL((uint64_t)(ll) + (d) - 1, (d)) + +#define DIV_ROUND_CLOSEST_ULL(x, divisor)( \ +{ \ + typeof(divisor) __d = divisor; \ + unsigned long long _tmp = (x) + (__d) / 2; \ + do_div(_tmp, __d); \ + _tmp; \ +} \ +) diff --git a/drivers/clk/create_clk_config.py b/drivers/clk/create_clk_config.py new file mode 100644 index 000000000..f1255ccec --- /dev/null +++ b/drivers/clk/create_clk_config.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 + +# Copyright 2024, UNSW +# SPDX-License-Identifier: BSD-2-Clause + +import os +import sys +from typing import List +from devicetree import edtlib, dtlib + +supported_compat_str_board = { "hardkernel,odroid-c4", "fsl,imx8mm-evk", "fsl,imx8mq" } + +debug_parser = True +clock_list = {} + +def log_normal_parser(print_str: str) -> None: + if (debug_parser): + print("PARSER|INFO: " + print_str) + +def log_warning_parser(print_str: str) -> None: + sys.stderr.write("PARSER|WARNING: " + print_str) + +def log_error_parser(print_str: str) -> None: + sys.stderr.write("PARSER|ERROR: " + print_str) + +def parse_clock_list(dt: dtlib.DT, clocks: List) -> (List, List): + pnodes = [] + clk_ids = [] + i = 0 + + while (i < len(clocks)): + if clocks[i] == 0: + clk_ids.append(clocks[i]) + i += 1 + continue + pnode = devicetree.phandle2node[clocks[i]] + if "#clock-cells" in pnode.props and pnode.props["#clock-cells"].to_num() == 0: + # TODO: consider the case of `xtal` and represent this clock another way + clk_ids.append(clocks[i]) + pnodes.append(pnode) + elif clocks[i] > 0: + clk_ids.append(clocks[i+1]) + pnodes.append(pnode) + i += 1 + else: + clk_ids.append(-1) + i += 1 + + return clk_ids, pnodes + +def add_clock(clk_id: int, frequency: int, pclk_id: int): + if clk_id in clock_list.keys(): + if clock_list[clk_id][0] == 0 and frequency: + clock_list[clk_id] = (frequency, clock_list[clk_id][1]) + if clock_list[clk_id][1] == 0 and pclk_id: + clock_list[clk_id] = (clock_list[clk_id][0], pclk_id) + else: + clock_list[clk_id] = (frequency, pclk_id) + +def extract_clocks(dt: dtlib.DT, node: dtlib.Node) -> List: + props = list(node.props.keys()) + if "clocks" in props: + clocks = node.props["clocks"].to_nums() + clock_ids, pnodes = parse_clock_list(devicetree, clocks) + for clk_id in clock_ids: + add_clock(clk_id, 0, 0) + for pnode in pnodes: + if pnode != node: + extract_clocks(dt, pnode) + + if "max-frequency" in props: + max_frequency = node.props["max-frequency"].to_nums() + + if "assigned-clocks" in props and "assigned-clock-rates" in props: + assigned_clocks, pnodes = parse_clock_list(devicetree, node.props["assigned-clocks"].to_nums()) + assigned_clock_rates = node.props["assigned-clock-rates"].to_nums() + for clk_id, clk_rate in zip(assigned_clocks, assigned_clock_rates): + if (clk_rate): + add_clock(clk_id, clk_rate, 0) + for pnode in pnodes: + if pnode != node: + extract_clocks(dt, pnode) + + if "assigned-clocks" in props and "assigned-clock-parents" in props: + assigned_clocks, pnodes = parse_clock_list(devicetree, node.props["assigned-clocks"].to_nums()) + assigned_clock_parents, ppnodes = parse_clock_list(devicetree, node.props["assigned-clock-parents"].to_nums()) + for clk_id, pclk_id in zip(assigned_clocks, assigned_clock_parents): + if (pclk_id): + add_clock(clk_id, 0, pclk_id) + for pnode in pnodes: + if pnode != node: + extract_clocks(dt, pnode) + for pnode in ppnodes: + if pnode != node: + extract_clocks(dt, pnode) + +def write_configs_to_headerfile(path: str) -> None: + with open(path + '/clk_config.h', "w") as f: + f.write("#include \n") + f.write("#define NUM_DEVICE_CLKS {}\n\n".format(len(clock_list))) + + clk_cfg_strs = [] + for key, value in clock_list.items(): + clk_cfg_strs.append(" {{ .clk_id = {}, .frequency = {}, .pclk_id = {} }}".format(key, value[0], value[1])) + + f.write("static struct clk_cfg clk_configs[] = {{\n{}\n}};".format(",\n".join(clk_cfg_strs))) + +if __name__ == "__main__": + supported = False + devicetree = dtlib.DT(sys.argv[1], force=True) + for compat_str in devicetree.root.props["compatible"].to_strings(): + if compat_str in supported_compat_str_board: + supported = True + break + + if not supported: + log_error_parser("this board is not supported.") + exit(1) + + + for node in devicetree.node_iter(): + props = list(node.props.keys()) + if "status" in props: + status = node.props["status"].to_string() + if status == "okay": + extract_clocks(devicetree, node) + + write_configs_to_headerfile(sys.argv[2]) + + print('Initial clock configuration has been saved in ' + sys.argv[2] + '/clk_config.h') diff --git a/drivers/clk/imx/clk-imx.c b/drivers/clk/imx/clk-imx.c new file mode 100644 index 000000000..acc44e518 --- /dev/null +++ b/drivers/clk/imx/clk-imx.c @@ -0,0 +1,504 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2010-2011 Canonical Ltd + * Copyright (C) 2011-2012 Mike Turquette, Linaro Ltd + * + * Gated clock implementation + * Source: https://github.com/torvalds/linux/blob/ + * cfaaa7d010d1fc58f9717fcc8591201e741d2d49/drivers/clk/imx/clk-gate2.c + */ + +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2018 NXP. + * + * Source: https://github.com/torvalds/linux/blob/ + * cfaaa7d010d1fc58f9717fcc8591201e741d2d49/drivers/clk/imx/clk-frac-pll.c + */ + +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Copyright 2018 NXP. + * + * Source: https://github.com/torvalds/linux/blob/ + * cfaaa7d010d1fc58f9717fcc8591201e741d2d49/drivers/clk/imx/clk-sscg-pll.c + */ + +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2018 NXP + * + * Source: https://github.com/torvalds/linux/blob/ + * cfaaa7d010d1fc58f9717fcc8591201e741d2d49/drivers/clk/imx/clk-composite-8m.c + */ + +#include +#include +#include +#include + +#define PLL_FRAC_DENOM 0x1000000 + +static int clk_gate2_enable(struct clk *clk) +{ + struct clk_gate_data *data = (struct clk_gate_data *)(clk->data); + + return regmap_update_bits(clk->base, data->offset, data->bit_idx, MASK(2), 0x3); +} + +static int clk_gate2_disable(struct clk *clk) +{ + struct clk_gate_data *data = (struct clk_gate_data *)(clk->data); + + return regmap_update_bits(clk->base, data->offset, data->bit_idx, MASK(2), 0); +} + +static int clk_gate2_is_enabled(struct clk *clk) +{ + struct clk_gate_data *data = (struct clk_gate_data *)(clk->data); + + if (regmap_read_bits(clk->base, data->offset, data->bit_idx, MASK(2)) == 0x3) + return 1; + + return 0; +} + +const struct clk_ops clk_gate2_ops = { + .enable = clk_gate2_enable, + .disable = clk_gate2_disable, + /* .disable_unused = clk_gate2_disable_unused, */ + .is_enabled = clk_gate2_is_enabled, +}; + +static uint64_t clk_pll_recalc_rate(const struct clk *clk, uint64_t prate) +{ + struct clk_frac_pll_data *data = (struct clk_frac_pll_data *)(clk->data); + uint64_t temp_rate = prate; + uint64_t rate; + + /* Output Divider value is (n + 1) * 2 */ + uint32_t output_div_val = regmap_read_bits(clk->base, data->offset, 0, MASK(5)); + output_div_val = (output_div_val + 1) * 2; + + /* Valid Frac Divider value is 1 to 2^24 */ + uint32_t frac_div_val = regmap_read_bits(clk->base, data->offset + 0x4, 7, MASK(24)); + + /* The specs say "Valid Int Divider value is 1 to 32", but seems wrong */ + uint32_t int_div_val = regmap_read_bits(clk->base, data->offset + 0x4, 0, MASK(7)); + + temp_rate *= 8; + temp_rate *= frac_div_val; + do_div(temp_rate, PLL_FRAC_DENOM); + do_div(temp_rate, output_div_val); + + /* Frac Divider value is (n) */ + rate = prate * 8 * (int_div_val + 1); + do_div(rate, output_div_val); + rate += temp_rate; + + return rate; +} + +static int clk_pll_set_rate(const struct clk *clk, uint64_t rate, uint64_t parent_rate) +{ + struct clk_frac_pll_data *data = (struct clk_frac_pll_data *)(clk->data); + uint32_t divfi, divff; + uint64_t temp64; + int ret; + + parent_rate *= 8; + rate *= 2; + divfi = rate / parent_rate; + temp64 = parent_rate * divfi; + temp64 = rate - temp64; + temp64 *= PLL_FRAC_DENOM; + do_div(temp64, parent_rate); + divff = temp64; + + /** + * Keep PLL_OUTPUT_VAL at zero. + * + * pllout = parent_rate * 8 / 2 * DIVF_VAL; + * where DIVF_VAL = 1 + DIVFI + DIVFF / 2^24. + */ + + /* Write PLL_FRAC_DIV_CTL */ + ret = regmap_update_bits(clk->base, data->offset + 0x4, 7, MASK(24), divff); + if (ret) + return ret; + + /* Write PLL_INT_DIV_CTL */ + ret = regmap_update_bits(clk->base, data->offset + 0x4, 0, MASK(7), divfi - 1); + if (ret) + return ret; + + /* Clear PLL_OUTPUT_DIV_VAL */ + ret = regmap_update_bits(clk->base, data->offset, 0, MASK(5), 0); + if (ret) + return ret; + + /* Set the NEV_DIV_VAL to reload the DIVFI and DIVFF */ + ret = regmap_update_bits(clk->base, data->offset, 12, MASK(1), 1); + if (ret) + return ret; + + // Wait ack + uint32_t timeout = 10000000; + while (--timeout) { + if (regmap_read_bits(clk->base, data->offset, 11, MASK(1))) { + break; + } + } + + if (!timeout) + return CLK_FAILED_OP; + + ret = regmap_update_bits(clk->base, data->offset, 14, MASK(1), 0); + if (ret) + return ret; + + /* clear the NEV_DIV_VAL */ + return regmap_update_bits(clk->base, data->offset, 12, MASK(1), 1); +} + +const struct clk_ops clk_frac_pll_ops = { + /* .prepare = clk_pll_prepare, */ + /* .unprepare = clk_pll_unprepare, */ + /* .is_prepared = clk_pll_is_prepared, */ + .recalc_rate = clk_pll_recalc_rate, + /* .round_rate = clk_pll_round_rate, */ + .set_rate = clk_pll_set_rate, +}; + +static uint64_t clk_sscg_pll_recalc_rate(const struct clk *clk, uint64_t prate) +{ + struct clk_sscg_pll_data *data = (struct clk_sscg_pll_data *)(clk->data); + uint64_t temp_rate = prate; + + uint32_t divr1 = regmap_read_bits(clk->base, data->offset + 0x8, 25, MASK(3)); + uint32_t divr2 = regmap_read_bits(clk->base, data->offset + 0x8, 19, MASK(6)); + uint32_t divf1 = regmap_read_bits(clk->base, data->offset + 0x8, 13, MASK(6)); + uint32_t divf2 = regmap_read_bits(clk->base, data->offset + 0x8, 7, MASK(6)); + uint32_t divq = regmap_read_bits(clk->base, data->offset + 0x8, 1, MASK(6)); + + if (regmap_read_bits(clk->base, data->offset, 4, MASK(1))) { + temp_rate = prate; + } else if (regmap_read_bits(clk->base, data->offset, 5, MASK(1))) { + temp_rate *= divf2; + do_div(temp_rate, (divr2 + 1) * (divq + 1)); + } else { + temp_rate *= 2; + temp_rate *= (divf1 + 1) * (divf2 + 1); + do_div(temp_rate, (divr1 + 1) * (divr2 + 1) * (divq + 1)); + } + + return 0; +} + +/* static int clk_sscg_pll_set_rate(const struct clk *clk, uint64_t rate, uint64_t parent_rate) */ +/* { */ +/* /\* struct clk_sscg_pll_data *data = (struct clk_sscg_pll_data *)(clk->data); *\/ */ + +/* /\* TODO: to be implemented. *\/ */ + +/* return 0; */ +/* } */ + +static int clk_sscg_pll_get_parent(const struct clk *clk, uint8_t *index) +{ + struct clk_sscg_pll_data *data = (struct clk_sscg_pll_data *)(clk->data); + + if (regmap_read_bits(clk->base, data->offset, 4, MASK(1))) { + *index = data->bypass2; + } else if (regmap_read_bits(clk->base, data->offset, 5, MASK(1))) { + *index = data->bypass1; + } + + return 0; +} + +static int clk_sscg_pll_set_parent(struct clk *clk, uint8_t index) +{ + /* struct clk_sscg_pll_data *data = (struct clk_sscg_pll_data *)(clk->data); */ + + /* TODO: This operation is based on `setup.bypass` instead of index + * passed from callee, need to be decided by clock manager */ + + return 0; +} + +const struct clk_ops clk_sscg_pll_ops = { + /* .prepare = clk_sscg_pll_prepare, */ + /* .unprepare = clk_sscg_pll_unprepare, */ + /* .is_prepared = clk_sscg_pll_is_prepared, */ + .recalc_rate = clk_sscg_pll_recalc_rate, + /* .set_rate = clk_sscg_pll_set_rate, */ + .set_parent = clk_sscg_pll_set_parent, + .get_parent = clk_sscg_pll_get_parent, + /* .determine_rate = clk_sscg_pll_determine_rate, */ +}; + +static int imx8mx_clk_composite_determine_rate(uint64_t rate, uint64_t parent_rate, uint32_t *prev_div, + uint32_t *post_div) +{ + uint32_t min_error = UINT_MAX; + int ret = CLK_FAILED_OP; + + for (int i = 1; i <= PCG_PREDIV_MAX; i++) { + for (int j = 1; j <= PCG_DIV_MAX; j++) { + uint32_t error = abs(((parent_rate / i) / j) - rate); + if (error < min_error) { + *prev_div = i; + *post_div = j; + min_error = error; + ret = 0; + } + } + } + + return ret; +} + +static uint64_t imx8m_clk_core_slice_recalc_rate(const struct clk *clk, uint64_t prate) +{ + struct clk_core_slice_data *data = (struct clk_core_slice_data *)(clk->data); + + uint32_t div_val = regmap_read_bits(clk->base, data->offset, data->div_shift, MASK(data->div_width)); + /* Divider value is n+1 */ + return DIV_ROUND_UP_ULL((uint64_t)prate, div_val + 1); +} + +static int imx8m_clk_core_slice_set_rate(const struct clk *clk, uint64_t rate, uint64_t parent_rate) +{ + struct clk_core_slice_data *data = (struct clk_core_slice_data *)(clk->data); + + uint32_t div = DIV_ROUND_UP(parent_rate, rate) - 1; + + return regmap_update_bits(clk->base, data->offset, data->div_shift, MASK(data->div_width), div); +} + +static int imx8m_clk_core_slice_get_parent(const struct clk *clk, uint8_t *index) +{ + struct clk_core_slice_data *data = (struct clk_core_slice_data *)(clk->data); + + uint32_t num_parents = clk->hw.init->num_parents; + uint32_t val = regmap_read_bits(clk->base, data->offset, data->mux_shift, data->mux_mask); + + if (val >= num_parents) + return CLK_UNKNOWN_TARGET; + + *index = val; + return 0; +} + +static int imx8m_clk_core_slice_set_parent(struct clk *clk, uint8_t index) +{ + struct clk_core_slice_data *data = (struct clk_core_slice_data *)(clk->data); + + /* + * write twice to make sure non-target interface + * SEL_A/B point the same clk input. + */ + regmap_update_bits(clk->base, data->offset, data->mux_shift, data->mux_mask, index); + regmap_update_bits(clk->base, data->offset, data->mux_shift, data->mux_mask, index); + + return 0; +} + +const struct clk_ops clk_core_slice_ops = { + .recalc_rate = imx8m_clk_core_slice_recalc_rate, + /* .round_rate = imx8m_clk_core_slice_round_rate, */ + .set_rate = imx8m_clk_core_slice_set_rate, + /* .determine_rate = imx8m_clk_core_slice_determine_rate, */ + .get_parent = imx8m_clk_core_slice_get_parent, + .set_parent = imx8m_clk_core_slice_set_parent, +}; + +static uint64_t imx8m_clk_common_slice_recalc_rate(const struct clk *clk, uint64_t prate) +{ + struct clk_common_slice_data *data = (struct clk_common_slice_data *)(clk->data); + + uint32_t prediv_val = regmap_read_bits(clk->base, data->offset, data->prevdiv_shift, MASK(data->prevdiv_width)); + /* Divider value is n+1 */ + uint64_t prediv_rate = DIV_ROUND_UP_ULL((uint64_t)prate, prediv_val + 1); + + uint32_t postdiv_val = regmap_read_bits(clk->base, data->offset, data->postdiv_shift, MASK(data->postdiv_width)); + /* Divider value is n+1 */ + return DIV_ROUND_UP_ULL((uint64_t)prediv_rate, postdiv_val + 1); +} + +static int imx8m_clk_common_slice_set_rate(const struct clk *clk, uint64_t rate, uint64_t parent_rate) +{ + struct clk_common_slice_data *data = (struct clk_common_slice_data *)(clk->data); + uint32_t prev_div = 1; + uint32_t post_div = 1; + + int ret = imx8mx_clk_composite_determine_rate(rate, parent_rate, &prev_div, &post_div); + if (ret) + return ret; + + ret = regmap_update_bits(clk->base, data->offset, data->prevdiv_shift, MASK(data->prevdiv_width), prev_div); + if (ret) + return ret; + + return regmap_update_bits(clk->base, data->offset, data->postdiv_shift, MASK(data->postdiv_width), post_div); +} + +static int imx8m_clk_common_slice_get_parent(const struct clk *clk, uint8_t *index) +{ + struct clk_common_slice_data *data = (struct clk_common_slice_data *)(clk->data); + + uint32_t num_parents = clk->hw.init->num_parents; + uint32_t val = regmap_read_bits(clk->base, data->offset, data->mux_shift, data->mux_mask); + + if (val >= num_parents) + return CLK_UNKNOWN_TARGET; + + *index = val; + return 0; +} + +static int imx8m_clk_common_slice_set_parent(struct clk *clk, uint8_t index) +{ + struct clk_common_slice_data *data = (struct clk_common_slice_data *)(clk->data); + + /* + * write twice to make sure non-target interface + * SEL_A/B point the same clk input. + */ + regmap_update_bits(clk->base, data->offset, data->mux_shift, data->mux_mask, index); + regmap_update_bits(clk->base, data->offset, data->mux_shift, data->mux_mask, index); + + return 0; +} + +const struct clk_ops clk_common_slice_ops = { + .recalc_rate = imx8m_clk_common_slice_recalc_rate, + /* .round_rate = imx8m_clk_common_slice_round_rate, */ + .set_rate = imx8m_clk_common_slice_set_rate, + /* .determine_rate = imx8m_clk_common_slice_determine_rate, */ + .get_parent = imx8m_clk_common_slice_get_parent, + .set_parent = imx8m_clk_common_slice_set_parent, +}; + +static uint64_t imx8m_clk_bus_slice_recalc_rate(const struct clk *clk, uint64_t prate) +{ + struct clk_bus_slice_data *data = (struct clk_bus_slice_data *)(clk->data); + + uint32_t prediv_val = regmap_read_bits(clk->base, data->offset, data->prevdiv_shift, MASK(data->prevdiv_width)); + /* Divider value is n+1 */ + uint64_t prediv_rate = DIV_ROUND_UP_ULL((uint64_t)prate, prediv_val + 1); + + uint32_t postdiv_val = regmap_read_bits(clk->base, data->offset, data->postdiv_shift, MASK(data->postdiv_width)); + /* Divider value is n+1 */ + return DIV_ROUND_UP_ULL((uint64_t)prediv_rate, postdiv_val + 1); +} + +static int imx8m_clk_bus_slice_set_rate(const struct clk *clk, uint64_t rate, uint64_t parent_rate) +{ + struct clk_bus_slice_data *data = (struct clk_bus_slice_data *)(clk->data); + uint32_t prev_div = 1; + uint32_t post_div = 1; + + int ret = imx8mx_clk_composite_determine_rate(rate, parent_rate, &prev_div, &post_div); + if (ret) + return ret; + + ret = regmap_update_bits(clk->base, data->offset, data->prevdiv_shift, MASK(data->prevdiv_width), prev_div); + if (ret) + return ret; + + return regmap_update_bits(clk->base, data->offset, data->postdiv_shift, MASK(data->postdiv_width), post_div); +} + +static int imx8m_clk_bus_slice_get_parent(const struct clk *clk, uint8_t *index) +{ + struct clk_bus_slice_data *data = (struct clk_bus_slice_data *)(clk->data); + + uint32_t num_parents = clk->hw.init->num_parents; + uint32_t val = regmap_read_bits(clk->base, data->offset, data->mux_shift, data->mux_mask); + + if (val >= num_parents) + return CLK_UNKNOWN_TARGET; + + *index = val; + return 0; +} + +static int imx8m_clk_bus_slice_set_parent(struct clk *clk, uint8_t index) +{ + struct clk_bus_slice_data *data = (struct clk_bus_slice_data *)(clk->data); + + /* + * write twice to make sure non-target interface + * SEL_A/B point the same clk input. + */ + regmap_update_bits(clk->base, data->offset, data->mux_shift, data->mux_mask, index); + regmap_update_bits(clk->base, data->offset, data->mux_shift, data->mux_mask, index); + + return 0; +} + +const struct clk_ops clk_bus_slice_ops = { + .recalc_rate = imx8m_clk_bus_slice_recalc_rate, + /* .round_rate = imx8m_clk_composite_divider_round_rate, */ + .set_rate = imx8m_clk_bus_slice_set_rate, + /* .determine_rate = imx8m_divider_determine_rate, */ + .get_parent = imx8m_clk_bus_slice_get_parent, + .set_parent = imx8m_clk_bus_slice_set_parent, +}; + +static int imx8m_cpu_set_rate(const struct clk *clk, uint64_t rate, uint64_t parent_rate) +{ + // Switch parent clock to void race condition + struct clk_cpu_data *data = (struct clk_cpu_data *)clk->data; + struct clk *cpu_clk = data->mux; + + int i; + uint32_t num_parents = cpu_clk->hw.init->num_parents; + for (i = 0; i < num_parents; i++) { + if (get_clk_by_name(cpu_clk->hw.init->parent_data[i].name) == data->step) { + cpu_clk->hw.init->ops->set_parent((struct clk *)clk, i); + break; + } + } + if (i == num_parents) { + LOG_DRIVER_ERR("Failed to switch parent clock 1\n"); + return CLK_FAILED_OP; + } + + // Adjust clock rate of IMX8MQ_ARM_PLL_OUT + struct clk *arm_pll_clk = data->pll; + uint64_t res_rate = 0; + int err = clk_set_rate(arm_pll_clk, rate, &res_rate); + if (err) { + LOG_DRIVER_ERR("Failed to adjust CPU frequency\n"); + return err; + } + + // Switch parent clock back to IMX8MQ_ARM_PLL + for (i = 0; i < num_parents; i++) { + if (get_clk_by_name(cpu_clk->hw.init->parent_data[i].name) == data->pll) { + cpu_clk->hw.init->ops->set_parent((struct clk *)clk, i); + break; + } + } + if (i == num_parents) { + LOG_DRIVER_ERR("Failed to switch parent clock 3\n"); + return CLK_FAILED_OP; + } + + return 0; + +} + +static uint64_t imx8m_cpu_recalc_rate(const struct clk *clk, uint64_t prate) +{ + (void)clk; + return prate; +} + +const struct clk_ops clk_cpu_ops = { + .recalc_rate = imx8m_cpu_recalc_rate, + .set_rate = imx8m_cpu_set_rate, +}; diff --git a/drivers/clk/imx/clk-imx8mq.c b/drivers/clk/imx/clk-imx8mq.c new file mode 100644 index 000000000..0115dcdef --- /dev/null +++ b/drivers/clk/imx/clk-imx8mq.c @@ -0,0 +1,2954 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2018 NXP. + * Copyright (C) 2017 Pengutronix, Lucas Stach + */ + +#include +#include +#include +#include +#include +#include +#include + +uintptr_t ccm_base = 0x3200000; +uintptr_t ccm_analog_base = 0x3300000; + +static struct clk_parent_data pll_ref_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "osc_27m", + }, + { + .name = "hdmi_phy_27m", + }, + { + .name = "dummy", + }, +}; + +static struct clk_parent_data arm_pll_bypass_sels[] = { + { + .name = "arm_pll", + }, + { + .name = "arm_pll_ref_sel", + }, +}; + +static struct clk_parent_data gpu_pll_bypass_sels[] = { + { + .name = "gpu_pll", + }, + { + .name = "gpu_pll_ref_sel", + }, +}; + +static struct clk_parent_data vpu_pll_bypass_sels[] = { + { + .name = "vpu_pll", + }, + { + .name = "vpu_pll_ref_sel", + }, +}; + +static struct clk_parent_data audio_pll1_bypass_sels[] = { + { + .name = "audio_pll1", + }, + { + .name = "audio_pll1_ref_sel", + }, +}; + +static struct clk_parent_data audio_pll2_bypass_sels[] = { + { + .name = "audio_pll2", + }, + { + .name = "audio_pll2_ref_sel", + }, +}; + +static struct clk_parent_data video_pll1_bypass_sels[] = { + { + .name = "video_pll1", + }, + { + .name = "video_pll1_ref_sel", + }, +}; + +static struct clk_parent_data sys3_pll_out_sels[] = { + { + .name = "sys3_pll1_ref_sel", + }, +}; + +static struct clk_parent_data dram_pll_out_sels[] = { + { + .name = "dram_pll1_ref_sel", + }, +}; + +static struct clk_parent_data video2_pll_out_sels[] = { + { + .name = "video2_pll1_ref_sel", + }, +}; + +static struct clk_parent_data imx8mq_a53_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "arm_pll_out", + }, + { + .name = "sys2_pll_500m", + }, + { + .name = "sys2_pll_1000m", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys1_pll_400m", + }, + { + .name = "audio_pll1_out", + }, + { + .name = "sys3_pll_out", + }, +}; + +static struct clk_parent_data imx8mq_a53_core_sels[] = { + { + .name = "arm_a53_div", + }, + { + .name = "arm_pll_out", + }, +}; + +static struct clk_parent_data imx8mq_arm_m4_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys2_pll_200m", + }, + { + .name = "sys2_pll_250m", + }, + { + .name = "sys1_pll_266m", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "audio_pll1_out", + }, + { + .name = "video_pll1_out", + }, + { + .name = "sys3_pll_out", + }, +}; + +static struct clk_parent_data imx8mq_vpu_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "arm_pll_out", + }, + { + .name = "sys2_pll_500m", + }, + { + .name = "sys2_pll_1000m", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys1_pll_400m", + }, + { + .name = "audio_pll1_out", + }, + { + .name = "vpu_pll_out", + }, +}; + +static struct clk_parent_data imx8mq_gpu_core_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "gpu_pll_out", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "sys2_pll_1000m", + }, + { + .name = "audio_pll1_out", + }, + { + .name = "video_pll1_out", + }, + { + .name = "audio_pll2_out", + }, +}; + +static struct clk_parent_data imx8mq_gpu_shader_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "gpu_pll_out", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "sys2_pll_1000m", + }, + { + .name = "audio_pll1_out", + }, + { + .name = "video_pll1_out", + }, + { + .name = "audio_pll2_out", + }, +}; + +static struct clk_parent_data imx8mq_main_axi_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys2_pll_333m", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys2_pll_250m", + }, + { + .name = "sys2_pll_1000m", + }, + { + .name = "audio_pll1_out", + }, + { + .name = "video_pll1_out", + }, + { + .name = "sys1_pll_100m", + }, +}; + +static struct clk_parent_data imx8mq_enet_axi_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys1_pll_266m", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys2_pll_250m", + }, + { + .name = "sys2_pll_200m", + }, + { + .name = "audio_pll1_out", + }, + { + .name = "video_pll1_out", + }, + { + .name = "sys3_pll_out", + }, +}; + +static struct clk_parent_data imx8mq_nand_usdhc_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys1_pll_266m", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys2_pll_200m", + }, + { + .name = "sys1_pll_133m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "sys2_pll_250m", + }, + { + .name = "audio_pll1_out", + }, +}; + +static struct clk_parent_data imx8mq_vpu_bus_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "vpu_pll_out", + }, + { + .name = "audio_pll2_out", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "sys2_pll_1000m", + }, + { + .name = "sys2_pll_200m", + }, + { + .name = "sys1_pll_100m", + }, +}; + +static struct clk_parent_data imx8mq_disp_axi_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys2_pll_125m", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "sys1_pll_400m", + }, + { + .name = "audio_pll2_out", + }, + { + .name = "clk_ext1", + }, + { + .name = "clk_ext4", + }, +}; + +static struct clk_parent_data imx8mq_disp_apb_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys2_pll_125m", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "sys1_pll_40m", + }, + { + .name = "audio_pll2_out", + }, + { + .name = "clk_ext1", + }, + { + .name = "clk_ext3", + }, +}; + +static struct clk_parent_data imx8mq_disp_rtrm_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys2_pll_200m", + }, + { + .name = "sys1_pll_400m", + }, + { + .name = "audio_pll1_out", + }, + { + .name = "video_pll1_out", + }, + { + .name = "clk_ext2", + }, + { + .name = "clk_ext3", + }, +}; + +static struct clk_parent_data imx8mq_usb_bus_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys2_pll_500m", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys2_pll_100m", + }, + { + .name = "sys2_pll_200m", + }, + { + .name = "clk_ext2", + }, + { + .name = "clk_ext4", + }, + { + .name = "audio_pll2_out", + }, +}; + +static struct clk_parent_data imx8mq_gpu_axi_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "gpu_pll_out", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "sys2_pll_1000m", + }, + { + .name = "audio_pll1_out", + }, + { + .name = "video_pll1_out", + }, + { + .name = "audio_pll2_out", + }, +}; + +static struct clk_parent_data imx8mq_gpu_ahb_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "gpu_pll_out", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "sys2_pll_1000m", + }, + { + .name = "audio_pll1_out", + }, + { + .name = "video_pll1_out", + }, + { + .name = "audio_pll2_out", + }, +}; + +static struct clk_parent_data imx8mq_noc_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "sys2_pll_1000m", + }, + { + .name = "sys2_pll_500m", + }, + { + .name = "audio_pll1_out", + }, + { + .name = "video_pll1_out", + }, + { + .name = "audio_pll2_out", + }, +}; + +static struct clk_parent_data imx8mq_noc_apb_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys1_pll_400m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "sys2_pll_333m", + }, + { + .name = "sys2_pll_200m", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "audio_pll1_out", + }, + { + .name = "video_pll1_out", + }, +}; + +static struct clk_parent_data imx8mq_ahb_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys1_pll_133m", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys1_pll_400m", + }, + { + .name = "sys2_pll_125m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "audio_pll1_out", + }, + { + .name = "video_pll1_out", + }, +}; + +static struct clk_parent_data imx8mq_audio_ahb_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys2_pll_500m", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys2_pll_1000m", + }, + { + .name = "sys2_pll_166m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "audio_pll1_out", + }, + { + .name = "video_pll1_out", + }, +}; + +static struct clk_parent_data imx8mq_dsi_ahb_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys2_pll_100m", + }, + { + .name = "sys1_pll_80m", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys2_pll_1000m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "clk_ext3", + }, + { + .name = "audio_pll2_out", + }, +}; + +static struct clk_parent_data imx8mq_dram_alt_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys1_pll_100m", + }, + { + .name = "sys2_pll_500m", + }, + { + .name = "sys2_pll_250m", + }, + { + .name = "sys1_pll_400m", + }, + { + .name = "audio_pll1_out", + }, + { + .name = "sys1_pll_266m", + }, +}; + +static struct clk_parent_data imx8mq_dram_apb_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys2_pll_200m", + }, + { + .name = "sys1_pll_40m", + }, + { + .name = "sys1_pll_160m", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "sys2_pll_250m", + }, + { + .name = "audio_pll2_out", + }, +}; + +static struct clk_parent_data imx8mq_vpu_g1_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "vpu_pll_out", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys2_pll_1000m", + }, + { + .name = "sys1_pll_100m", + }, + { + .name = "sys2_pll_125m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "audio_pll1_out", + }, +}; + +static struct clk_parent_data imx8mq_vpu_g2_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "vpu_pll_out", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys2_pll_1000m", + }, + { + .name = "sys1_pll_100m", + }, + { + .name = "sys2_pll_125m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "audio_pll1_out", + }, +}; + +static struct clk_parent_data imx8mq_disp_dtrc_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "vpu_pll_out", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys2_pll_1000m", + }, + { + .name = "sys1_pll_160m", + }, + { + .name = "sys2_pll_100m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "audio_pll2_out", + }, +}; + +static struct clk_parent_data imx8mq_disp_dc8000_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "vpu_pll_out", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys2_pll_1000m", + }, + { + .name = "sys1_pll_160m", + }, + { + .name = "sys2_pll_100m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "audio_pll2_out", + }, +}; + +static struct clk_parent_data imx8mq_pcie1_ctrl_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys2_pll_250m", + }, + { + .name = "sys2_pll_200m", + }, + { + .name = "sys1_pll_266m", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys2_pll_500m", + }, + { + .name = "sys2_pll_333m", + }, + { + .name = "sys3_pll_out", + }, +}; + +static struct clk_parent_data imx8mq_pcie1_phy_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys2_pll_100m", + }, + { + .name = "sys2_pll_500m", + }, + { + .name = "clk_ext1", + }, + { + .name = "clk_ext2", + }, + { + .name = "clk_ext3", + }, + { + .name = "clk_ext4", + }, +}; + +static struct clk_parent_data imx8mq_pcie1_aux_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys2_pll_200m", + }, + { + .name = "sys2_pll_50m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "sys2_pll_100m", + }, + { + .name = "sys1_pll_80m", + }, + { + .name = "sys1_pll_160m", + }, + { + .name = "sys1_pll_200m", + }, +}; + +static struct clk_parent_data imx8mq_dc_pixel_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "video_pll1_out", + }, + { + .name = "audio_pll2_out", + }, + { + .name = "audio_pll1_out", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys2_pll_1000m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "clk_ext4", + }, +}; + +static struct clk_parent_data imx8mq_lcdif_pixel_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "video_pll1_out", + }, + { + .name = "audio_pll2_out", + }, + { + .name = "audio_pll1_out", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys2_pll_1000m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "clk_ext4", + }, +}; + +static struct clk_parent_data imx8mq_sai1_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "audio_pll1_out", + }, + { + .name = "audio_pll2_out", + }, + { + .name = "video_pll1_out", + }, + { + .name = "sys1_pll_133m", + }, + { + .name = "osc_27m", + }, + { + .name = "clk_ext1", + }, + { + .name = "clk_ext2", + }, +}; + +static struct clk_parent_data imx8mq_sai2_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "audio_pll1_out", + }, + { + .name = "audio_pll2_out", + }, + { + .name = "video_pll1_out", + }, + { + .name = "sys1_pll_133m", + }, + { + .name = "osc_27m", + }, + { + .name = "clk_ext2", + }, + { + .name = "clk_ext3", + }, +}; + +static struct clk_parent_data imx8mq_sai3_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "audio_pll1_out", + }, + { + .name = "audio_pll2_out", + }, + { + .name = "video_pll1_out", + }, + { + .name = "sys1_pll_133m", + }, + { + .name = "osc_27m", + }, + { + .name = "clk_ext3", + }, + { + .name = "clk_ext4", + }, +}; + +static struct clk_parent_data imx8mq_sai4_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "audio_pll1_out", + }, + { + .name = "audio_pll2_out", + }, + { + .name = "video_pll1_out", + }, + { + .name = "sys1_pll_133m", + }, + { + .name = "osc_27m", + }, + { + .name = "clk_ext1", + }, + { + .name = "clk_ext2", + }, +}; + +static struct clk_parent_data imx8mq_sai5_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "audio_pll1_out", + }, + { + .name = "audio_pll2_out", + }, + { + .name = "video_pll1_out", + }, + { + .name = "sys1_pll_133m", + }, + { + .name = "osc_27m", + }, + { + .name = "clk_ext2", + }, + { + .name = "clk_ext3", + }, +}; + +static struct clk_parent_data imx8mq_sai6_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "audio_pll1_out", + }, + { + .name = "audio_pll2_out", + }, + { + .name = "video_pll1_out", + }, + { + .name = "sys1_pll_133m", + }, + { + .name = "osc_27m", + }, + { + .name = "clk_ext3", + }, + { + .name = "clk_ext4", + }, +}; + +static struct clk_parent_data imx8mq_spdif1_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "audio_pll1_out", + }, + { + .name = "audio_pll2_out", + }, + { + .name = "video_pll1_out", + }, + { + .name = "sys1_pll_133m", + }, + { + .name = "osc_27m", + }, + { + .name = "clk_ext2", + }, + { + .name = "clk_ext3", + }, +}; + +static struct clk_parent_data imx8mq_spdif2_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "audio_pll1_out", + }, + { + .name = "audio_pll2_out", + }, + { + .name = "video_pll1_out", + }, + { + .name = "sys1_pll_133m", + }, + { + .name = "osc_27m", + }, + { + .name = "clk_ext3", + }, + { + .name = "clk_ext4", + }, +}; + +static struct clk_parent_data imx8mq_enet_ref_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys2_pll_125m", + }, + { + .name = "sys2_pll_500m", + }, + { + .name = "sys2_pll_100m", + }, + { + .name = "sys1_pll_160m", + }, + { + .name = "audio_pll1_out", + }, + { + .name = "video_pll1_out", + }, + { + .name = "clk_ext4", + }, +}; + +static struct clk_parent_data imx8mq_enet_timer_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys2_pll_100m", + }, + { + .name = "audio_pll1_out", + }, + { + .name = "clk_ext1", + }, + { + .name = "clk_ext2", + }, + { + .name = "clk_ext3", + }, + { + .name = "clk_ext4", + }, + { + .name = "video_pll1_out", + }, +}; + +static struct clk_parent_data imx8mq_enet_phy_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys2_pll_50m", + }, + { + .name = "sys2_pll_125m", + }, + { + .name = "sys2_pll_500m", + }, + { + .name = "audio_pll1_out", + }, + { + .name = "video_pll1_out", + }, + { + .name = "audio_pll2_out", + }, +}; + +static struct clk_parent_data imx8mq_nand_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys2_pll_500m", + }, + { + .name = "audio_pll1_out", + }, + { + .name = "sys1_pll_400m", + }, + { + .name = "audio_pll2_out", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "sys2_pll_250m", + }, + { + .name = "video_pll1_out", + }, +}; + +static struct clk_parent_data imx8mq_qspi_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys1_pll_400m", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys2_pll_500m", + }, + { + .name = "audio_pll2_out", + }, + { + .name = "sys1_pll_266m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "sys1_pll_100m", + }, +}; + +static struct clk_parent_data imx8mq_usdhc1_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys1_pll_400m", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys2_pll_500m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "sys1_pll_266m", + }, + { + .name = "audio_pll2_out", + }, + { + .name = "sys1_pll_100m", + }, +}; + +static struct clk_parent_data imx8mq_usdhc2_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys1_pll_400m", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys2_pll_500m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "sys1_pll_266m", + }, + { + .name = "audio_pll2_out", + }, + { + .name = "sys1_pll_100m", + }, +}; + +static struct clk_parent_data imx8mq_i2c1_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys1_pll_160m", + }, + { + .name = "sys2_pll_50m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "audio_pll1_out", + }, + { + .name = "video_pll1_out", + }, + { + .name = "audio_pll2_out", + }, + { + .name = "sys1_pll_133m", + }, +}; + +static struct clk_parent_data imx8mq_i2c2_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys1_pll_160m", + }, + { + .name = "sys2_pll_50m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "audio_pll1_out", + }, + { + .name = "video_pll1_out", + }, + { + .name = "audio_pll2_out", + }, + { + .name = "sys1_pll_133m", + }, +}; + +static struct clk_parent_data imx8mq_i2c3_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys1_pll_160m", + }, + { + .name = "sys2_pll_50m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "audio_pll1_out", + }, + { + .name = "video_pll1_out", + }, + { + .name = "audio_pll2_out", + }, + { + .name = "sys1_pll_133m", + }, +}; + +static struct clk_parent_data imx8mq_i2c4_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys1_pll_160m", + }, + { + .name = "sys2_pll_50m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "audio_pll1_out", + }, + { + .name = "video_pll1_out", + }, + { + .name = "audio_pll2_out", + }, + { + .name = "sys1_pll_133m", + }, +}; + +static struct clk_parent_data imx8mq_uart1_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys1_pll_80m", + }, + { + .name = "sys2_pll_200m", + }, + { + .name = "sys2_pll_100m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "clk_ext2", + }, + { + .name = "clk_ext4", + }, + { + .name = "audio_pll2_out", + }, +}; + +static struct clk_parent_data imx8mq_uart2_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys1_pll_80m", + }, + { + .name = "sys2_pll_200m", + }, + { + .name = "sys2_pll_100m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "clk_ext2", + }, + { + .name = "clk_ext3", + }, + { + .name = "audio_pll2_out", + }, +}; + +static struct clk_parent_data imx8mq_uart3_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys1_pll_80m", + }, + { + .name = "sys2_pll_200m", + }, + { + .name = "sys2_pll_100m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "clk_ext2", + }, + { + .name = "clk_ext4", + }, + { + .name = "audio_pll2_out", + }, +}; + +static struct clk_parent_data imx8mq_uart4_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys1_pll_80m", + }, + { + .name = "sys2_pll_200m", + }, + { + .name = "sys2_pll_100m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "clk_ext2", + }, + { + .name = "clk_ext3", + }, + { + .name = "audio_pll2_out", + }, +}; + +static struct clk_parent_data imx8mq_usb_core_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys1_pll_100m", + }, + { + .name = "sys1_pll_40m", + }, + { + .name = "sys2_pll_100m", + }, + { + .name = "sys2_pll_200m", + }, + { + .name = "clk_ext2", + }, + { + .name = "clk_ext3", + }, + { + .name = "audio_pll2_out", + }, +}; + +static struct clk_parent_data imx8mq_usb_phy_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys1_pll_100m", + }, + { + .name = "sys1_pll_40m", + }, + { + .name = "sys2_pll_100m", + }, + { + .name = "sys2_pll_200m", + }, + { + .name = "clk_ext2", + }, + { + .name = "clk_ext3", + }, + { + .name = "audio_pll2_out", + }, +}; + +static struct clk_parent_data imx8mq_gic_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys2_pll_200m", + }, + { + .name = "sys1_pll_40m", + }, + { + .name = "sys2_pll_100m", + }, + { + .name = "sys2_pll_200m", + }, + { + .name = "clk_ext2", + }, + { + .name = "clk_ext3", + }, + { + .name = "audio_pll2_out", + }, +}; + +static struct clk_parent_data imx8mq_ecspi1_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys2_pll_200m", + }, + { + .name = "sys1_pll_40m", + }, + { + .name = "sys1_pll_160m", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "sys2_pll_250m", + }, + { + .name = "audio_pll2_out", + }, +}; + +static struct clk_parent_data imx8mq_ecspi2_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys2_pll_200m", + }, + { + .name = "sys1_pll_40m", + }, + { + .name = "sys1_pll_160m", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "sys2_pll_250m", + }, + { + .name = "audio_pll2_out", + }, +}; + +static struct clk_parent_data imx8mq_pwm1_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys2_pll_100m", + }, + { + .name = "sys1_pll_160m", + }, + { + .name = "sys1_pll_40m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "clk_ext1", + }, + { + .name = "sys1_pll_80m", + }, + { + .name = "video_pll1_out", + }, +}; + +static struct clk_parent_data imx8mq_pwm2_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys2_pll_100m", + }, + { + .name = "sys1_pll_160m", + }, + { + .name = "sys1_pll_40m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "clk_ext1", + }, + { + .name = "sys1_pll_80m", + }, + { + .name = "video_pll1_out", + }, +}; + +static struct clk_parent_data imx8mq_pwm3_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys2_pll_100m", + }, + { + .name = "sys1_pll_160m", + }, + { + .name = "sys1_pll_40m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "clk_ext2", + }, + { + .name = "sys1_pll_80m", + }, + { + .name = "video_pll1_out", + }, +}; + +static struct clk_parent_data imx8mq_pwm4_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys2_pll_100m", + }, + { + .name = "sys1_pll_160m", + }, + { + .name = "sys1_pll_40m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "clk_ext2", + }, + { + .name = "sys1_pll_80m", + }, + { + .name = "video_pll1_out", + }, +}; + +static struct clk_parent_data imx8mq_gpt1_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys2_pll_100m", + }, + { + .name = "sys1_pll_400m", + }, + { + .name = "sys1_pll_40m", + }, + { + .name = "sys1_pll_80m", + }, + { + .name = "audio_pll1_out", + }, + { + .name = "clk_ext1", + }, +}; + +static struct clk_parent_data imx8mq_wdog_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys1_pll_133m", + }, + { + .name = "sys1_pll_160m", + }, + { + .name = "vpu_pll_out", + }, + { + .name = "sys2_pll_125m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "sys1_pll_80m", + }, + { + .name = "sys2_pll_166m", + }, +}; + +static struct clk_parent_data imx8mq_wrclk_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys1_pll_40m", + }, + { + .name = "vpu_pll_out", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "sys2_pll_200m", + }, + { + .name = "sys1_pll_266m", + }, + { + .name = "sys2_pll_500m", + }, + { + .name = "sys1_pll_100m", + }, +}; + +static struct clk_parent_data imx8mq_dsi_core_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys1_pll_266m", + }, + { + .name = "sys2_pll_250m", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys2_pll_1000m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "audio_pll2_out", + }, + { + .name = "video_pll1_out", + }, +}; + +static struct clk_parent_data imx8mq_dsi_phy_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys2_pll_125m", + }, + { + .name = "sys2_pll_100m", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys2_pll_1000m", + }, + { + .name = "clk_ext2", + }, + { + .name = "audio_pll2_out", + }, + { + .name = "video_pll1_out", + }, +}; + +static struct clk_parent_data imx8mq_dsi_dbi_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys1_pll_266m", + }, + { + .name = "sys2_pll_100m", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys2_pll_1000m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "audio_pll2_out", + }, + { + .name = "video_pll1_out", + }, +}; + +static struct clk_parent_data imx8mq_dsi_esc_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys2_pll_100m", + }, + { + .name = "sys1_pll_80m", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys2_pll_1000m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "clk_ext3", + }, + { + .name = "audio_pll2_out", + }, +}; + +static struct clk_parent_data imx8mq_csi1_core_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys1_pll_266m", + }, + { + .name = "sys2_pll_250m", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys2_pll_1000m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "audio_pll2_out", + }, + { + .name = "video_pll1_out", + }, +}; + +static struct clk_parent_data imx8mq_csi1_phy_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys2_pll_125m", + }, + { + .name = "sys2_pll_100m", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys2_pll_1000m", + }, + { + .name = "clk_ext2", + }, + { + .name = "audio_pll2_out", + }, + { + .name = "video_pll1_out", + }, +}; + +static struct clk_parent_data imx8mq_csi1_esc_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys2_pll_100m", + }, + { + .name = "sys1_pll_80m", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys2_pll_1000m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "clk_ext3", + }, + { + .name = "audio_pll2_out", + }, +}; + +static struct clk_parent_data imx8mq_csi2_core_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys1_pll_266m", + }, + { + .name = "sys2_pll_250m", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys2_pll_1000m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "audio_pll2_out", + }, + { + .name = "video_pll1_out", + }, +}; + +static struct clk_parent_data imx8mq_csi2_phy_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys2_pll_125m", + }, + { + .name = "sys2_pll_100m", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys2_pll_1000m", + }, + { + .name = "clk_ext2", + }, + { + .name = "audio_pll2_out", + }, + { + .name = "video_pll1_out", + }, +}; + +static struct clk_parent_data imx8mq_csi2_esc_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys2_pll_100m", + }, + { + .name = "sys1_pll_80m", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys2_pll_1000m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "clk_ext3", + }, + { + .name = "audio_pll2_out", + }, +}; + +static struct clk_parent_data imx8mq_pcie2_ctrl_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys2_pll_250m", + }, + { + .name = "sys2_pll_200m", + }, + { + .name = "sys1_pll_266m", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys2_pll_500m", + }, + { + .name = "sys2_pll_333m", + }, + { + .name = "sys3_pll_out", + }, +}; + +static struct clk_parent_data imx8mq_pcie2_phy_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys2_pll_100m", + }, + { + .name = "sys2_pll_500m", + }, + { + .name = "clk_ext1", + }, + { + .name = "clk_ext2", + }, + { + .name = "clk_ext3", + }, + { + .name = "clk_ext4", + }, + { + .name = "sys1_pll_400m", + }, +}; + +static struct clk_parent_data imx8mq_pcie2_aux_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys2_pll_200m", + }, + { + .name = "sys2_pll_50m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "sys2_pll_100m", + }, + { + .name = "sys1_pll_80m", + }, + { + .name = "sys1_pll_160m", + }, + { + .name = "sys1_pll_200m", + }, +}; + +static struct clk_parent_data imx8mq_ecspi3_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys2_pll_200m", + }, + { + .name = "sys1_pll_40m", + }, + { + .name = "sys1_pll_160m", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "sys2_pll_250m", + }, + { + .name = "audio_pll2_out", + }, +}; + +static struct clk_parent_data imx8mq_dram_core_sels[] = { + { + .name = "dram_pll_out", + }, + { + .name = "dram_alt_root", + }, +}; + +static struct clk_parent_data imx8mq_clko1_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys1_pll_800m", + }, + { + .name = "osc_27m", + }, + { + .name = "sys1_pll_200m", + }, + { + .name = "audio_pll2_out", + }, + { + .name = "sys2_pll_500m", + }, + { + .name = "vpu_pll_out", + }, + { + .name = "sys1_pll_80m", + }, +}; + +static struct clk_parent_data imx8mq_clko2_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "sys2_pll_200m", + }, + { + .name = "sys1_pll_400m", + }, + { + .name = "sys2_pll_166m", + }, + { + .name = "sys3_pll_out", + }, + { + .name = "audio_pll1_out", + }, + { + .name = "video_pll1_out", + }, + { + .name = "ckil", + }, +}; + +static struct clk_parent_data pllout_monitor_sels[] = { + { + .name = "osc_25m", + }, + { + .name = "osc_27m", + }, + { + .name = "dummy", + }, + { + .name = "dummy", + }, + { + .name = "ckil", + }, + { + .name = "audio_pll1_out_monitor", + }, + { + .name = "audio_pll2_out_monitor", + }, + { + .name = "video_pll1_out_monitor", + }, + { + .name = "gpu_pll_out_monitor", + }, + { + .name = "vpu_pll_out_monitor", + }, + { + .name = "arm_pll_out_monitor", + }, + { + .name = "sys_pll1_out_monitor", + }, + { + .name = "sys_pll2_out_monitor", + }, + { + .name = "sys_pll3_out_monitor", + }, + { + .name = "dram_pll_out_monitor", + }, + { + .name = "video_pll2_out_monitor", + }, +}; + +static IMX_CLK_SOURCE(dummy, 0); +static IMX_CLK_SOURCE(ckil, 0x8000); +static IMX_CLK_SOURCE(osc_25m, 0x17d7840); +static IMX_CLK_SOURCE(osc_27m, 0x19bfcc0); +static IMX_CLK_SOURCE(clk_ext1, 0x7ed6b40); +static IMX_CLK_SOURCE(clk_ext2, 0x7ed6b40); +static IMX_CLK_SOURCE(clk_ext3, 0x7ed6b40); +static IMX_CLK_SOURCE(clk_ext4, 0x7ed6b40); + +static IMX_CLK_MUX(arm_pll_ref_sel, CCM_ANALOG_BASE, 0x28, 16, 2, pll_ref_sels, ARRAY_SIZE(pll_ref_sels)); +static IMX_CLK_MUX(gpu_pll_ref_sel, CCM_ANALOG_BASE, 0x18, 16, 2, pll_ref_sels, ARRAY_SIZE(pll_ref_sels)); +static IMX_CLK_MUX(vpu_pll_ref_sel, CCM_ANALOG_BASE, 0x20, 16, 2, pll_ref_sels, ARRAY_SIZE(pll_ref_sels)); +static IMX_CLK_MUX(audio_pll1_ref_sel, CCM_ANALOG_BASE, 0x0, 16, 2, pll_ref_sels, ARRAY_SIZE(pll_ref_sels)); +static IMX_CLK_MUX(audio_pll2_ref_sel, CCM_ANALOG_BASE, 0x8, 16, 2, pll_ref_sels, ARRAY_SIZE(pll_ref_sels)); +static IMX_CLK_MUX(video_pll1_ref_sel, CCM_ANALOG_BASE, 0x10, 16, 2, pll_ref_sels, ARRAY_SIZE(pll_ref_sels)); +static IMX_CLK_MUX(sys3_pll1_ref_sel, CCM_ANALOG_BASE, 0x48, 0, 2, pll_ref_sels, ARRAY_SIZE(pll_ref_sels)); +static IMX_CLK_MUX(dram_pll1_ref_sel, CCM_ANALOG_BASE, 0x60, 0, 2, pll_ref_sels, ARRAY_SIZE(pll_ref_sels)); +static IMX_CLK_MUX(video2_pll1_ref_sel, CCM_ANALOG_BASE, 0x54, 0, 2, pll_ref_sels, ARRAY_SIZE(pll_ref_sels)); + +static IMX_CLK_DIV(arm_pll_ref_div, { &arm_pll_ref_sel }, CCM_ANALOG_BASE, 0x28, 5, 6); +static IMX_CLK_DIV(gpu_pll_ref_div, { &gpu_pll_ref_sel }, CCM_ANALOG_BASE, 0x18, 5, 6); +static IMX_CLK_DIV(vpu_pll_ref_div, { &vpu_pll_ref_sel }, CCM_ANALOG_BASE, 0x20, 5, 6); +static IMX_CLK_DIV(audio_pll1_ref_div, { &audio_pll1_ref_sel }, CCM_ANALOG_BASE, 0x0, 5, 6); +static IMX_CLK_DIV(audio_pll2_ref_div, { &audio_pll2_ref_sel }, CCM_ANALOG_BASE, 0x8, 5, 6); +static IMX_CLK_DIV(video_pll1_ref_div, { &video_pll1_ref_sel }, CCM_ANALOG_BASE, 0x10, 5, 6); + +static IMX_CLK_FRAC_PLL(arm_pll, { &arm_pll_ref_div }, CCM_ANALOG_BASE, 0x28); +static IMX_CLK_FRAC_PLL(gpu_pll, { &gpu_pll_ref_div }, CCM_ANALOG_BASE, 0x18); +static IMX_CLK_FRAC_PLL(vpu_pll, { &vpu_pll_ref_div }, CCM_ANALOG_BASE, 0x20); +static IMX_CLK_FRAC_PLL(audio_pll1, { &audio_pll1_ref_div }, CCM_ANALOG_BASE, 0x0); +static IMX_CLK_FRAC_PLL(audio_pll2, { &audio_pll2_ref_div }, CCM_ANALOG_BASE, 0x8); +static IMX_CLK_FRAC_PLL(video_pll1, { &video_pll1_ref_div }, CCM_ANALOG_BASE, 0x10); + +/* PLL bypass out */ +static IMX_CLK_MUX_FLAGS(arm_pll_bypass, CCM_ANALOG_BASE, 0x28, 14, 1, arm_pll_bypass_sels, + ARRAY_SIZE(arm_pll_bypass_sels), CLK_SET_RATE_PARENT); +static IMX_CLK_MUX(gpu_pll_bypass, CCM_ANALOG_BASE, 0x18, 14, 1, gpu_pll_bypass_sels, ARRAY_SIZE(gpu_pll_bypass_sels)); +static IMX_CLK_MUX(vpu_pll_bypass, CCM_ANALOG_BASE, 0x20, 14, 1, vpu_pll_bypass_sels, ARRAY_SIZE(vpu_pll_bypass_sels)); +static IMX_CLK_MUX(audio_pll1_bypass, CCM_ANALOG_BASE, 0x0, 14, 1, audio_pll1_bypass_sels, + ARRAY_SIZE(audio_pll1_bypass_sels)); +static IMX_CLK_MUX(audio_pll2_bypass, CCM_ANALOG_BASE, 0x8, 14, 1, audio_pll2_bypass_sels, + ARRAY_SIZE(audio_pll2_bypass_sels)); +static IMX_CLK_MUX(video_pll1_bypass, CCM_ANALOG_BASE, 0x10, 14, 1, video_pll1_bypass_sels, + ARRAY_SIZE(video_pll1_bypass_sels)); + +/* PLL OUT GATE */ +static IMX_CLK_GATE(arm_pll_out, { &arm_pll_bypass }, CCM_ANALOG_BASE, 0x28, 21); +static IMX_CLK_GATE(gpu_pll_out, { &gpu_pll_bypass }, CCM_ANALOG_BASE, 0x18, 21); +static IMX_CLK_GATE(vpu_pll_out, { &vpu_pll_bypass }, CCM_ANALOG_BASE, 0x20, 21); +static IMX_CLK_GATE(audio_pll1_out, { &audio_pll1_bypass }, CCM_ANALOG_BASE, 0x0, 21); +static IMX_CLK_GATE(audio_pll2_out, { &audio_pll2_bypass }, CCM_ANALOG_BASE, 0x8, 21); +static IMX_CLK_GATE(video_pll1_out, { &video_pll1_bypass }, CCM_ANALOG_BASE, 0x10, 21); + +static IMX_CLK_FIXED(sys1_pll_out, 800000000); +static IMX_CLK_FIXED(sys2_pll_out, 1000000000); +static IMX_CLK_SSCG_PLL(sys3_pll_out, sys3_pll_out_sels, ARRAY_SIZE(sys3_pll_out_sels), 0, 0, 0, CCM_ANALOG_BASE, 0x48, + CLK_IS_CRITICAL); +static IMX_CLK_SSCG_PLL(dram_pll_out, dram_pll_out_sels, ARRAY_SIZE(dram_pll_out_sels), 0, 0, 0, CCM_ANALOG_BASE, 0x60, + CLK_IS_CRITICAL | CLK_GET_RATE_NOCACHE); +static IMX_CLK_SSCG_PLL(video2_pll_out, video2_pll_out_sels, ARRAY_SIZE(video2_pll_out_sels), 0, 0, 0, CCM_ANALOG_BASE, + 0x54, 0); + +/* SYS PLL1 fixed output */ +static IMX_CLK_FIXED_FACTOR(sys1_pll_40m, { &sys1_pll_out }, 1, 20); +static IMX_CLK_FIXED_FACTOR(sys1_pll_80m, { &sys1_pll_out }, 1, 10); +static IMX_CLK_FIXED_FACTOR(sys1_pll_100m, { &sys1_pll_out }, 1, 8); +static IMX_CLK_FIXED_FACTOR(sys1_pll_133m, { &sys1_pll_out }, 1, 6); +static IMX_CLK_FIXED_FACTOR(sys1_pll_160m, { &sys1_pll_out }, 1, 5); +static IMX_CLK_FIXED_FACTOR(sys1_pll_200m, { &sys1_pll_out }, 1, 4); +static IMX_CLK_FIXED_FACTOR(sys1_pll_266m, { &sys1_pll_out }, 1, 3); +static IMX_CLK_FIXED_FACTOR(sys1_pll_400m, { &sys1_pll_out }, 1, 2); +static IMX_CLK_FIXED_FACTOR(sys1_pll_800m, { &sys1_pll_out }, 1, 1); + +/* SYS PLL2 fixed output */ +static IMX_CLK_FIXED_FACTOR(sys2_pll_50m, { &sys2_pll_out }, 1, 20); +static IMX_CLK_FIXED_FACTOR(sys2_pll_100m, { &sys2_pll_out }, 1, 10); +static IMX_CLK_FIXED_FACTOR(sys2_pll_125m, { &sys2_pll_out }, 1, 8); +static IMX_CLK_FIXED_FACTOR(sys2_pll_166m, { &sys2_pll_out }, 1, 6); +static IMX_CLK_FIXED_FACTOR(sys2_pll_200m, { &sys2_pll_out }, 1, 5); +static IMX_CLK_FIXED_FACTOR(sys2_pll_250m, { &sys2_pll_out }, 1, 4); +static IMX_CLK_FIXED_FACTOR(sys2_pll_333m, { &sys2_pll_out }, 1, 3); +static IMX_CLK_FIXED_FACTOR(sys2_pll_500m, { &sys2_pll_out }, 1, 2); +static IMX_CLK_FIXED_FACTOR(sys2_pll_1000m, { &sys2_pll_out }, 1, 1); + +static IMX_CLK_DIV(audio_pll1_out_monitor, { &audio_pll1_bypass }, CCM_ANALOG_BASE, 0x78, 0, 3); +static IMX_CLK_DIV(audio_pll2_out_monitor, { &audio_pll2_bypass }, CCM_ANALOG_BASE, 0x78, 4, 3); +static IMX_CLK_DIV(video_pll1_out_monitor, { &video_pll1_bypass }, CCM_ANALOG_BASE, 0x78, 8, 3); +static IMX_CLK_DIV(gpu_pll_out_monitor, { &gpu_pll_bypass }, CCM_ANALOG_BASE, 0x78, 12, 3); +static IMX_CLK_DIV(vpu_pll_out_monitor, { &vpu_pll_bypass }, CCM_ANALOG_BASE, 0x78, 16, 3); +static IMX_CLK_DIV(arm_pll_out_monitor, { &arm_pll_bypass }, CCM_ANALOG_BASE, 0x78, 20, 3); +static IMX_CLK_DIV(sys_pll1_out_monitor, { &sys1_pll_out }, CCM_ANALOG_BASE, 0x7c, 0, 3); +static IMX_CLK_DIV(sys_pll2_out_monitor, { &sys2_pll_out }, CCM_ANALOG_BASE, 0x7c, 4, 3); +static IMX_CLK_DIV(sys_pll3_out_monitor, { &sys3_pll_out }, CCM_ANALOG_BASE, 0x7c, 8, 3); +static IMX_CLK_DIV(dram_pll_out_monitor, { &dram_pll_out }, CCM_ANALOG_BASE, 0x7c, 12, 3); +static IMX_CLK_DIV(video_pll2_out_monitor, { &video2_pll_out }, CCM_ANALOG_BASE, 0x7c, 16, 3); +static IMX_CLK_MUX(pllout_monitor_sel, CCM_ANALOG_BASE, 0x74, 0, 4, pllout_monitor_sels, + ARRAY_SIZE(pllout_monitor_sels)); +static IMX_CLK_GATE(pllout_monitor_clk2, { &pllout_monitor_sel }, CCM_ANALOG_BASE, 0x74, 4); + +/* CORE */ +static IMX_CLK_COMPOSITE_CORE(arm_a53_div, imx8mq_a53_sels, CCM_BASE, 0x8000); + +static IMX_CLK_COMPOSITE_CORE(arm_m4_core, imx8mq_arm_m4_sels, CCM_BASE, 0x8080); +static IMX_CLK_COMPOSITE_CORE(vpu_core, imx8mq_vpu_sels, CCM_BASE, 0x8100); +static IMX_CLK_COMPOSITE_CORE(gpu_core, imx8mq_gpu_core_sels, CCM_BASE, 0x8180); +static IMX_CLK_COMPOSITE(gpu_shader, imx8mq_gpu_shader_sels, CCM_BASE, 0x8200); + +/* CORE SEL */ +static IMX_CLK_MUX2(arm_a53_core, CCM_BASE, 0x9880, 24, 1, imx8mq_a53_core_sels, ARRAY_SIZE(imx8mq_a53_core_sels)); + +/* BUS */ +static IMX_CLK_COMPOSITE_BUS(main_axi, imx8mq_main_axi_sels, CCM_BASE, 0x8800); +static IMX_CLK_COMPOSITE_BUS(enet_axi, imx8mq_enet_axi_sels, CCM_BASE, 0x8880); +static IMX_CLK_COMPOSITE_BUS(nand_usdhc_bus, imx8mq_nand_usdhc_sels, CCM_BASE, 0x8900); +static IMX_CLK_COMPOSITE_BUS(vpu_bus, imx8mq_vpu_bus_sels, CCM_BASE, 0x8980); +static IMX_CLK_COMPOSITE_BUS(disp_axi, imx8mq_disp_axi_sels, CCM_BASE, 0x8a00); +static IMX_CLK_COMPOSITE_BUS(disp_apb, imx8mq_disp_apb_sels, CCM_BASE, 0x8a80); +static IMX_CLK_COMPOSITE_BUS(disp_rtrm, imx8mq_disp_rtrm_sels, CCM_BASE, 0x8b00); +static IMX_CLK_COMPOSITE_BUS(usb_bus, imx8mq_usb_bus_sels, CCM_BASE, 0x8b80); +static IMX_CLK_COMPOSITE_BUS(gpu_axi, imx8mq_gpu_axi_sels, CCM_BASE, 0x8c00); +static IMX_CLK_COMPOSITE_BUS(gpu_ahb, imx8mq_gpu_ahb_sels, CCM_BASE, 0x8c80); +static IMX_CLK_COMPOSITE_BUS(noc, imx8mq_noc_sels, CCM_BASE, 0x8d00); +static IMX_CLK_COMPOSITE_BUS(noc_apb, imx8mq_noc_apb_sels, CCM_BASE, 0x8d80); + +/* AHB */ +/* AHB clock is used by the AHB bus therefore marked as critical */ +static IMX_CLK_COMPOSITE_BUS(ahb, imx8mq_ahb_sels, CCM_BASE, 0x9000); +static IMX_CLK_COMPOSITE_BUS(audio_ahb, imx8mq_audio_ahb_sels, CCM_BASE, 0x9100); + +/* IPG */ +static IMX_CLK_DIV2(ipg_root, { &ahb }, CCM_BASE, 0x9080, 0, 1); +static IMX_CLK_DIV2(ipg_audio_root, { &audio_ahb }, CCM_BASE, 0x9180, 0, 1); + +/* + * DRAM clocks are manipulated from TF-A outside clock framework. + * The fw_managed helper sets GET_RATE_NOCACHE and clears SET_PARENT_GATE + * as div value should always be read from hardware + */ +static IMX_CLK_MUX2_FLAGS(dram_core_clk, CCM_BASE, 0x9800, 24, 1, imx8mq_dram_core_sels, + ARRAY_SIZE(imx8mq_dram_core_sels), CLK_IS_CRITICAL); +static IMX_CLK_COMPOSITE_FW_MANAGED(dram_alt, imx8mq_dram_alt_sels, CCM_BASE, 0xa000); +static IMX_CLK_COMPOSITE_FW_MANAGED_CRITICAL(dram_apb, imx8mq_dram_apb_sels, CCM_BASE, 0xa080); + +/* IP */ +static IMX_CLK_COMPOSITE(vpu_g1, imx8mq_vpu_g1_sels, CCM_BASE, 0xa100); +static IMX_CLK_COMPOSITE(vpu_g2, imx8mq_vpu_g2_sels, CCM_BASE, 0xa180); +static IMX_CLK_COMPOSITE(disp_dtrc, imx8mq_disp_dtrc_sels, CCM_BASE, 0xa200); +static IMX_CLK_COMPOSITE(disp_dc8000, imx8mq_disp_dc8000_sels, CCM_BASE, 0xa280); +static IMX_CLK_COMPOSITE(pcie1_ctrl, imx8mq_pcie1_ctrl_sels, CCM_BASE, 0xa300); +static IMX_CLK_COMPOSITE(pcie1_phy, imx8mq_pcie1_phy_sels, CCM_BASE, 0xa380); +static IMX_CLK_COMPOSITE(pcie1_aux, imx8mq_pcie1_aux_sels, CCM_BASE, 0xa400); +static IMX_CLK_COMPOSITE(dc_pixel, imx8mq_dc_pixel_sels, CCM_BASE, 0xa480); +static IMX_CLK_COMPOSITE(lcdif_pixel, imx8mq_lcdif_pixel_sels, CCM_BASE, 0xa500); +static IMX_CLK_COMPOSITE(sai1, imx8mq_sai1_sels, CCM_BASE, 0xa580); +static IMX_CLK_COMPOSITE(sai2, imx8mq_sai2_sels, CCM_BASE, 0xa600); +static IMX_CLK_COMPOSITE(sai3, imx8mq_sai3_sels, CCM_BASE, 0xa680); +static IMX_CLK_COMPOSITE(sai4, imx8mq_sai4_sels, CCM_BASE, 0xa700); +static IMX_CLK_COMPOSITE(sai5, imx8mq_sai5_sels, CCM_BASE, 0xa780); +static IMX_CLK_COMPOSITE(sai6, imx8mq_sai6_sels, CCM_BASE, 0xa800); +static IMX_CLK_COMPOSITE(spdif1, imx8mq_spdif1_sels, CCM_BASE, 0xa880); +static IMX_CLK_COMPOSITE(spdif2, imx8mq_spdif2_sels, CCM_BASE, 0xa900); +static IMX_CLK_COMPOSITE(enet_ref, imx8mq_enet_ref_sels, CCM_BASE, 0xa980); +static IMX_CLK_COMPOSITE(enet_timer, imx8mq_enet_timer_sels, CCM_BASE, 0xaa00); +static IMX_CLK_COMPOSITE(enet_phy, imx8mq_enet_phy_sels, CCM_BASE, 0xaa80); +static IMX_CLK_COMPOSITE(nand, imx8mq_nand_sels, CCM_BASE, 0xab00); +static IMX_CLK_COMPOSITE(qspi, imx8mq_qspi_sels, CCM_BASE, 0xab80); +static IMX_CLK_COMPOSITE(usdhc1, imx8mq_usdhc1_sels, CCM_BASE, 0xac00); +static IMX_CLK_COMPOSITE(usdhc2, imx8mq_usdhc2_sels, CCM_BASE, 0xac80); +static IMX_CLK_COMPOSITE(i2c1, imx8mq_i2c1_sels, CCM_BASE, 0xad00); +static IMX_CLK_COMPOSITE(i2c2, imx8mq_i2c2_sels, CCM_BASE, 0xad80); +static IMX_CLK_COMPOSITE(i2c3, imx8mq_i2c3_sels, CCM_BASE, 0xae00); +static IMX_CLK_COMPOSITE(i2c4, imx8mq_i2c4_sels, CCM_BASE, 0xae80); +static IMX_CLK_COMPOSITE(uart1, imx8mq_uart1_sels, CCM_BASE, 0xaf00); +static IMX_CLK_COMPOSITE(uart2, imx8mq_uart2_sels, CCM_BASE, 0xaf80); +static IMX_CLK_COMPOSITE(uart3, imx8mq_uart3_sels, CCM_BASE, 0xb000); +static IMX_CLK_COMPOSITE(uart4, imx8mq_uart4_sels, CCM_BASE, 0xb080); +static IMX_CLK_COMPOSITE(usb_core_ref, imx8mq_usb_core_sels, CCM_BASE, 0xb100); +static IMX_CLK_COMPOSITE(usb_phy_ref, imx8mq_usb_phy_sels, CCM_BASE, 0xb180); +static IMX_CLK_COMPOSITE(gic, imx8mq_gic_sels, CCM_BASE, 0xb200); +static IMX_CLK_COMPOSITE(ecspi1, imx8mq_ecspi1_sels, CCM_BASE, 0xb280); +static IMX_CLK_COMPOSITE(ecspi2, imx8mq_ecspi2_sels, CCM_BASE, 0xb300); +static IMX_CLK_COMPOSITE(pwm1, imx8mq_pwm1_sels, CCM_BASE, 0xb380); +static IMX_CLK_COMPOSITE(pwm2, imx8mq_pwm2_sels, CCM_BASE, 0xb400); +static IMX_CLK_COMPOSITE(pwm3, imx8mq_pwm3_sels, CCM_BASE, 0xb480); +static IMX_CLK_COMPOSITE(pwm4, imx8mq_pwm4_sels, CCM_BASE, 0xb500); +static IMX_CLK_COMPOSITE(gpt1, imx8mq_gpt1_sels, CCM_BASE, 0xb580); +static IMX_CLK_COMPOSITE(wdog, imx8mq_wdog_sels, CCM_BASE, 0xb900); +static IMX_CLK_COMPOSITE(wrclk, imx8mq_wrclk_sels, CCM_BASE, 0xb980); +static IMX_CLK_COMPOSITE(clko1, imx8mq_clko1_sels, CCM_BASE, 0xba00); +static IMX_CLK_COMPOSITE(clko2, imx8mq_clko2_sels, CCM_BASE, 0xba80); +static IMX_CLK_COMPOSITE(dsi_core, imx8mq_dsi_core_sels, CCM_BASE, 0xbb00); +static IMX_CLK_COMPOSITE(dsi_phy_ref, imx8mq_dsi_phy_sels, CCM_BASE, 0xbb80); +static IMX_CLK_COMPOSITE(dsi_dbi, imx8mq_dsi_dbi_sels, CCM_BASE, 0xbc00); +static IMX_CLK_COMPOSITE(dsi_esc, imx8mq_dsi_esc_sels, CCM_BASE, 0xbc80); +static IMX_CLK_COMPOSITE(dsi_ahb, imx8mq_dsi_ahb_sels, CCM_BASE, 0x9200); +static IMX_CLK_DIV2(dsi_ipg_div, { &dsi_ahb }, CCM_BASE, 0x9280, 0, 6); +static IMX_CLK_COMPOSITE(csi1_core, imx8mq_csi1_core_sels, CCM_BASE, 0xbd00); +static IMX_CLK_COMPOSITE(csi1_phy_ref, imx8mq_csi1_phy_sels, CCM_BASE, 0xbd80); +static IMX_CLK_COMPOSITE(csi1_esc, imx8mq_csi1_esc_sels, CCM_BASE, 0xbe00); +static IMX_CLK_COMPOSITE(csi2_core, imx8mq_csi2_core_sels, CCM_BASE, 0xbe80); +static IMX_CLK_COMPOSITE(csi2_phy_ref, imx8mq_csi2_phy_sels, CCM_BASE, 0xbf00); +static IMX_CLK_COMPOSITE(csi2_esc, imx8mq_csi2_esc_sels, CCM_BASE, 0xbf80); +static IMX_CLK_COMPOSITE(pcie2_ctrl, imx8mq_pcie2_ctrl_sels, CCM_BASE, 0xc000); +static IMX_CLK_COMPOSITE(pcie2_phy, imx8mq_pcie2_phy_sels, CCM_BASE, 0xc080); +static IMX_CLK_COMPOSITE(pcie2_aux, imx8mq_pcie2_aux_sels, CCM_BASE, 0xc100); +static IMX_CLK_COMPOSITE(ecspi3, imx8mq_ecspi3_sels, CCM_BASE, 0xc180); + +static IMX_CLK_GATE4(ecspi1_root_clk, { &ecspi1 }, CCM_BASE, 0x4070, 0); +static IMX_CLK_GATE4(ecspi2_root_clk, { &ecspi2 }, CCM_BASE, 0x4080, 0); +static IMX_CLK_GATE4(ecspi3_root_clk, { &ecspi3 }, CCM_BASE, 0x4090, 0); +static IMX_CLK_GATE4(enet1_root_clk, { &enet_axi }, CCM_BASE, 0x40a0, 0); +static IMX_CLK_GATE4(gpio1_root_clk, { &ipg_root }, CCM_BASE, 0x40b0, 0); +static IMX_CLK_GATE4(gpio2_root_clk, { &ipg_root }, CCM_BASE, 0x40c0, 0); +static IMX_CLK_GATE4(gpio3_root_clk, { &ipg_root }, CCM_BASE, 0x40d0, 0); +static IMX_CLK_GATE4(gpio4_root_clk, { &ipg_root }, CCM_BASE, 0x40e0, 0); +static IMX_CLK_GATE4(gpio5_root_clk, { &ipg_root }, CCM_BASE, 0x40f0, 0); +static IMX_CLK_GATE4(gpt1_root_clk, { &gpt1 }, CCM_BASE, 0x4100, 0); +static IMX_CLK_GATE4(i2c1_root_clk, { &i2c1 }, CCM_BASE, 0x4170, 0); +static IMX_CLK_GATE4(i2c2_root_clk, { &i2c2 }, CCM_BASE, 0x4180, 0); +static IMX_CLK_GATE4(i2c3_root_clk, { &i2c3 }, CCM_BASE, 0x4190, 0); +static IMX_CLK_GATE4(i2c4_root_clk, { &i2c4 }, CCM_BASE, 0x41a0, 0); +static IMX_CLK_GATE4(mu_root_clk, { &ipg_root }, CCM_BASE, 0x4210, 0); +static IMX_CLK_GATE4(ocotp_root_clk, { &ipg_root }, CCM_BASE, 0x4220, 0); +static IMX_CLK_GATE4(pcie1_root_clk, { &pcie1_ctrl }, CCM_BASE, 0x4250, 0); +static IMX_CLK_GATE4(pcie2_root_clk, { &pcie2_ctrl }, CCM_BASE, 0x4640, 0); +static IMX_CLK_GATE4(pwm1_root_clk, { &pwm1 }, CCM_BASE, 0x4280, 0); +static IMX_CLK_GATE4(pwm2_root_clk, { &pwm2 }, CCM_BASE, 0x4290, 0); +static IMX_CLK_GATE4(pwm3_root_clk, { &pwm3 }, CCM_BASE, 0x42a0, 0); +static IMX_CLK_GATE4(pwm4_root_clk, { &pwm4 }, CCM_BASE, 0x42b0, 0); +static IMX_CLK_GATE4(qspi_root_clk, { &qspi }, CCM_BASE, 0x42f0, 0); + +static IMX_CLK_GATE2_SHARED2(nand_root_clk, { &nand }, CCM_BASE, 0x4300, 0, &share_count_nand); +static IMX_CLK_GATE2_SHARED2(nand_usdhc_rawnand_clk, { &nand_usdhc_bus }, CCM_BASE, 0x4300, 0, &share_count_nand); +static IMX_CLK_GATE2_SHARED2(sai1_root_clk, { &sai1 }, CCM_BASE, 0x4330, 0, &share_count_sai1); +static IMX_CLK_GATE2_SHARED2(sai1_ipg_clk, { &ipg_audio_root }, CCM_BASE, 0x4330, 0, &share_count_sai1); +static IMX_CLK_GATE2_SHARED2(sai2_root_clk, { &sai2 }, CCM_BASE, 0x4340, 0, &share_count_sai2); +static IMX_CLK_GATE2_SHARED2(sai2_ipg_clk, { &ipg_root }, CCM_BASE, 0x4340, 0, &share_count_sai2); +static IMX_CLK_GATE2_SHARED2(sai3_root_clk, { &sai3 }, CCM_BASE, 0x4350, 0, &share_count_sai3); +static IMX_CLK_GATE2_SHARED2(sai3_ipg_clk, { &ipg_root }, CCM_BASE, 0x4350, 0, &share_count_sai3); +static IMX_CLK_GATE2_SHARED2(sai4_root_clk, { &sai4 }, CCM_BASE, 0x4360, 0, &share_count_sai4); +static IMX_CLK_GATE2_SHARED2(sai4_ipg_clk, { &ipg_audio_root }, CCM_BASE, 0x4360, 0, &share_count_sai4); +static IMX_CLK_GATE2_SHARED2(sai5_root_clk, { &sai5 }, CCM_BASE, 0x4370, 0, &share_count_sai5); +static IMX_CLK_GATE2_SHARED2(sai5_ipg_clk, { &ipg_audio_root }, CCM_BASE, 0x4370, 0, &share_count_sai5); +static IMX_CLK_GATE2_SHARED2(sai6_root_clk, { &sai6 }, CCM_BASE, 0x4380, 0, &share_count_sai6); +static IMX_CLK_GATE2_SHARED2(sai6_ipg_clk, { &ipg_audio_root }, CCM_BASE, 0x4380, 0, &share_count_sai6); +static IMX_CLK_GATE4(uart1_root_clk, { &uart1 }, CCM_BASE, 0x4490, 0); +static IMX_CLK_GATE4(uart2_root_clk, { &uart2 }, CCM_BASE, 0x44a0, 0); +static IMX_CLK_GATE4(uart3_root_clk, { &uart3 }, CCM_BASE, 0x44b0, 0); +static IMX_CLK_GATE4(uart4_root_clk, { &uart4 }, CCM_BASE, 0x44c0, 0); +static IMX_CLK_GATE4(usb1_ctrl_root_clk, { &usb_bus }, CCM_BASE, 0x44d0, 0); +static IMX_CLK_GATE4(usb2_ctrl_root_clk, { &usb_bus }, CCM_BASE, 0x44e0, 0); +static IMX_CLK_GATE4(usb1_phy_root_clk, { &usb_phy_ref }, CCM_BASE, 0x44f0, 0); +static IMX_CLK_GATE4(usb2_phy_root_clk, { &usb_phy_ref }, CCM_BASE, 0x4500, 0); +static IMX_CLK_GATE4(usdhc1_root_clk, { &usdhc1 }, CCM_BASE, 0x4510, 0); +static IMX_CLK_GATE4(usdhc2_root_clk, { &usdhc2 }, CCM_BASE, 0x4520, 0); +static IMX_CLK_GATE4(wdog1_root_clk, { &wdog }, CCM_BASE, 0x4530, 0); +static IMX_CLK_GATE4(wdog2_root_clk, { &wdog }, CCM_BASE, 0x4540, 0); +static IMX_CLK_GATE4(wdog3_root_clk, { &wdog }, CCM_BASE, 0x4550, 0); +static IMX_CLK_GATE2_FLAGS(vpu_g1_root_clk, { &vpu_g1 }, CCM_BASE, 0x4560, 0, + CLK_SET_RATE_PARENT | CLK_OPS_PARENT_ENABLE); +static IMX_CLK_GATE4(gpu_root_clk, { &gpu_core }, CCM_BASE, 0x4570, 0); +static IMX_CLK_GATE2_FLAGS(vpu_g2_root_clk, { &vpu_g2 }, CCM_BASE, 0x45a0, 0, + CLK_SET_RATE_PARENT | CLK_OPS_PARENT_ENABLE); +static IMX_CLK_GATE2_SHARED2(disp_root_clk, { &disp_dc8000 }, CCM_BASE, 0x45d0, 0, &share_count_dcss); +static IMX_CLK_GATE2_SHARED2(disp_axi_root_clk, { &disp_axi }, CCM_BASE, 0x45d0, 0, &share_count_dcss); +static IMX_CLK_GATE2_SHARED2(disp_apb_root_clk, { &disp_apb }, CCM_BASE, 0x45d0, 0, &share_count_dcss); +static IMX_CLK_GATE2_SHARED2(disp_rtrm_root_clk, { &disp_rtrm }, CCM_BASE, 0x45d0, 0, &share_count_dcss); +static IMX_CLK_GATE4(tmu_root_clk, { &ipg_root }, CCM_BASE, 0x4620, 0); +static IMX_CLK_GATE2_FLAGS(vpu_dec_root_clk, { &vpu_bus }, CCM_BASE, 0x4630, 0, + CLK_SET_RATE_PARENT | CLK_OPS_PARENT_ENABLE); +static IMX_CLK_GATE4(csi1_root_clk, { &csi1_core }, CCM_BASE, 0x4650, 0); +static IMX_CLK_GATE4(csi2_root_clk, { &csi2_core }, CCM_BASE, 0x4660, 0); +static IMX_CLK_GATE4(sdma1_clk, { &ipg_root }, CCM_BASE, 0x43a0, 0); +static IMX_CLK_GATE4(sdma2_clk, { &ipg_audio_root }, CCM_BASE, 0x43b0, 0); + +static IMX_CLK_FIXED_FACTOR(gpt_3m, { &osc_25m }, 1, 8); +static IMX_CLK_FIXED_FACTOR(dram_alt_root, { &dram_alt }, 1, 4); + +static IMX_CLK_CPU(arm_clk, { &arm_a53_div }, &arm_a53_core, &arm_a53_div, &arm_pll_out, &arm_a53_core); + +static struct clk *imx8mq_clks[IMX8MQ_CLK_END] = { + [IMX8MQ_CLK_DUMMY] = &dummy, + [IMX8MQ_CLK_32K] = &ckil, + [IMX8MQ_CLK_25M] = &osc_25m, + [IMX8MQ_CLK_27M] = &osc_27m, + [IMX8MQ_CLK_EXT1] = &clk_ext1, + [IMX8MQ_CLK_EXT2] = &clk_ext2, + [IMX8MQ_CLK_EXT3] = &clk_ext3, + [IMX8MQ_CLK_EXT4] = &clk_ext4, + [IMX8MQ_ARM_PLL_REF_SEL] = &arm_pll_ref_sel, + [IMX8MQ_GPU_PLL_REF_SEL] = &gpu_pll_ref_sel, + [IMX8MQ_VPU_PLL_REF_SEL] = &vpu_pll_ref_sel, + [IMX8MQ_AUDIO_PLL1_REF_SEL] = &audio_pll1_ref_sel, + [IMX8MQ_AUDIO_PLL2_REF_SEL] = &audio_pll2_ref_sel, + [IMX8MQ_VIDEO_PLL1_REF_SEL] = &video_pll1_ref_sel, + [IMX8MQ_SYS3_PLL1_REF_SEL] = &sys3_pll1_ref_sel, + [IMX8MQ_DRAM_PLL1_REF_SEL] = &dram_pll1_ref_sel, + [IMX8MQ_VIDEO2_PLL1_REF_SEL] = &video2_pll1_ref_sel, + [IMX8MQ_ARM_PLL_REF_DIV] = &arm_pll_ref_div, + [IMX8MQ_GPU_PLL_REF_DIV] = &gpu_pll_ref_div, + [IMX8MQ_VPU_PLL_REF_DIV] = &vpu_pll_ref_div, + [IMX8MQ_AUDIO_PLL1_REF_DIV] = &audio_pll1_ref_div, + [IMX8MQ_AUDIO_PLL2_REF_DIV] = &audio_pll2_ref_div, + [IMX8MQ_VIDEO_PLL1_REF_DIV] = &video_pll1_ref_div, + [IMX8MQ_ARM_PLL] = &arm_pll, + [IMX8MQ_GPU_PLL] = &gpu_pll, + [IMX8MQ_VPU_PLL] = &vpu_pll, + [IMX8MQ_AUDIO_PLL1] = &audio_pll1, + [IMX8MQ_AUDIO_PLL2] = &audio_pll2, + [IMX8MQ_VIDEO_PLL1] = &video_pll1, + [IMX8MQ_ARM_PLL_BYPASS] = &arm_pll_bypass, + [IMX8MQ_GPU_PLL_BYPASS] = &gpu_pll_bypass, + [IMX8MQ_VPU_PLL_BYPASS] = &vpu_pll_bypass, + [IMX8MQ_AUDIO_PLL1_BYPASS] = &audio_pll1_bypass, + [IMX8MQ_AUDIO_PLL2_BYPASS] = &audio_pll2_bypass, + [IMX8MQ_VIDEO_PLL1_BYPASS] = &video_pll1_bypass, + [IMX8MQ_ARM_PLL_OUT] = &arm_pll_out, + [IMX8MQ_GPU_PLL_OUT] = &gpu_pll_out, + [IMX8MQ_VPU_PLL_OUT] = &vpu_pll_out, + [IMX8MQ_AUDIO_PLL1_OUT] = &audio_pll1_out, + [IMX8MQ_AUDIO_PLL2_OUT] = &audio_pll2_out, + [IMX8MQ_VIDEO_PLL1_OUT] = &video_pll1_out, + [IMX8MQ_SYS1_PLL_OUT] = &sys1_pll_out, + [IMX8MQ_SYS2_PLL_OUT] = &sys2_pll_out, + [IMX8MQ_SYS3_PLL_OUT] = &sys3_pll_out, + [IMX8MQ_DRAM_PLL_OUT] = &dram_pll_out, + [IMX8MQ_VIDEO2_PLL_OUT] = &video2_pll_out, + [IMX8MQ_SYS1_PLL_40M] = &sys1_pll_40m, + [IMX8MQ_SYS1_PLL_80M] = &sys1_pll_80m, + [IMX8MQ_SYS1_PLL_100M] = &sys1_pll_100m, + [IMX8MQ_SYS1_PLL_133M] = &sys1_pll_133m, + [IMX8MQ_SYS1_PLL_160M] = &sys1_pll_160m, + [IMX8MQ_SYS1_PLL_200M] = &sys1_pll_200m, + [IMX8MQ_SYS1_PLL_266M] = &sys1_pll_266m, + [IMX8MQ_SYS1_PLL_400M] = &sys1_pll_400m, + [IMX8MQ_SYS1_PLL_800M] = &sys1_pll_800m, + [IMX8MQ_SYS2_PLL_50M] = &sys2_pll_50m, + [IMX8MQ_SYS2_PLL_100M] = &sys2_pll_100m, + [IMX8MQ_SYS2_PLL_125M] = &sys2_pll_125m, + [IMX8MQ_SYS2_PLL_166M] = &sys2_pll_166m, + [IMX8MQ_SYS2_PLL_200M] = &sys2_pll_200m, + [IMX8MQ_SYS2_PLL_250M] = &sys2_pll_250m, + [IMX8MQ_SYS2_PLL_333M] = &sys2_pll_333m, + [IMX8MQ_SYS2_PLL_500M] = &sys2_pll_500m, + [IMX8MQ_SYS2_PLL_1000M] = &sys2_pll_1000m, + [IMX8MQ_CLK_MON_AUDIO_PLL1_DIV] = &audio_pll1_out_monitor, + [IMX8MQ_CLK_MON_AUDIO_PLL2_DIV] = &audio_pll2_out_monitor, + [IMX8MQ_CLK_MON_VIDEO_PLL1_DIV] = &video_pll1_out_monitor, + [IMX8MQ_CLK_MON_GPU_PLL_DIV] = &gpu_pll_out_monitor, + [IMX8MQ_CLK_MON_VPU_PLL_DIV] = &vpu_pll_out_monitor, + [IMX8MQ_CLK_MON_ARM_PLL_DIV] = &arm_pll_out_monitor, + [IMX8MQ_CLK_MON_SYS_PLL1_DIV] = &sys_pll1_out_monitor, + [IMX8MQ_CLK_MON_SYS_PLL2_DIV] = &sys_pll2_out_monitor, + [IMX8MQ_CLK_MON_SYS_PLL3_DIV] = &sys_pll3_out_monitor, + [IMX8MQ_CLK_MON_DRAM_PLL_DIV] = &dram_pll_out_monitor, + [IMX8MQ_CLK_MON_VIDEO_PLL2_DIV] = &video_pll2_out_monitor, + [IMX8MQ_CLK_MON_SEL] = &pllout_monitor_sel, + [IMX8MQ_CLK_MON_CLK2_OUT] = &pllout_monitor_clk2, + [IMX8MQ_CLK_A53_DIV] = &arm_a53_div, + [IMX8MQ_CLK_A53_CG] = &arm_a53_div, + [IMX8MQ_CLK_A53_SRC] = &arm_a53_div, + [IMX8MQ_CLK_M4_CORE] = &arm_m4_core, + [IMX8MQ_CLK_VPU_CORE] = &vpu_core, + [IMX8MQ_CLK_GPU_CORE] = &gpu_core, + [IMX8MQ_CLK_GPU_SHADER] = &gpu_shader, + [IMX8MQ_CLK_M4_SRC] = &arm_m4_core, + [IMX8MQ_CLK_M4_CG] = &arm_m4_core, + [IMX8MQ_CLK_M4_DIV] = &arm_m4_core, + [IMX8MQ_CLK_VPU_SRC] = &vpu_core, + [IMX8MQ_CLK_VPU_CG] = &vpu_core, + [IMX8MQ_CLK_VPU_DIV] = &vpu_core, + [IMX8MQ_CLK_GPU_CORE_SRC] = &gpu_core, + [IMX8MQ_CLK_GPU_CORE_CG] = &gpu_core, + [IMX8MQ_CLK_GPU_CORE_DIV] = &gpu_core, + [IMX8MQ_CLK_GPU_SHADER_SRC] = &gpu_shader, + [IMX8MQ_CLK_GPU_SHADER_CG] = &gpu_shader, + [IMX8MQ_CLK_GPU_SHADER_DIV] = &gpu_shader, + [IMX8MQ_CLK_A53_CORE] = &arm_a53_core, + [IMX8MQ_CLK_MAIN_AXI] = &main_axi, + [IMX8MQ_CLK_ENET_AXI] = &enet_axi, + [IMX8MQ_CLK_NAND_USDHC_BUS] = &nand_usdhc_bus, + [IMX8MQ_CLK_VPU_BUS] = &vpu_bus, + [IMX8MQ_CLK_DISP_AXI] = &disp_axi, + [IMX8MQ_CLK_DISP_APB] = &disp_apb, + [IMX8MQ_CLK_DISP_RTRM] = &disp_rtrm, + [IMX8MQ_CLK_USB_BUS] = &usb_bus, + [IMX8MQ_CLK_GPU_AXI] = &gpu_axi, + [IMX8MQ_CLK_GPU_AHB] = &gpu_ahb, + [IMX8MQ_CLK_NOC] = &noc, + [IMX8MQ_CLK_NOC_APB] = &noc_apb, + [IMX8MQ_CLK_AHB] = &ahb, + [IMX8MQ_CLK_AUDIO_AHB] = &audio_ahb, + [IMX8MQ_CLK_IPG_ROOT] = &ipg_root, + [IMX8MQ_CLK_IPG_AUDIO_ROOT] = &ipg_audio_root, + [IMX8MQ_CLK_DRAM_CORE] = &dram_core_clk, + [IMX8MQ_CLK_DRAM_ALT] = &dram_alt, + [IMX8MQ_CLK_DRAM_APB] = &dram_apb, + [IMX8MQ_CLK_VPU_G1] = &vpu_g1, + [IMX8MQ_CLK_VPU_G2] = &vpu_g2, + [IMX8MQ_CLK_DISP_DTRC] = &disp_dtrc, + [IMX8MQ_CLK_DISP_DC8000] = &disp_dc8000, + [IMX8MQ_CLK_PCIE1_CTRL] = &pcie1_ctrl, + [IMX8MQ_CLK_PCIE1_PHY] = &pcie1_phy, + [IMX8MQ_CLK_PCIE1_AUX] = &pcie1_aux, + [IMX8MQ_CLK_DC_PIXEL] = &dc_pixel, + [IMX8MQ_CLK_LCDIF_PIXEL] = &lcdif_pixel, + [IMX8MQ_CLK_SAI1] = &sai1, + [IMX8MQ_CLK_SAI2] = &sai2, + [IMX8MQ_CLK_SAI3] = &sai3, + [IMX8MQ_CLK_SAI4] = &sai4, + [IMX8MQ_CLK_SAI5] = &sai5, + [IMX8MQ_CLK_SAI6] = &sai6, + [IMX8MQ_CLK_SPDIF1] = &spdif1, + [IMX8MQ_CLK_SPDIF2] = &spdif2, + [IMX8MQ_CLK_ENET_REF] = &enet_ref, + [IMX8MQ_CLK_ENET_TIMER] = &enet_timer, + [IMX8MQ_CLK_ENET_PHY_REF] = &enet_phy, + [IMX8MQ_CLK_NAND] = &nand, + [IMX8MQ_CLK_QSPI] = &qspi, + [IMX8MQ_CLK_USDHC1] = &usdhc1, + [IMX8MQ_CLK_USDHC2] = &usdhc2, + [IMX8MQ_CLK_I2C1] = &i2c1, + [IMX8MQ_CLK_I2C2] = &i2c2, + [IMX8MQ_CLK_I2C3] = &i2c3, + [IMX8MQ_CLK_I2C4] = &i2c4, + [IMX8MQ_CLK_UART1] = &uart1, + [IMX8MQ_CLK_UART2] = &uart2, + [IMX8MQ_CLK_UART3] = &uart3, + [IMX8MQ_CLK_UART4] = &uart4, + [IMX8MQ_CLK_USB_CORE_REF] = &usb_core_ref, + [IMX8MQ_CLK_USB_PHY_REF] = &usb_phy_ref, + [IMX8MQ_CLK_GIC] = &gic, + [IMX8MQ_CLK_ECSPI1] = &ecspi1, + [IMX8MQ_CLK_ECSPI2] = &ecspi2, + [IMX8MQ_CLK_PWM1] = &pwm1, + [IMX8MQ_CLK_PWM2] = &pwm2, + [IMX8MQ_CLK_PWM3] = &pwm3, + [IMX8MQ_CLK_PWM4] = &pwm4, + [IMX8MQ_CLK_GPT1] = &gpt1, + [IMX8MQ_CLK_WDOG] = &wdog, + [IMX8MQ_CLK_WRCLK] = &wrclk, + [IMX8MQ_CLK_CLKO1] = &clko1, + [IMX8MQ_CLK_CLKO2] = &clko2, + [IMX8MQ_CLK_DSI_CORE] = &dsi_core, + [IMX8MQ_CLK_DSI_PHY_REF] = &dsi_phy_ref, + [IMX8MQ_CLK_DSI_DBI] = &dsi_dbi, + [IMX8MQ_CLK_DSI_ESC] = &dsi_esc, + [IMX8MQ_CLK_DSI_AHB] = &dsi_ahb, + [IMX8MQ_CLK_DSI_IPG_DIV] = &dsi_ipg_div, + [IMX8MQ_CLK_CSI1_CORE] = &csi1_core, + [IMX8MQ_CLK_CSI1_PHY_REF] = &csi1_phy_ref, + [IMX8MQ_CLK_CSI1_ESC] = &csi1_esc, + [IMX8MQ_CLK_CSI2_CORE] = &csi2_core, + [IMX8MQ_CLK_CSI2_PHY_REF] = &csi2_phy_ref, + [IMX8MQ_CLK_CSI2_ESC] = &csi2_esc, + [IMX8MQ_CLK_PCIE2_CTRL] = &pcie2_ctrl, + [IMX8MQ_CLK_PCIE2_PHY] = &pcie2_phy, + [IMX8MQ_CLK_PCIE2_AUX] = &pcie2_aux, + [IMX8MQ_CLK_ECSPI3] = &ecspi3, + [IMX8MQ_CLK_ECSPI1_ROOT] = &ecspi1_root_clk, + [IMX8MQ_CLK_ECSPI2_ROOT] = &ecspi2_root_clk, + [IMX8MQ_CLK_ECSPI3_ROOT] = &ecspi3_root_clk, + [IMX8MQ_CLK_ENET1_ROOT] = &enet1_root_clk, + [IMX8MQ_CLK_ARM] = &arm_clk, + [IMX8MQ_CLK_GPIO1_ROOT] = &gpio1_root_clk, + [IMX8MQ_CLK_GPIO2_ROOT] = &gpio2_root_clk, + [IMX8MQ_CLK_GPIO3_ROOT] = &gpio3_root_clk, + [IMX8MQ_CLK_GPIO4_ROOT] = &gpio4_root_clk, + [IMX8MQ_CLK_GPIO5_ROOT] = &gpio5_root_clk, + [IMX8MQ_CLK_GPT1_ROOT] = &gpt1_root_clk, + [IMX8MQ_CLK_I2C1_ROOT] = &i2c1_root_clk, + [IMX8MQ_CLK_I2C2_ROOT] = &i2c2_root_clk, + [IMX8MQ_CLK_I2C3_ROOT] = &i2c3_root_clk, + [IMX8MQ_CLK_I2C4_ROOT] = &i2c4_root_clk, + [IMX8MQ_CLK_MU_ROOT] = &mu_root_clk, + [IMX8MQ_CLK_OCOTP_ROOT] = &ocotp_root_clk, + [IMX8MQ_CLK_PCIE1_ROOT] = &pcie1_root_clk, + [IMX8MQ_CLK_PCIE2_ROOT] = &pcie2_root_clk, + [IMX8MQ_CLK_PWM1_ROOT] = &pwm1_root_clk, + [IMX8MQ_CLK_PWM2_ROOT] = &pwm2_root_clk, + [IMX8MQ_CLK_PWM3_ROOT] = &pwm3_root_clk, + [IMX8MQ_CLK_PWM4_ROOT] = &pwm4_root_clk, + [IMX8MQ_CLK_QSPI_ROOT] = &qspi_root_clk, + [IMX8MQ_CLK_RAWNAND_ROOT] = &nand_root_clk, + [IMX8MQ_CLK_NAND_USDHC_BUS_RAWNAND_CLK] = &nand_usdhc_rawnand_clk, + [IMX8MQ_CLK_SAI1_ROOT] = &sai1_root_clk, + [IMX8MQ_CLK_SAI1_IPG] = &sai1_ipg_clk, + [IMX8MQ_CLK_SAI2_ROOT] = &sai2_root_clk, + [IMX8MQ_CLK_SAI2_IPG] = &sai2_ipg_clk, + [IMX8MQ_CLK_SAI3_ROOT] = &sai3_root_clk, + [IMX8MQ_CLK_SAI3_IPG] = &sai3_ipg_clk, + [IMX8MQ_CLK_SAI4_ROOT] = &sai4_root_clk, + [IMX8MQ_CLK_SAI4_IPG] = &sai4_ipg_clk, + [IMX8MQ_CLK_SAI5_ROOT] = &sai5_root_clk, + [IMX8MQ_CLK_SAI5_IPG] = &sai5_ipg_clk, + [IMX8MQ_CLK_SAI6_ROOT] = &sai6_root_clk, + [IMX8MQ_CLK_SAI6_IPG] = &sai6_ipg_clk, + [IMX8MQ_CLK_UART1_ROOT] = &uart1_root_clk, + [IMX8MQ_CLK_UART2_ROOT] = &uart2_root_clk, + [IMX8MQ_CLK_UART3_ROOT] = &uart3_root_clk, + [IMX8MQ_CLK_UART4_ROOT] = &uart4_root_clk, + [IMX8MQ_CLK_USB1_CTRL_ROOT] = &usb1_ctrl_root_clk, + [IMX8MQ_CLK_USB2_CTRL_ROOT] = &usb2_ctrl_root_clk, + [IMX8MQ_CLK_USB1_PHY_ROOT] = &usb1_phy_root_clk, + [IMX8MQ_CLK_USB2_PHY_ROOT] = &usb2_phy_root_clk, + [IMX8MQ_CLK_USDHC1_ROOT] = &usdhc1_root_clk, + [IMX8MQ_CLK_USDHC2_ROOT] = &usdhc2_root_clk, + [IMX8MQ_CLK_WDOG1_ROOT] = &wdog1_root_clk, + [IMX8MQ_CLK_WDOG2_ROOT] = &wdog2_root_clk, + [IMX8MQ_CLK_WDOG3_ROOT] = &wdog3_root_clk, + [IMX8MQ_CLK_VPU_G1_ROOT] = &vpu_g1_root_clk, + [IMX8MQ_CLK_GPU_ROOT] = &gpu_root_clk, + [IMX8MQ_CLK_VPU_G2_ROOT] = &vpu_g2_root_clk, + [IMX8MQ_CLK_DISP_ROOT] = &disp_root_clk, + [IMX8MQ_CLK_DISP_AXI_ROOT] = &disp_axi_root_clk, + [IMX8MQ_CLK_DISP_APB_ROOT] = &disp_apb_root_clk, + [IMX8MQ_CLK_DISP_RTRM_ROOT] = &disp_rtrm_root_clk, + [IMX8MQ_CLK_TMU_ROOT] = &tmu_root_clk, + [IMX8MQ_CLK_VPU_DEC_ROOT] = &vpu_dec_root_clk, + [IMX8MQ_CLK_CSI1_ROOT] = &csi1_root_clk, + [IMX8MQ_CLK_CSI2_ROOT] = &csi2_root_clk, + [IMX8MQ_CLK_SDMA1_ROOT] = &sdma1_clk, + [IMX8MQ_CLK_SDMA2_ROOT] = &sdma2_clk, + [IMX8MQ_GPT_3M_CLK] = &gpt_3m, + [IMX8MQ_CLK_DRAM_ALT_ROOT] = &dram_alt_root, +}; + +int clk_msr_stat(struct clk *clk_list[]) +{ +#ifdef DEBUG_DRIVER + int i; + uint64_t rate = 0; + int err; + + LOG_DRIVER("-------Expected clock rates------\n"); + for (i = 0; i < NUM_CLK_LIST; i++) { + if (clk_list[i]) { + err = clk_get_rate(clk_list[i], &rate); + if (err) { + LOG_DRIVER_ERR("Failed to get rate of %s: -%u\n", clk_list[i]->hw.init->name, err); + return err; + } + LOG_DRIVER("[%4d][%10luHz] %s\n", i, rate, clk_list[i]->hw.init->name); + } + } + LOG_DRIVER("-----------------------------\n"); +#endif + + return 0; +} + +struct clk **get_clk_list(void) +{ + return imx8mq_clks; +} + +void clk_probe(struct clk *clk_list[]) +{ + for (int i = 0; i < NUM_CLK_LIST; i++) { + if (!clk_list[i]) { + continue; + } + + struct clk_init_data *init_data = clk_list[i]->hw.init; + + if (clk_list[i]->base == CCM_BASE) { + clk_list[i]->base = ccm_base; + } else if (clk_list[i]->base == CCM_ANALOG_BASE) { + clk_list[i]->base = ccm_analog_base; + } + + if (clk_list[i] && init_data->ops->init) { + init_data->ops->init(clk_list[i]); + } + } +} diff --git a/drivers/clk/imx/clk_driver.mk b/drivers/clk/imx/clk_driver.mk new file mode 100644 index 000000000..09807a5e6 --- /dev/null +++ b/drivers/clk/imx/clk_driver.mk @@ -0,0 +1,37 @@ +# +# Copyright 2024, UNSW +# +# SPDX-License-Identifier: BSD-2-Clause + +CLK_DRIVER_DIR := $(dir $(lastword $(MAKEFILE_LIST))) +CLK_CONFIG_HEADER := $(BUILD_DIR)/clk_config.h +CLK_DRIVER_CONF_INC := $(SDDF)/include/sddf/clk +CLK_DRIVER_INC := $(CLK_DRIVER_DIR)/include +CLK_DRIVER_COMMON_DIR := $(SDDF)/drivers/clk/ + +CLK_DRIVER_BOARD_SOURCES := clk-imx.c clk-imx8mq.c +CLK_DRIVER_COMMON_SOURCES := clk.c clk-operations.c +CLK_DRIVER_SOURCES := $(addprefix $(CLK_DRIVER_DIR), $(CLK_DRIVER_BOARD_SOURCES)) \ + $(addprefix $(CLK_DRIVER_COMMON_DIR), $(CLK_DRIVER_COMMON_SOURCES)) +CLK_DRIVER_OBJS = $(patsubst $(CLK_DRIVER_COMMON_DIR)%.c, clk_driver/%.o, $(CLK_DRIVER_SOURCES)) + +$(CLK_DRIVER_OBJS): CFLAGS += -I${CLK_DRIVER_INC} \ + -I${CLK_DRIVER_CONF_INC} \ + -I${BUILD_DIR} \ + -I${UART_DRIVER_DIR}/include \ + -I${CLK_DRIVER_COMMON_DIR} +clk_driver/%.o: $(CLK_DRIVER_COMMON_DIR)/%.c $(CLK_CONFIG_HEADER) + mkdir -p $(dir $@) + $(CC) -c $(CFLAGS) -o $@ $< + +clk_driver.elf: $(CLK_DRIVER_OBJS) libsddf_util_debug.a + $(LD) $(LDFLAGS) $^ $(LIBS) -o $@ + +$(CLK_CONFIG_HEADER): $(DTS) $(CLK_DRIVER_COMMON_DIR)/create_clk_config.py + $(PYTHON) $(CLK_DRIVER_COMMON_DIR)/create_clk_config.py $(DTS) $(BUILD_DIR) + +clean:: + rm -f clk_driver.o + +clobber:: + rm -rf clk_driver.elf diff --git a/drivers/clk/imx/include/clk-imx.h b/drivers/clk/imx/include/clk-imx.h new file mode 100644 index 000000000..a3386e61f --- /dev/null +++ b/drivers/clk/imx/include/clk-imx.h @@ -0,0 +1,397 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2010-2011 Canonical Ltd + * Copyright (C) 2011-2012 Mike Turquette, Linaro Ltd + * + * Gated clock implementation + * Source: https://github.com/torvalds/linux/blob/ + * cfaaa7d010d1fc58f9717fcc8591201e741d2d49/drivers/clk/imx/clk-gate2.c + */ + +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2018 NXP. + * + * Source: https://github.com/torvalds/linux/blob/ + * cfaaa7d010d1fc58f9717fcc8591201e741d2d49/drivers/clk/imx/clk-frac-pll.c + */ + +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Copyright 2018 NXP. + * + * Source: https://github.com/torvalds/linux/blob/ + * cfaaa7d010d1fc58f9717fcc8591201e741d2d49/drivers/clk/imx/clk-sscg-pll.c + */ + +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2018 NXP + * + * Source: https://github.com/torvalds/linux/blob/ + * cfaaa7d010d1fc58f9717fcc8591201e741d2d49/drivers/clk/imx/clk-composite-8m.c + */ +#pragma once + +#include + +/* These two magic numbers are used to define static clock + * structures at compile time, and will be replaced with + * real base addresses in clk_probe(). + */ +#define CCM_BASE 0x1234 +#define CCM_ANALOG_BASE 0x5678 + +#define PCG_PREDIV_SHIFT 16 +#define PCG_PREDIV_WIDTH 3 +#define PCG_PREDIV_MAX 8 + +#define PCG_DIV_SHIFT 0 +#define PCG_CORE_DIV_WIDTH 3 +#define PCG_DIV_WIDTH 6 +#define PCG_DIV_MAX 64 + +#define PCG_PCS_SHIFT 24 +#define PCG_PCS_MASK 0x7 + +#define PCG_CGC_SHIFT 28 + +struct clk_gate2 { + uint8_t bit_idx; + uint8_t cgr_val; + uint8_t cgr_mask; + uint8_t flags; + uint32_t *share_count; +}; + +struct clk_frac_pll_data { + uint32_t offset; +}; + +struct clk_sscg_pll_data { + /* struct clk_sscg_pll_setup setup; */ + uint32_t offset; + uint8_t parent; + uint8_t bypass1; + uint8_t bypass2; +}; + +/** + * Refer to section 5.1.5.4.1 Core clock slice in datasheet. + */ +struct clk_core_slice_data { + /* Common */ + uint32_t offset; + /* Mux */ + uint8_t mux_shift; + uint8_t mux_mask; + /* Gate */ + uint8_t gate_shift; + /* Divider */ + uint8_t div_shift; + uint8_t div_width; +}; + +/** + * Refer to section 5.1.5.4.2 Bus clock slice in datasheet. + */ +struct clk_bus_slice_data { + /* Common */ + uint32_t offset; + /* Mux */ + uint8_t mux_shift; + uint8_t mux_mask; + /* Gate */ + uint8_t gate_shift; + /* Prev Divider */ + uint8_t prevdiv_shift; + uint8_t prevdiv_width; + /* Post Divider */ + uint8_t postdiv_shift; + uint8_t postdiv_width; +}; + +/** + * Refer to section 5.1.5.4.3 Peripheral clock slice in datasheet. + */ +struct clk_common_slice_data { + /* Common */ + uint32_t offset; + /* Mux */ + uint8_t mux_shift; + uint8_t mux_mask; + /* Gate */ + uint8_t gate_shift; + /* Prev Divider */ + uint8_t prevdiv_shift; + uint8_t prevdiv_width; + /* Post Divider */ + uint8_t postdiv_shift; + uint8_t postdiv_width; +}; + +/** + * A virutal clock for CPU frequency adjusting + * */ +struct clk_cpu_data { + struct clk *mux; + struct clk *step; + struct clk *pll; + struct clk *div; +}; + +extern const struct clk_ops clk_gate2_ops; +extern const struct clk_ops clk_frac_pll_ops; +extern const struct clk_ops clk_sscg_pll_ops; +extern const struct clk_ops clk_core_slice_ops; +extern const struct clk_ops clk_bus_slice_ops; +extern const struct clk_ops clk_common_slice_ops; +extern const struct clk_ops clk_cpu_ops; + +struct clk **get_clk_list(void); + +#define IMX_CLK_SOURCE(_name, _rate) \ +struct clk _name = { \ + .data = &(struct clk_source_data){ \ + .rate = (_rate), \ + }, \ + .hw.init = &(struct clk_init_data){ \ + .name = #_name, \ + .ops = &clk_source_ops, \ + } \ +} + +/* need to see difference between fixed clk and source clk */ +#define IMX_CLK_FIXED(_name, _rate) \ +IMX_CLK_SOURCE(_name, _rate) + +#define IMX_CLK_MUX_FLAGS(_name, _base, _offset, _shift, _width, _parent_data, _num_parents, _init_flags) \ +struct clk _name = { \ + .base = (_base), \ + .data = &(struct clk_mux_data) { \ + .offset = (_offset), \ + .mask = MASK(_width), \ + .shift = (_shift), \ + }, \ + .hw.init = &(struct clk_init_data) { \ + .name = #_name, \ + .ops = &clk_mux_ops, \ + .parent_data = (_parent_data), \ + .num_parents = (_num_parents), \ + .flags = (_init_flags), \ + }, \ +} + +#define IMX_CLK_MUX(_name, _base, _offset, _shift, _width, _parent_data, _num_parents) \ +IMX_CLK_MUX_FLAGS(_name, _base, _offset, _shift, _width, \ + _parent_data, _num_parents, 0) + +#define IMX_CLK_MUX2(_name, _base, _offset, _shift, _width, _parent_data, _num_parents) \ +IMX_CLK_MUX_FLAGS(_name, _base, _offset, _shift, _width, \ + _parent_data, _num_parents, CLK_OPS_PARENT_ENABLE) + +#define IMX_CLK_MUX2_FLAGS(_name, _base, _offset, _shift, _width, _parent_data, _num_parents, _flags) \ +IMX_CLK_MUX_FLAGS(_name, _base, _offset, _shift, _width, \ + _parent_data, _num_parents, _flags | CLK_OPS_PARENT_ENABLE) + +#define IMX_CLK_DIV_FLAGS(_name, _parent_clks, _base, _offset, _shift, _width, _flags) \ +struct clk _name = { \ + .base = (_base), \ + .data = &(struct clk_div_data) { \ + .offset = (_offset), \ + .shift = (_shift), \ + .width = (_width), \ + }, \ + .hw.init = &(struct clk_init_data) { \ + .name = #_name, \ + .ops = &clk_divider_ops, \ + .parent_clks = (const struct clk *[]) _parent_clks, \ + .num_parents = 1, \ + .flags = 0, \ + }, \ +} + +#define IMX_CLK_DIV(_name, _parent_clks, _base, _offset, _shift, _width) \ +IMX_CLK_DIV_FLAGS(_name, _parent_clks, _base, _offset, _shift, _width, 0) + +#define IMX_CLK_DIV2(_name, _parent_clks, _base, _offset, _shift, _width) \ +IMX_CLK_DIV_FLAGS(_name, _parent_clks, _base, _offset, _shift, \ + _width, CLK_SET_RATE_PARENT | CLK_OPS_PARENT_ENABLE) + +#define IMX_CLK_FRAC_PLL(_name, _parent_clks, _base, _offset) \ +struct clk _name = { \ + .base = (_base), \ + .data = &(struct clk_frac_pll_data) { \ + .offset = (_offset), \ + }, \ + .hw.init = &(struct clk_init_data) { \ + .name = #_name, \ + .ops = &clk_frac_pll_ops, \ + .parent_clks = (const struct clk *[]) _parent_clks, \ + .num_parents = 1, \ + }, \ +} + +#define IMX_CLK_GATE1(_name, _parent_clks, _base, _offset, _shift) \ +CLK_GATE(_name, _offset, _shift, 0, _parent_clks, 1, 0) + +#define IMX_CLK_GATE(_name, _parent_clks, _base, _offset, _shift) \ +struct clk _name = { \ + .base = (_base), \ + .data = &(struct clk_gate_data) { \ + .offset = (_offset), \ + .bit_idx = (_shift), \ + }, \ + .hw.init = &(struct clk_init_data) { \ + .name = #_name, \ + .ops = &clk_gate_ops, \ + .parent_clks = (const struct clk *[]) _parent_clks, \ + .num_parents = 1, \ + }, \ +} + +#define IMX_CLK_GATE2_FLAGS(_name, _parent_clks, _base, _offset, _shift, _flags) \ +struct clk _name = { \ + .base = (_base), \ + .data = &(struct clk_gate_data) { \ + .offset = (_offset), \ + .bit_idx = (_shift), \ + }, \ + .hw.init = &(struct clk_init_data) { \ + .name = #_name, \ + .ops = &clk_gate2_ops, \ + .parent_clks = (const struct clk *[]) _parent_clks, \ + .num_parents = 1, \ + }, \ +} + +#define IMX_CLK_GATE2_SHARED2(_name, _parent_clks, _base, _offset, _shift, _shared_count) \ +IMX_CLK_GATE2_FLAGS(_name, _parent_clks, _base, _offset, _shift, 0) + +#define IMX_CLK_GATE4(_name, _parent_clks, _base, _offset, _shift) \ +IMX_CLK_GATE2_FLAGS(_name, _parent_clks, _base, _offset, _shift, 0) + +#define IMX_CLK_FIXED_FACTOR(_name, _parent_clks, _mult, _div) \ +CLK_FIXED_FACTOR(_name, _mult, _div, 0, _parent_clks, 1, CLK_SET_RATE_PARENT) + +#define IMX_CLK_SSCG_PLL(_name, _parent_data, _num_parents, _parent, _bypass1, _bypass2, _base, _offset, _flags) \ +struct clk _name = { \ + .base = (_base), \ + .data = &(struct clk_sscg_pll_data) { \ + .offset = (_offset), \ + .parent = (_parent), \ + .bypass1 = (_bypass1), \ + .bypass2 = (_bypass2), \ + }, \ + .hw.init = &(struct clk_init_data) { \ + .name = #_name, \ + .ops = &clk_sscg_pll_ops, \ + .parent_data = (_parent_data), \ + .num_parents = 1, \ + .flags = _flags, \ + }, \ +} + +#define IMX_CLK_COMPOSITE_CORE(_name, _parent_data, _base, _offset) \ +struct clk _name = { \ + .base = (_base), \ + .data = &(struct clk_core_slice_data) { \ + .offset = (_offset), \ + .mux_shift = PCG_PCS_SHIFT, \ + .mux_mask = PCG_PCS_MASK, \ + .div_shift = PCG_DIV_SHIFT, \ + .div_width = PCG_CORE_DIV_WIDTH, \ + .gate_shift = PCG_CGC_SHIFT, \ + }, \ + .hw.init = &(struct clk_init_data) { \ + .name = #_name, \ + .ops = &clk_core_slice_ops, \ + .parent_data = (_parent_data), \ + .num_parents = ARRAY_SIZE(_parent_data), \ + .flags = (CLK_DIVIDER_ROUND_CLOSEST | \ + CLK_SET_RATE_NO_REPARENT | \ + CLK_OPS_PARENT_ENABLE) \ + }, \ +} + +#define IMX_CLK_COMPOSITE_BUS(_name, _parent_data, _base, _offset) \ +struct clk _name = { \ + .base = (_base), \ + .data = &(struct clk_bus_slice_data) { \ + .offset = (_offset), \ + .mux_shift = PCG_PCS_SHIFT, \ + .mux_mask = PCG_PCS_MASK, \ + .prevdiv_shift = PCG_PREDIV_SHIFT, \ + .prevdiv_width = PCG_PREDIV_WIDTH, \ + .postdiv_shift = PCG_DIV_SHIFT, \ + .postdiv_width = PCG_DIV_WIDTH, \ + .gate_shift = PCG_CGC_SHIFT, \ + }, \ + .hw.init = &(struct clk_init_data) { \ + .name = #_name, \ + .ops = &clk_bus_slice_ops, \ + .parent_data = (_parent_data), \ + .num_parents = ARRAY_SIZE(_parent_data), \ + .flags = (CLK_DIVIDER_ROUND_CLOSEST | \ + CLK_SET_RATE_NO_REPARENT | \ + CLK_OPS_PARENT_ENABLE) \ + }, \ +} + +#define IMX_CLK_COMPOSITE_FLAGS(_name, _parent_data, _base, _offset, _flags) \ +struct clk _name = { \ + .base = (_base), \ + .data = &(struct clk_common_slice_data) { \ + .offset = (_offset), \ + .mux_shift = PCG_PCS_SHIFT, \ + .mux_mask = PCG_PCS_MASK, \ + .prevdiv_shift = PCG_PREDIV_SHIFT, \ + .prevdiv_width = PCG_PREDIV_WIDTH, \ + .postdiv_shift = PCG_DIV_SHIFT, \ + .postdiv_width = PCG_DIV_WIDTH, \ + .gate_shift = PCG_CGC_SHIFT, \ + }, \ + .hw.init = &(struct clk_init_data) { \ + .name = #_name, \ + .ops = &clk_common_slice_ops, \ + .parent_data = (_parent_data), \ + .num_parents = ARRAY_SIZE(_parent_data), \ + .flags = CLK_DIVIDER_ROUND_CLOSEST | \ + CLK_SET_RATE_NO_REPARENT | \ + CLK_OPS_PARENT_ENABLE | \ + (_flags), \ + }, \ +} + +#define IMX_CLK_COMPOSITE(_name, _parent_data, _base, _offset) \ +IMX_CLK_COMPOSITE_FLAGS(_name, _parent_data, _base, _offset, \ + CLK_OPS_PARENT_ENABLE) + +#define IMX_CLK_COMPOSITE_FW_MANAGED(_name, _parent_data, _base, _offset) \ +IMX_CLK_COMPOSITE_FLAGS(_name, _parent_data, _base, _offset, \ + (CLK_SET_RATE_NO_REPARENT | CLK_OPS_PARENT_ENABLE \ + | CLK_GET_RATE_NOCACHE)) + +#define IMX_CLK_COMPOSITE_FW_MANAGED_CRITICAL(_name, _parent_data, _base, _offset) \ +IMX_CLK_COMPOSITE_FLAGS(_name, _parent_data, _base, _offset, \ + (CLK_SET_RATE_NO_REPARENT | \ + CLK_OPS_PARENT_ENABLE | \ + CLK_GET_RATE_NOCACHE | CLK_IS_CRITICAL)) + +#define IMX_CLK_CPU(_name, _parent_clks, _mux, _step, _pll, _div) \ +struct clk _name = { \ + .base = 0, \ + .data = &(struct clk_cpu_data) { \ + .mux = _mux, \ + .step = _step, \ + .pll = _pll, \ + .div = _div, \ + }, \ + .hw.init = &(struct clk_init_data) { \ + .name = #_name, \ + .ops = &clk_cpu_ops, \ + .parent_clks = (const struct clk *[]) _parent_clks, \ + .num_parents = 1, \ + .flags = 0, \ + }, \ +} diff --git a/drivers/clk/imx/include/clk-imx8mq.h b/drivers/clk/imx/include/clk-imx8mq.h new file mode 100644 index 000000000..4140ce3cb --- /dev/null +++ b/drivers/clk/imx/include/clk-imx8mq.h @@ -0,0 +1,11 @@ +/* + * Copyright 2024, UNSW + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +#define NUM_CLK_LIST IMX8MQ_CLK_END diff --git a/drivers/clk/meson/clk-measure.c b/drivers/clk/meson/clk-measure.c new file mode 100644 index 000000000..3869944ed --- /dev/null +++ b/drivers/clk/meson/clk-measure.c @@ -0,0 +1,230 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * + * This program is derived from: + * https://github.com/hardkernel/u-boot/blob/odroidg12-v2015.01/arch/arm/cpu/armv8/g12a/clock.c + * + * Copyright (C) 2015 Amlogic, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include +#include +#include +#include +#include + +#define TIMER_CH 1 + +#define MSR_CLK_BASE 0x03300000 +#define MSR_CLK_DUTY 0x00 +#define MSR_CLK_REG0 0x01 +#define MSR_CLK_REG1 0x02 +#define MSR_CLK_REG2 0x03 + +#define DIV_ROUND_CLOSEST_ULL(x, divisor)( \ +{ \ + typeof(divisor) __d = divisor; \ + unsigned long long _tmp = (x) + (__d) / 2; \ + do_div(_tmp, __d); \ + _tmp; \ +} \ +) + +static const char *const sm1_table[] = { + [0] = "am_ring_osc_clk_out_ee[0]", + [1] = "am_ring_osc_clk_out_ee[1]", + [2] = "am_ring_osc_clk_out_ee[2]", + [3] = "am_ring_osc_clk_out_ee[3]", + [4] = "gp0_pll_clk", + [5] = "gp1_pll_clk", + [6] = "cts_enci_clk", + [7] = "clk81", + [8] = "cts_encp_clk", + [9] = "cts_encl_clk", + [10] = "cts_vdac_clk", + [11] = "mac_eth_tx_clk", + [12] = "hifi_pll_clk", + [13] = "mod_tcon_clko", + [14] = "cts_FEC_CLK_0", + [15] = "cts_FEC_CLK_1", + [16] = "cts_FEC_CLK_2", + [17] = "sys_pll_div16", + [18] = "sys_cpu_clk_div16", + [19] = "lcd_an_clk_ph2", + [20] = "rtc_osc_clk_out", + [21] = "lcd_an_clk_ph3", + [22] = "mac_eth_phy_ref_clk", + [23] = "mpll_clk_50m", + [24] = "cts_eth_clk125Mhz", + [25] = "cts_eth_clk_rmii", + [26] = "sc_clk_int", + [27] = "co_clkin_to_mac", + [28] = "cts_sar_adc_clk", + [29] = "pcie_clk_inp", + [30] = "pcie_clk_inn", + [31] = "mpll_clk_test_out", + [32] = "cts_vdec_clk", + [33] = "1'b0", + [34] = "eth_mppll_50m_ckout", + [35] = "cts_mali_clk", + [36] = "cts_hdmi_tx_pixel_clk", + [37] = "cts_cdac_clk_c", + [38] = "cts_vdin_meas_clk", + [39] = "cts_bt656_clk0", + [40] = "arm_ring_osc_clk_out[4]", + [41] = "mac_eth_rx_clk_rmii", + [42] = "mp0_clk_out", + [43] = "fclk_div5", + [44] = "cts_pwm_B_clk", + [45] = "cts_pwm_A_clk", + [46] = "cts_vpu_clk", + [47] = "ddr_dpll_pt_clk", + [48] = "mp1_clk_out", + [49] = "mp2_clk_out", + [50] = "mp3_clk_out", + [51] = "cts_sd_emmc_clk_C", + [52] = "cts_sd_emmc_clk_B", + [53] = "cts_sd_emmc_clk_A", + [54] = "cts_vpu_clkc", + [55] = "vid_pll_div_clk_out", + [56] = "cts_wave420l_aclk", + [57] = "cts_wave420l_cclk", + [58] = "cts_wave420l_bclk", + [59] = "cts_hcodec_clk", + [60] = "arm_ring_osc_clk_out[5]", + [61] = "gpio_clk_msr", + [62] = "cts_hevcb_clk", + [63] = "cts_dsi_meas_clk", + [64] = "cts_spicc_1_clk", + [65] = "cts_spicc_0_clk", + [66] = "cts_vid_lock_clk", + [67] = "cts_dsi_phy_clk", + [68] = "cts_hdcp22_esmclk", + [69] = "cts_hdcp22_skpclk", + [70] = "cts_pwm_F_clk", + [71] = "cts_pwm_E_clk", + [72] = "cts_pwm_D_clk", + [73] = "cts_pwm_C_clk", + [74] = "arm_ring_osc_clk_out[6]", + [75] = "cts_hevcf_clk", + [76] = "arm_ring_osc_clk_out[7]", + [77] = "rng_ring_osc_clk[0]", + [78] = "rng_ring_osc_clk[1]", + [79] = "rng_ring_osc_clk[2]", + [80] = "rng_ring_osc_clk[3]", + [81] = "cts_vapbclk", + [82] = "cts_ge2d_clk", + [83] = "co_rx_clk", + [84] = "co_tx_clk", + [85] = "arm_ring_osc_clk_out[8]", + [86] = "arm_ring_osc_clk_out[9]", + [87] = "mipi_csi_phy_clk", + [88] = "csi2_adapt_clk", + [89] = "HDMI_CLK_TODIG", + [90] = "cts_hdmitx_sys_clk", + [91] = "nna_core_clk", + [92] = "nna_axi_clk", + [93] = "vad_clk", + [94] = "eth_phy_rxclk", + [95] = "eth_phy_plltxclk", + [96] = "cts_vpu_clkb", + [97] = "cts_vpu_clkb_tmp", + [98] = "cts_ts_clk", + [99] = "arm_ring_osc_clk_out[10]", + [100] = "arm_ring_osc_clk_out[11]", + [101] = "arm_ring_osc_clk_out[12]", + [102] = "arm_ring_osc_clk_out[13]", + [103] = "arm_ring_osc_clk_out[14]", + [104] = "arm_ring_osc_clk_out[15]", + [105] = "arm_ring_osc_clk_out[16]", + [106] = "ephy_test_clk", + [107] = "au_dac_clk_g128x", + [108] = "c_alocker_in_clk", + [109] = "c_alocker_out_clk", + [110] = "audio_tdmout_c_sclk", + [111] = "audio_tdmout_b_sclk", + [112] = "audio_tdmout_a_sclk", + [113] = "audio_tdmin_lb_sclk", + [114] = "audio_tdmin_c_sclk", + [115] = "audio_tdmin_b_sclk", + [116] = "audio_tdmin_a_sclk", + [117] = "audio_resampleA_clk", + [118] = "audio_pdm_sysclk", + [119] = "audio_spdifout_b_mst_clk", + [120] = "audio_spdifout_mst_clk", + [121] = "audio_spdifin_mst_clk", + [122] = "mod_audio_pdm_dclk_o", + [123] = "audio_resampled_clk", + [124] = "earcx_pll_(dmac)_clk", + [125] = "earcrx_pll_test_clk", + [126] = "csi_phy0_clk_out", + [127] = "clk_csi2_data", +}; + +unsigned long clk_msr(unsigned long clk_mux) +{ + volatile uint32_t *mclk_reg0 = ((void *)MSR_CLK_BASE + (MSR_CLK_REG0 << 2)); + volatile uint32_t *mclk_reg2 = ((void *)MSR_CLK_BASE + (MSR_CLK_REG2 << 2)); + uint32_t duration = 640; + uint32_t regval = 0; + + /* Disable continuous measurement */ + /* Disable interrupts */ + *mclk_reg0 = regval; + + regval |= duration; /* 64uS is enough for measure the frequence? */ + *mclk_reg0 = regval; + + regval |= (clk_mux << 20); /* Select MUX */ + *mclk_reg0 = regval; + + regval |= (1 << 19); /* enable the clock */ + regval |= (1 << 16); /* enable measuring */ + *mclk_reg0 = regval; + + regval = *mclk_reg0; + + uint64_t start_time = sddf_timer_time_now(TIMER_CH); + uint64_t now_time = start_time; + + /* TODO: Check the busy bit */ + /* if (regval & (1 << 31)) */ + /* sddf_dprintf("CLK | ERR: The clock measure logic is busy\n"); */ + + /* Wait for the measurement to be done */ + while (true) { + now_time = sddf_timer_time_now(TIMER_CH); + + if ((now_time - start_time) > 1000000) { + break; + } + /* TODO: Could be optimised via timeouts */ + /* if (sleep_us) */ + /* udelay(sleep_us); */ + } + + regval &= ~(1 << 16); /* disable measuring */ + *mclk_reg0 = regval; + + uint32_t msr_val = *mclk_reg2; + + return DIV_ROUND_CLOSEST_ULL((msr_val & ((1 << 19) - 1)) * 1000000ULL, duration); +} + +const char *const *get_msr_clk_list(void) +{ + return sm1_table; +} diff --git a/drivers/clk/meson/clk-meson.c b/drivers/clk/meson/clk-meson.c new file mode 100644 index 000000000..45399a334 --- /dev/null +++ b/drivers/clk/meson/clk-meson.c @@ -0,0 +1,471 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +/* + * Operations for MPLL clocks are derived from: + * https://github.com/torvalds/linux/blob/befe87380e21f0d37633273e1068c9318f8135ff/drivers/clk/meson/clk-mpll.c + * + * Copyright (c) 2016 AmLogic, Inc. + * Author: Michael Turquette + */ + +// SPDX-License-Identifier: GPL-2.0-only +/* + * Operations for PLL clocks are derived from: + * https://github.com/torvalds/linux/blob/befe87380e21f0d37633273e1068c9318f8135ff/drivers/clk/meson/clk-pll.c + * + * Copyright (c) 2015 Endless Mobile, Inc. + * Author: Carlo Caione + * + * Copyright (c) 2018 Baylibre, SAS. + * Author: Jerome Brunet + */ + +// SPDX-License-Identifier: GPL-2.0-only +/* + * Operations for gate clocks are derived from: + * https://github.com/torvalds/linux/blob/befe87380e21f0d37633273e1068c9318f8135ff/drivers/clk/meson/clk-regmap.c + * + * Copyright (c) 2018 BayLibre, SAS. + * Author: Jerome Brunet + */ + +// SPDX-License-Identifier: GPL-2.0-only +/* + * Operations for fixed factor clocks are derived from: + * https://github.com/torvalds/linux/blob/befe87380e21f0d37633273e1068c9318f8135ff/drivers/clk/clk-fixed-factor.c + * + * Copyright (C) 2011 Sascha Hauer, Pengutronix + */ + +// SPDX-License-Identifier: GPL-2.0-only +/* + * Operations for divider clocks are derived from: + * https://github.com/torvalds/linux/blob/befe87380e21f0d37633273e1068c9318f8135ff/drivers/clk/clk-divider.c + * + * Copyright (C) 2011 Sascha Hauer, Pengutronix + * Copyright (C) 2011 Richard Zhao, Linaro + * Copyright (C) 2011-2012 Mike Turquette, Linaro Ltd + * + * Adjustable divider clock implementation + */ + +// SPDX-License-Identifier: GPL-2.0-only +/* + * Operations for multiplexer clocks are derived from: + * https://github.com/torvalds/linux/blob/befe87380e21f0d37633273e1068c9318f8135ff/drivers/clk/clk-mux.c + * + * Copyright (C) 2011 Sascha Hauer, Pengutronix + * Copyright (C) 2011 Richard Zhao, Linaro + * Copyright (C) 2011-2012 Mike Turquette, Linaro Ltd + * + * Simple multiplexer clock implementation + */ + +// SPDX-License-Identifier: GPL-2.0-only +/* + * Operations for meson_vid_pll_div are derived from: + * https://github.com/torvalds/linux/blob/7ec462100ef9142344ddbf86f2c3008b97acddbe/drivers/clk/meson/vid-pll-div.c + * https://github.com/torvalds/linux/blob/7ec462100ef9142344ddbf86f2c3008b97acddbe/drivers/clk/meson/vclk.c + * + * Copyright (c) 2018 BayLibre, SAS. + * Author: Neil Armstrong + */ + +#include +#include +#include +#include + +static inline uint32_t meson_parm_read(uint64_t base, struct parm parm) +{ + return regmap_read_bits(base, parm.reg_off, parm.shift, MASK(parm.width)); +} + +int regmap_multi_reg_write(uint64_t base, const struct reg_sequence *regs, int num_regs) +{ + int i; + for (i = 0; i < num_regs; i++) { + reg_write(base, regs[i].reg, regs[i].def); + if (regs[i].delay_us) { + delay_us(regs[i].delay_us); + } + } + return 0; +} + +static void meson_clk_pll_init(struct clk *clk) +{ + struct meson_clk_pll_data *data = (struct meson_clk_pll_data *)(clk->data); + if ((data->flags & CLK_MESON_PLL_NOINIT_ENABLED) && clk->hw.init->ops->is_enabled(clk)) + return; + + if (data->init_count) { + /* Set the reset bit */ + regmap_update_bits(clk->base, data->rst.reg_off, data->rst.shift, MASK(data->rst.width), 1); + + regmap_multi_reg_write(clk->base, data->init_regs, data->init_count); + + /* Clear the reset bit */ + regmap_update_bits(clk->base, data->rst.reg_off, data->rst.shift, MASK(data->rst.width), 0); + } +} + +static unsigned long meson_clk_pll_recalc_rate(const struct clk *clk, unsigned long parent_rate) +{ + struct meson_clk_pll_data *data = (struct meson_clk_pll_data *)(clk->data); + uint32_t n, m, frac; + + n = regmap_read_bits(clk->base, data->n.reg_off, data->n.shift, MASK(data->n.width)); + if (n == 0) + return 0; + + m = regmap_read_bits(clk->base, data->m.reg_off, data->m.shift, MASK(data->m.width)); + + frac = data->frac.width ? regmap_read_bits(clk->base, data->frac.reg_off, data->frac.shift, MASK(data->frac.width)) + : 0; + + uint64_t rate = (uint64_t)parent_rate * m; + + if (frac) { + uint64_t frac_rate = (uint64_t)parent_rate * frac; + rate += DIV_ROUND_UP_ULL(frac_rate, (1 << data->frac.width)); + } + + return DIV_ROUND_UP_ULL(rate, n); +} + +static int meson_clk_pll_is_enabled(struct clk *clk) +{ + struct meson_clk_pll_data *data = (struct meson_clk_pll_data *)(clk->data); + + if (data->rst.width && meson_parm_read(clk->base, data->rst)) { + return 0; + } + + if (!meson_parm_read(clk->base, data->en) || !meson_parm_read(clk->base, data->l)) { + return 0; + } + + return 1; +} + +static int meson_clk_pll_enable(struct clk *clk) +{ + struct meson_clk_pll_data *data = (struct meson_clk_pll_data *)(clk->data); + + if (meson_clk_pll_is_enabled(clk)) + return 0; + + regmap_update_bits(clk->base, data->rst.reg_off, data->rst.shift, MASK(data->rst.width), 1); + regmap_update_bits(clk->base, data->en.reg_off, data->en.shift, MASK(data->en.width), 1); + regmap_update_bits(clk->base, data->rst.reg_off, data->rst.shift, MASK(data->rst.width), 1); + + regmap_update_bits(clk->base, data->current_en.reg_off, data->current_en.shift, MASK(data->current_en.width), 1); + return 0; +} + +static int meson_clk_pll_disable(struct clk *clk) +{ + struct meson_clk_pll_data *data = (struct meson_clk_pll_data *)(clk->data); + + regmap_update_bits(clk->base, data->rst.reg_off, data->rst.shift, MASK(data->rst.width), 1); + regmap_update_bits(clk->base, data->en.reg_off, data->en.shift, MASK(data->en.width), 0); + return 0; +} + +const struct clk_ops meson_clk_pll_ops = { + .init = meson_clk_pll_init, + .recalc_rate = meson_clk_pll_recalc_rate, + /* .determine_rate = meson_clk_pll_determine_rate, */ + /* .set_rate = meson_clk_pll_set_rate, */ + .is_enabled = meson_clk_pll_is_enabled, + .enable = meson_clk_pll_enable, + .disable = meson_clk_pll_disable, +}; + +const struct clk_ops meson_clk_pll_ro_ops = { + .recalc_rate = meson_clk_pll_recalc_rate, + .is_enabled = meson_clk_pll_is_enabled, +}; + +#define SDM_DEN 16384 +#define N2_MIN 4 +#define N2_MAX 511 + +static uint64_t mpll_recalc_rate(const struct clk *clk, uint64_t prate) +{ + struct meson_clk_mpll_data *data = (struct meson_clk_mpll_data *)(clk->data); + uint32_t sdm, n2; + + sdm = regmap_read_bits(clk->base, data->sdm.reg_off, data->sdm.shift, MASK(data->sdm.width)); + n2 = regmap_read_bits(clk->base, data->n2.reg_off, data->n2.shift, MASK(data->n2.width)); + + uint32_t divisor = (SDM_DEN * n2) + sdm; + if (n2 < N2_MIN) + return -1; + + return DIV_ROUND_UP_ULL((uint64_t)prate * SDM_DEN, divisor); +} + +static int mpll_set_rate(const struct clk *clk, uint64_t rate, uint64_t parent_rate) +{ + struct meson_clk_mpll_data *data = (struct meson_clk_mpll_data *)(clk->data); + uint64_t div = parent_rate; + uint64_t frac = do_div(div, rate); + uint32_t sdm, n2; + + frac *= SDM_DEN; + + if (data->flags & CLK_MESON_MPLL_ROUND_CLOSEST) + sdm = DIV_ROUND_CLOSEST_ULL(frac, rate); + else + sdm = DIV_ROUND_UP_ULL(frac, rate); + + if (sdm == SDM_DEN) { + sdm = 0; + div += 1; + } + + if (div < N2_MIN) { + n2 = N2_MIN; + sdm = 0; + } else if (div > N2_MAX) { + n2 = N2_MAX; + sdm = SDM_DEN - 1; + } else { + n2 = div; + } + + regmap_update_bits(clk->base, data->sdm.reg_off, data->sdm.shift, MASK(data->sdm.width), sdm); + regmap_update_bits(clk->base, data->n2.reg_off, data->n2.shift, MASK(data->n2.width), n2); + + return 0; +} + +static void mpll_init(struct clk *clk) +{ + struct meson_clk_mpll_data *data = (struct meson_clk_mpll_data *)(clk->data); + if (data->init_count) { + regmap_multi_reg_write(clk->base, data->init_regs, data->init_count); + } + + /* Enable the fractional part */ + regmap_update_bits(clk->base, data->sdm_en.reg_off, data->sdm_en.shift, MASK(data->sdm_en.width), 1); + + /* Set spread spectrum if possible */ + uint32_t ss = data->flags & CLK_MESON_MPLL_SPREAD_SPECTRUM ? 1 : 0; + regmap_update_bits(clk->base, data->ssen.reg_off, data->ssen.shift, MASK(data->ssen.width), ss); +} + +const struct clk_ops meson_clk_mpll_ops = { + .recalc_rate = mpll_recalc_rate, + /* .determine_rate = mpll_determine_rate, */ + .set_rate = mpll_set_rate, + .init = mpll_init, +}; + +static int meson_clk_pcie_pll_enable(struct clk *clk) +{ + struct meson_clk_pll_data *data = (struct meson_clk_pll_data *)(clk->data); + int retries = 10; + int delay = 5000; + + do { + meson_clk_pll_init(clk); + do { + if (meson_parm_read(clk->base, data->l)) { + return 0; + } + delay_us(20); + } while (--delay); + } while (--retries); + + return -1; +} + +const struct clk_ops meson_clk_pcie_pll_ops = { + .init = meson_clk_pll_init, + .recalc_rate = meson_clk_pll_recalc_rate, + /* .determine_rate = meson_clk_pll_determine_rate, */ + .is_enabled = meson_clk_pll_is_enabled, + .enable = meson_clk_pcie_pll_enable, + .disable = meson_clk_pll_disable, +}; + +struct vid_pll_div { + unsigned int shift_val; + unsigned int shift_sel; + unsigned int divider; + unsigned int multiplier; +}; + +#define VID_PLL_DIV(_val, _sel, _ft, _fb) \ + { \ + .shift_val = (_val), \ + .shift_sel = (_sel), \ + .divider = (_ft), \ + .multiplier = (_fb), \ + } + +static const struct vid_pll_div vid_pll_div_table[] = { + VID_PLL_DIV(0x0aaa, 0, 2, 1), /* 2/1 => /2 */ + VID_PLL_DIV(0x5294, 2, 5, 2), /* 5/2 => /2.5 */ + VID_PLL_DIV(0x0db6, 0, 3, 1), /* 3/1 => /3 */ + VID_PLL_DIV(0x36cc, 1, 7, 2), /* 7/2 => /3.5 */ + VID_PLL_DIV(0x6666, 2, 15, 4), /* 15/4 => /3.75 */ + VID_PLL_DIV(0x0ccc, 0, 4, 1), /* 4/1 => /4 */ + VID_PLL_DIV(0x739c, 2, 5, 1), /* 5/1 => /5 */ + VID_PLL_DIV(0x0e38, 0, 6, 1), /* 6/1 => /6 */ + VID_PLL_DIV(0x0000, 3, 25, 4), /* 25/4 => /6.25 */ + VID_PLL_DIV(0x3c78, 1, 7, 1), /* 7/1 => /7 */ + VID_PLL_DIV(0x78f0, 2, 15, 2), /* 15/2 => /7.5 */ + VID_PLL_DIV(0x0fc0, 0, 12, 1), /* 12/1 => /12 */ + VID_PLL_DIV(0x3f80, 1, 14, 1), /* 14/1 => /14 */ + VID_PLL_DIV(0x7f80, 2, 15, 1), /* 15/1 => /15 */ +}; + +static unsigned long meson_vid_pll_div_recalc_rate(const struct clk *clk, unsigned long prate) +{ + struct meson_vid_pll_div_data *data = (struct meson_vid_pll_div_data *)(clk->data); + const struct vid_pll_div *div; + uint32_t shift_val, shift_sel; + + shift_val = regmap_read_bits(clk->base, data->val.reg_off, data->val.shift, MASK(data->val.width)); + shift_sel = regmap_read_bits(clk->base, data->sel.reg_off, data->sel.shift, MASK(data->sel.width)); + + int i; + + for (i = 0; i < ARRAY_SIZE(vid_pll_div_table); ++i) { + if (vid_pll_div_table[i].shift_val == shift_val && vid_pll_div_table[i].shift_sel == shift_sel) { + div = &vid_pll_div_table[i]; + return DIV_ROUND_UP_ULL(prate * div->multiplier, div->divider); + } + } + + /* Return 0 if the vid pll is not configured */ + return 0; +} + +const struct clk_ops meson_vid_pll_div_ro_ops = { + .recalc_rate = meson_vid_pll_div_recalc_rate, +}; + +static int meson_vclk_gate_enable(struct clk *clk) +{ + struct meson_vclk_gate_data *data = (struct meson_vclk_gate_data *)(clk->data); + + regmap_update_bits(clk->base, data->enable.reg_off, data->enable.shift, MASK(data->enable.width), 1); + + /* Do a reset pulse */ + regmap_update_bits(clk->base, data->reset.reg_off, data->reset.shift, MASK(data->reset.width), 1); + regmap_update_bits(clk->base, data->reset.reg_off, data->reset.shift, MASK(data->reset.width), 0); + + return 0; +} + +static int meson_vclk_gate_disable(struct clk *clk) +{ + struct meson_vclk_gate_data *data = (struct meson_vclk_gate_data *)(clk->data); + + regmap_update_bits(clk->base, data->enable.reg_off, data->enable.shift, MASK(data->enable.width), 0); + return 0; +} + +static int meson_vclk_gate_is_enabled(struct clk *clk) +{ + struct meson_vclk_gate_data *data = (struct meson_vclk_gate_data *)(clk->data); + return regmap_read_bits(clk->base, data->enable.reg_off, data->enable.shift, MASK(data->enable.width)); +} + +const struct clk_ops meson_vclk_gate_ops = { + .enable = meson_vclk_gate_enable, + .disable = meson_vclk_gate_disable, + .is_enabled = meson_vclk_gate_is_enabled, +}; + +static unsigned long meson_vclk_div_recalc_rate(const struct clk *clk, unsigned long prate) +{ + struct meson_vclk_div_data *data = (struct meson_vclk_div_data *)(clk->data); + uint32_t div = regmap_read_bits(clk->base, data->div.reg_off, data->div.shift, MASK(data->div.width)); + + /* TODO: Need to verify the following cases */ + if (data->flags & CLK_DIVIDER_ONE_BASED) { + ; + } else if (data->flags & CLK_DIVIDER_POWER_OF_TWO) { + div = 1 << div; + } else if (data->flags & CLK_DIVIDER_MAX_AT_ZERO) { + div = div ? div : MASK(data->div.width) + 1; + } else { + div += 1; + } + + return DIV_ROUND_UP_ULL((uint64_t)prate, div); +} + +static int meson_vclk_div_set_rate(const struct clk *clk, uint64_t rate, uint64_t parent_rate) +{ + struct meson_vclk_div_data *data = (struct meson_vclk_div_data *)(clk->data); + + uint32_t div = DIV_ROUND_UP(parent_rate, rate); + + if (data->flags & CLK_DIVIDER_ONE_BASED) { + /* TODO: to be implemented */ + ; + } else if (data->flags & CLK_DIVIDER_POWER_OF_TWO) { + /* div = __ffs(div); */ + } else if (data->flags & CLK_DIVIDER_MAX_AT_ZERO) { + div = (div == MASK(data->div.width) + 1) ? 0 : div; + } else { + div -= 1; + } + return regmap_update_bits(clk->base, data->div.reg_off, data->div.shift, MASK(data->div.width), div); +} + +static int meson_vclk_div_enable(struct clk *clk) +{ + struct meson_vclk_div_data *data = (struct meson_vclk_div_data *)(clk->data); + + regmap_update_bits(clk->base, data->reset.reg_off, data->reset.shift, MASK(data->reset.width), 0); + regmap_update_bits(clk->base, data->enable.reg_off, data->enable.shift, MASK(data->enable.width), 1); + + return 0; +} + +static int meson_vclk_div_disable(struct clk *clk) +{ + struct meson_vclk_div_data *data = (struct meson_vclk_div_data *)(clk->data); + + regmap_update_bits(clk->base, data->enable.reg_off, data->enable.shift, MASK(data->enable.width), 0); + regmap_update_bits(clk->base, data->reset.reg_off, data->reset.shift, MASK(data->reset.width), 1); + return 0; +} + +static int meson_vclk_div_is_enabled(struct clk *clk) +{ + struct meson_vclk_div_data *data = (struct meson_vclk_div_data *)(clk->data); + return regmap_read_bits(clk->base, data->enable.reg_off, data->enable.shift, MASK(data->enable.width)); +} + +const struct clk_ops meson_vclk_div_ops = { + .recalc_rate = meson_vclk_div_recalc_rate, + /* .determine_rate = meson_vclk_div_determine_rate, */ + .set_rate = meson_vclk_div_set_rate, + .enable = meson_vclk_div_enable, + .disable = meson_vclk_div_disable, + .is_enabled = meson_vclk_div_is_enabled, +}; + +static unsigned long meson_clk_cpu_dyndiv_recalc_rate(const struct clk *clk, unsigned long prate) +{ + struct meson_clk_cpu_dyndiv_data *data = (struct meson_clk_cpu_dyndiv_data *)(clk->data); + uint32_t div = meson_parm_read(clk->base, data->div); + + div += 1; + + return DIV_ROUND_UP_ULL((uint64_t)prate, div); +} + +const struct clk_ops meson_clk_cpu_dyndiv_ops = { + .recalc_rate = meson_clk_cpu_dyndiv_recalc_rate, + /* .determine_rate = meson_clk_cpu_dyndiv_determine_rate, */ + /* .set_rate = meson_clk_cpu_dyndiv_set_rate, */ +}; diff --git a/drivers/clk/meson/clk_driver.mk b/drivers/clk/meson/clk_driver.mk new file mode 100644 index 000000000..7e6afe7d1 --- /dev/null +++ b/drivers/clk/meson/clk_driver.mk @@ -0,0 +1,37 @@ +# +# Copyright 2024, UNSW +# +# SPDX-License-Identifier: BSD-2-Clause + +CLK_DRIVER_DIR := $(dir $(lastword $(MAKEFILE_LIST))) +CLK_CONFIG_HEADER := $(BUILD_DIR)/clk_config.h +CLK_DRIVER_CONF_INC := $(SDDF)/include/sddf/clk +CLK_DRIVER_INC := $(CLK_DRIVER_DIR)/include +CLK_DRIVER_COMMON_DIR := $(SDDF)/drivers/clk/ + +CLK_DRIVER_BOARD_SOURCES := clk-measure.c clk-meson.c sm1-clk.c +CLK_DRIVER_COMMON_SOURCES := clk.c clk-operations.c +CLK_DRIVER_SOURCES := $(addprefix $(CLK_DRIVER_DIR), $(CLK_DRIVER_BOARD_SOURCES)) \ + $(addprefix $(CLK_DRIVER_COMMON_DIR), $(CLK_DRIVER_COMMON_SOURCES)) +CLK_DRIVER_OBJS = $(patsubst $(CLK_DRIVER_COMMON_DIR)%.c, clk_driver/%.o, $(CLK_DRIVER_SOURCES)) + +$(CLK_DRIVER_OBJS): CFLAGS += -I${CLK_DRIVER_INC} \ + -I${CLK_DRIVER_CONF_INC} \ + -I${BUILD_DIR} \ + -I${UART_DRIVER_DIR}/include \ + -I${CLK_DRIVER_COMMON_DIR} +clk_driver/%.o: $(CLK_DRIVER_COMMON_DIR)/%.c $(CLK_CONFIG_HEADER) + mkdir -p $(dir $@) + $(CC) -c $(CFLAGS) -o $@ $< + +clk_driver.elf: $(CLK_DRIVER_OBJS) libsddf_util_debug.a + $(LD) $(LDFLAGS) $^ $(LIBS) -o $@ + +$(CLK_CONFIG_HEADER): $(DTS) $(CLK_DRIVER_COMMON_DIR)/create_clk_config.py + $(PYTHON) $(CLK_DRIVER_COMMON_DIR)/create_clk_config.py $(DTS) $(BUILD_DIR) + +clean:: + rm -f clk_driver.o + +clobber:: + rm -rf clk_driver.elf diff --git a/drivers/clk/meson/include/clk-measure.h b/drivers/clk/meson/include/clk-measure.h new file mode 100644 index 000000000..7de22a6b1 --- /dev/null +++ b/drivers/clk/meson/include/clk-measure.h @@ -0,0 +1,9 @@ +/* + * Copyright 2024, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +const char *const *get_msr_clk_list(void); +unsigned long clk_msr(unsigned long clk_mux); diff --git a/drivers/clk/meson/include/clk-meson.h b/drivers/clk/meson/include/clk-meson.h new file mode 100644 index 000000000..77e52d97c --- /dev/null +++ b/drivers/clk/meson/include/clk-meson.h @@ -0,0 +1,174 @@ +#pragma once + +#include + +#define CLK_MESON_MPLL_ROUND_CLOSEST BIT(0) +#define CLK_MESON_MPLL_SPREAD_SPECTRUM BIT(1) + +#define CLK_MESON_PLL_ROUND_CLOSEST BIT(0) +#define CLK_MESON_PLL_NOINIT_ENABLED BIT(1) + +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2015 Endless Mobile, Inc. + * Author: Carlo Caione + * + * Derived from: https://github.com/torvalds/linux/blob/6485cf5ea253d40d507cd71253c9568c5470cd27/drivers/clk/meson/parm.h + */ +/** + * struct parm - struct defines bits of a clock control field in registers + * + * @reg_off: offset of the register + * @shift: shift of the control field + * @width: width of the control field + * + * Register/value pairs for sequences of writes with an optional delay in + * microseconds to be applied after each write. + */ +struct parm { + uint16_t reg_off; + uint8_t shift; + uint8_t width; +}; + +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2011 Wolfson Microelectronics plc + * + * Author: Mark Brown + * + * Derived from: https://github.com/torvalds/linux/blob/6485cf5ea253d40d507cd71253c9568c5470cd27/include/linux/regmap.h + */ +/** + * struct reg_sequence - An individual write from a sequence of writes. + * + * @reg: Register address. + * @def: Register value. + * @delay_us: Delay to be applied after the register write in microseconds + */ +struct reg_sequence { + unsigned int reg; + unsigned int def; + unsigned int delay_us; +}; + +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2019 BayLibre, SAS. + * Author: Jerome Brunet + */ +/** + * struct reg_sequence - An individual write from a sequence of writes. + * + * @m: Register address. + * @n: Register value. + * @delay_us: Delay to be applied after the register write in microseconds + * + * Register/value pairs for sequences of writes with an optional delay in + * microseconds to be applied after each write. + */ +struct pll_params_table { + unsigned int m; + unsigned int n; +}; + +struct meson_clk_cpu_dyndiv_data { + struct parm div; + struct parm dyn; +}; + +struct meson_clk_pll_data { + struct parm en; + struct parm m; + struct parm n; + struct parm frac; + struct parm l; + struct parm rst; + struct parm current_en; + struct parm l_detect; + const struct reg_sequence *init_regs; + unsigned int init_count; + const struct pll_params_table *table; + /* const struct pll_mult_range *range; */ + uint8_t range_min; + uint8_t range_max; + uint8_t flags; +}; + +struct meson_clk_mpll_data { + struct parm sdm; + struct parm sdm_en; + struct parm n2; + struct parm ssen; + struct parm misc; + const struct reg_sequence *init_regs; + unsigned int init_count; + /* spinlock_t *lock; */ + uint8_t flags; +}; + +struct meson_vid_pll_div_data { + struct parm val; + struct parm sel; +}; + +struct meson_vclk_gate_data { + struct parm enable; + struct parm reset; + uint8_t flags; +}; + +struct meson_vclk_div_data { + struct parm div; + struct parm enable; + struct parm reset; + const struct clk_div_table *table; + uint8_t flags; +}; + +extern const struct clk_ops meson_clk_pll_ops; +extern const struct clk_ops meson_clk_pll_ro_ops; +extern const struct clk_ops meson_clk_mpll_ops; +extern const struct clk_ops meson_clk_pcie_pll_ops; +extern const struct clk_ops meson_vid_pll_div_ro_ops; +extern const struct clk_ops meson_vclk_gate_ops; +extern const struct clk_ops meson_vclk_div_ops; +extern const struct clk_ops meson_clk_cpu_dyndiv_ops; + +#define MESON_CLK81_GATE(_name, _reg, _bit) \ +struct clk _name = { \ + .data = &(struct clk_gate_data) { \ + .offset = (_reg), \ + .bit_idx = (_bit), \ + .flags = 0, \ + }, \ + .hw.init = &(struct clk_init_data) { \ + .name = #_name, \ + .ops = &clk_gate_ops, \ + .parent_clks = (const struct clk *[]) { \ + &g12a_clk81, \ + }, \ + .num_parents = 1, \ + .flags = 0, \ + }, \ +} + +#define MESON_CLK81_GATE_RO(_name, _reg, _bit) \ +struct clk _name = { \ + .data = &(struct clk_gate_data) { \ + .offset = (_reg), \ + .bit_idx = (_bit), \ + .flags = 0, \ + }, \ + .hw.init = &(struct clk_init_data) { \ + .name = #_name, \ + .ops = &clk_gate_ro_ops, \ + .parent_clks = (const struct clk *[]) { \ + &g12a_clk81, \ + }, \ + .num_parents = 1, \ + .flags = 0, \ + }, \ +} + +struct clk **get_clk_list(void); diff --git a/drivers/clk/meson/include/clk-sm1.h b/drivers/clk/meson/include/clk-sm1.h new file mode 100644 index 000000000..1ac3e05eb --- /dev/null +++ b/drivers/clk/meson/include/clk-sm1.h @@ -0,0 +1,11 @@ +/* + * Copyright 2024, UNSW + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +#define NUM_CLK_LIST 280 diff --git a/drivers/clk/meson/include/g12a-regs.h b/drivers/clk/meson/include/g12a-regs.h new file mode 100644 index 000000000..9c1a91305 --- /dev/null +++ b/drivers/clk/meson/include/g12a-regs.h @@ -0,0 +1,131 @@ +/* SPDX-License-Identifier: (GPL-2.0-or-later OR MIT) */ +/* + * Copyright (c) 2016 Amlogic, Inc. + * Author: Michael Turquette + * + * Copyright (c) 2018 Amlogic, inc. + * Author: Qiufang Dai + * Author: Jian Hu + * + * Source: https://github.com/torvalds/linux/blob/20371ba120635d9ab7fc7670497105af8f33eb08/drivers/clk/meson/g12a.h + */ +#ifndef __G12A_H +#define __G12A_H + +/* + * Clock controller register offsets + * + * Register offsets from the data sheet must be multiplied by 4 before + * adding them to the base address to get the right value. + */ +#define HHI_MIPI_CNTL0 0x000 +#define HHI_MIPI_CNTL1 0x004 +#define HHI_MIPI_CNTL2 0x008 +#define HHI_MIPI_STS 0x00C +#define HHI_GP0_PLL_CNTL0 0x040 +#define HHI_GP0_PLL_CNTL1 0x044 +#define HHI_GP0_PLL_CNTL2 0x048 +#define HHI_GP0_PLL_CNTL3 0x04C +#define HHI_GP0_PLL_CNTL4 0x050 +#define HHI_GP0_PLL_CNTL5 0x054 +#define HHI_GP0_PLL_CNTL6 0x058 +#define HHI_GP0_PLL_STS 0x05C +#define HHI_GP1_PLL_CNTL0 0x060 +#define HHI_GP1_PLL_CNTL1 0x064 +#define HHI_GP1_PLL_CNTL2 0x068 +#define HHI_GP1_PLL_CNTL3 0x06C +#define HHI_GP1_PLL_CNTL4 0x070 +#define HHI_GP1_PLL_CNTL5 0x074 +#define HHI_GP1_PLL_CNTL6 0x078 +#define HHI_GP1_PLL_STS 0x07C +#define HHI_PCIE_PLL_CNTL0 0x098 +#define HHI_PCIE_PLL_CNTL1 0x09C +#define HHI_PCIE_PLL_CNTL2 0x0A0 +#define HHI_PCIE_PLL_CNTL3 0x0A4 +#define HHI_PCIE_PLL_CNTL4 0x0A8 +#define HHI_PCIE_PLL_CNTL5 0x0AC +#define HHI_PCIE_PLL_STS 0x0B8 +#define HHI_HIFI_PLL_CNTL0 0x0D8 +#define HHI_HIFI_PLL_CNTL1 0x0DC +#define HHI_HIFI_PLL_CNTL2 0x0E0 +#define HHI_HIFI_PLL_CNTL3 0x0E4 +#define HHI_HIFI_PLL_CNTL4 0x0E8 +#define HHI_HIFI_PLL_CNTL5 0x0EC +#define HHI_HIFI_PLL_CNTL6 0x0F0 +#define HHI_VIID_CLK_DIV 0x128 +#define HHI_VIID_CLK_CNTL 0x12C +#define HHI_GCLK_MPEG0 0x140 +#define HHI_GCLK_MPEG1 0x144 +#define HHI_GCLK_MPEG2 0x148 +#define HHI_GCLK_OTHER 0x150 +#define HHI_GCLK_OTHER2 0x154 +#define HHI_SYS_CPU_CLK_CNTL1 0x15c +#define HHI_VID_CLK_DIV 0x164 +#define HHI_MPEG_CLK_CNTL 0x174 +#define HHI_AUD_CLK_CNTL 0x178 +#define HHI_VID_CLK_CNTL 0x17c +#define HHI_TS_CLK_CNTL 0x190 +#define HHI_VID_CLK_CNTL2 0x194 +#define HHI_SYS_CPU_CLK_CNTL0 0x19c +#define HHI_VID_PLL_CLK_DIV 0x1A0 +#define HHI_MALI_CLK_CNTL 0x1b0 +#define HHI_VPU_CLKC_CNTL 0x1b4 +#define HHI_VPU_CLK_CNTL 0x1bC +#define HHI_ISP_CLK_CNTL 0x1C0 +#define HHI_NNA_CLK_CNTL 0x1C8 +#define HHI_HDMI_CLK_CNTL 0x1CC +#define HHI_VDEC_CLK_CNTL 0x1E0 +#define HHI_VDEC2_CLK_CNTL 0x1E4 +#define HHI_VDEC3_CLK_CNTL 0x1E8 +#define HHI_VDEC4_CLK_CNTL 0x1EC +#define HHI_HDCP22_CLK_CNTL 0x1F0 +#define HHI_VAPBCLK_CNTL 0x1F4 +#define HHI_SYS_CPUB_CLK_CNTL1 0x200 +#define HHI_SYS_CPUB_CLK_CNTL 0x208 +#define HHI_VPU_CLKB_CNTL 0x20C +#define HHI_SYS_CPU_CLK_CNTL2 0x210 +#define HHI_SYS_CPU_CLK_CNTL3 0x214 +#define HHI_SYS_CPU_CLK_CNTL4 0x218 +#define HHI_SYS_CPU_CLK_CNTL5 0x21c +#define HHI_SYS_CPU_CLK_CNTL6 0x220 +#define HHI_GEN_CLK_CNTL 0x228 +#define HHI_VDIN_MEAS_CLK_CNTL 0x250 +#define HHI_MIPIDSI_PHY_CLK_CNTL 0x254 +#define HHI_NAND_CLK_CNTL 0x25C +#define HHI_SD_EMMC_CLK_CNTL 0x264 +#define HHI_MPLL_CNTL0 0x278 +#define HHI_MPLL_CNTL1 0x27C +#define HHI_MPLL_CNTL2 0x280 +#define HHI_MPLL_CNTL3 0x284 +#define HHI_MPLL_CNTL4 0x288 +#define HHI_MPLL_CNTL5 0x28c +#define HHI_MPLL_CNTL6 0x290 +#define HHI_MPLL_CNTL7 0x294 +#define HHI_MPLL_CNTL8 0x298 +#define HHI_FIX_PLL_CNTL0 0x2A0 +#define HHI_FIX_PLL_CNTL1 0x2A4 +#define HHI_FIX_PLL_CNTL3 0x2AC +#define HHI_SYS_PLL_CNTL0 0x2f4 +#define HHI_SYS_PLL_CNTL1 0x2f8 +#define HHI_SYS_PLL_CNTL2 0x2fc +#define HHI_SYS_PLL_CNTL3 0x300 +#define HHI_SYS_PLL_CNTL4 0x304 +#define HHI_SYS_PLL_CNTL5 0x308 +#define HHI_SYS_PLL_CNTL6 0x30c +#define HHI_HDMI_PLL_CNTL0 0x320 +#define HHI_HDMI_PLL_CNTL1 0x324 +#define HHI_HDMI_PLL_CNTL2 0x328 +#define HHI_HDMI_PLL_CNTL3 0x32c +#define HHI_HDMI_PLL_CNTL4 0x330 +#define HHI_HDMI_PLL_CNTL5 0x334 +#define HHI_HDMI_PLL_CNTL6 0x338 +#define HHI_SPICC_CLK_CNTL 0x3dc +#define HHI_SYS1_PLL_CNTL0 0x380 +#define HHI_SYS1_PLL_CNTL1 0x384 +#define HHI_SYS1_PLL_CNTL2 0x388 +#define HHI_SYS1_PLL_CNTL3 0x38c +#define HHI_SYS1_PLL_CNTL4 0x390 +#define HHI_SYS1_PLL_CNTL5 0x394 +#define HHI_SYS1_PLL_CNTL6 0x398 + +#endif /* __G12A_H */ diff --git a/drivers/clk/meson/sm1-clk.c b/drivers/clk/meson/sm1-clk.c new file mode 100644 index 000000000..c722da97f --- /dev/null +++ b/drivers/clk/meson/sm1-clk.c @@ -0,0 +1,1592 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Amlogic Meson-G12A Clock Controller Driver + * + * Copyright (c) 2016 Baylibre SAS. + * Author: Michael Turquette + * + * Copyright (c) 2018 Amlogic, inc. + * Author: Qiufang Dai + * Author: Jian Hu + * + * Derived from: https://github.com/torvalds/linux/blob/befe87380e21f0d37633273e1068c9318f8135ff/drivers/clk/meson/g12a.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +uintptr_t clk_regs = 0x3200000; +uintptr_t msr_clk_base = 0x3200000; + +static struct clk g12a_xtal = { .data = + &(struct clk_source_data) { + .rate = 24000000, + }, + .hw.init = &(struct clk_init_data) { + .name = "xtal", + .ops = &clk_source_ops, + } }; + +static struct clk g12a_fixed_pll_dco = { + .data = &(struct meson_clk_pll_data){ + .en = { + .reg_off = HHI_FIX_PLL_CNTL0, + .shift = 28, + .width = 1, + }, + .m = { + .reg_off = HHI_FIX_PLL_CNTL0, + .shift = 0, + .width = 8, + }, + .n = { + .reg_off = HHI_FIX_PLL_CNTL0, + .shift = 10, + .width = 5, + }, + .frac = { + .reg_off = HHI_FIX_PLL_CNTL1, + .shift = 0, + .width = 17, + }, + .l = { + .reg_off = HHI_FIX_PLL_CNTL0, + .shift = 31, + .width = 1, + }, + .rst = { + .reg_off = HHI_FIX_PLL_CNTL0, + .shift = 29, + .width = 1, + }, + }, + .hw.init = &(struct clk_init_data){ + .name = "fixed_pll_dco", + .ops = &meson_clk_pll_ro_ops, + .parent_data = &(const struct clk_parent_data) { + .name = "xtal", + }, + .num_parents = 1, + }, +}; +static CLK_DIV_RO(g12a_fixed_pll, HHI_FIX_PLL_CNTL0, 16, 2, CLK_DIVIDER_POWER_OF_TWO, { &g12a_fixed_pll_dco }, 1, 0); +static struct clk g12a_sys_pll_dco = { + .data = &(struct meson_clk_pll_data){ + .en = { + .reg_off = HHI_SYS_PLL_CNTL0, + .shift = 28, + .width = 1, + }, + .m = { + .reg_off = HHI_SYS_PLL_CNTL0, + .shift = 0, + .width = 8, + }, + .n = { + .reg_off = HHI_SYS_PLL_CNTL0, + .shift = 10, + .width = 5, + }, + .l = { + .reg_off = HHI_SYS_PLL_CNTL0, + .shift = 31, + .width = 1, + }, + .rst = { + .reg_off = HHI_SYS_PLL_CNTL0, + .shift = 29, + .width = 1, + }, + .range_min = 128, + .range_max = 250, + }, + .hw.init = &(struct clk_init_data){ + .name = "sys_pll_dco", + .ops = &meson_clk_pll_ops, + .parent_data = &(const struct clk_parent_data) { + .name = "xtal", + }, + .num_parents = 1, + /* This clock feeds the CPU, avoid disabling it */ + .flags = CLK_IS_CRITICAL, + }, +}; +static CLK_DIV(g12a_sys_pll, HHI_SYS_PLL_CNTL0, 16, 3, CLK_DIVIDER_POWER_OF_TWO, { &g12a_sys_pll_dco }, 1, 0); +static CLK_GATE_RO(g12a_sys_pll_div16_en, HHI_SYS_CPU_CLK_CNTL1, 24, 0, { &g12a_sys_pll }, 1, 0); +static CLK_FIXED_FACTOR(g12a_sys_pll_div16, 1, 16, 0, { &g12a_sys_pll_div16_en }, 1, 0); +static CLK_FIXED_FACTOR(g12a_fclk_div2_div, 1, 2, 0, { &g12a_fixed_pll }, 1, 0); +static CLK_GATE(g12a_fclk_div2, HHI_FIX_PLL_CNTL1, 24, 0, { &g12a_fclk_div2_div }, 1, 0); +static CLK_FIXED_FACTOR(g12a_fclk_div3_div, 1, 3, 0, { &g12a_fixed_pll }, 1, 0); +static CLK_GATE(g12a_fclk_div3, HHI_FIX_PLL_CNTL1, 20, 0, { &g12a_fclk_div3_div }, 1, 0); +const struct clk_parent_data g12a_cpu_clk_premux0_parent_table[] = { + { + .name = "xtal", + }, + { .clk = &g12a_fclk_div2 }, + { .clk = &g12a_fclk_div3 }, +}; +static CLK_MUX(g12a_cpu_clk_premux0, HHI_SYS_CPU_CLK_CNTL0, 0x3, 0, 0, CLK_MUX_ROUND_CLOSEST, + g12a_cpu_clk_premux0_parent_table, 3, 0); +const struct clk_parent_data g12a_cpu_clk_premux1_parent_table[] = { + { + .name = "xtal", + }, + { .clk = &g12a_fclk_div2 }, + { .clk = &g12a_fclk_div3 }, +}; +static CLK_MUX(g12a_cpu_clk_premux1, HHI_SYS_CPU_CLK_CNTL0, 0x3, 16, 0, 0, g12a_cpu_clk_premux1_parent_table, 3, 0); +static struct clk g12a_cpu_clk_mux0_div = { + .data = &(struct meson_clk_cpu_dyndiv_data){ + .div = { + .reg_off = HHI_SYS_CPU_CLK_CNTL0, + .shift = 4, + .width = 6, + }, + .dyn = { + .reg_off = HHI_SYS_CPU_CLK_CNTL0, + .shift = 26, + .width = 1, + }, + }, + .hw.init = &(struct clk_init_data){ + .name = "cpu_clk_dyn0_div", + .ops = &meson_clk_cpu_dyndiv_ops, + .parent_clks = (const struct clk *[]) { + &g12a_cpu_clk_premux0 + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; +const struct clk_parent_data g12a_cpu_clk_postmux0_parent_table[] = { + { .clk = &g12a_cpu_clk_premux0 }, + { .clk = &g12a_cpu_clk_mux0_div }, +}; +static CLK_MUX(g12a_cpu_clk_postmux0, HHI_SYS_CPU_CLK_CNTL0, 0x1, 2, 0, CLK_MUX_ROUND_CLOSEST, + g12a_cpu_clk_postmux0_parent_table, 2, 0); +static CLK_DIV_RO(g12a_cpu_clk_mux1_div, HHI_SYS_CPU_CLK_CNTL0, 20, 6, 0, { &g12a_cpu_clk_premux1 }, 1, 0); +const struct clk_parent_data g12a_cpu_clk_postmux1_parent_table[] = { + { .clk = &g12a_cpu_clk_premux1 }, + { .clk = &g12a_cpu_clk_mux1_div }, +}; +static CLK_MUX(g12a_cpu_clk_postmux1, HHI_SYS_CPU_CLK_CNTL0, 0x1, 18, 0, 0, g12a_cpu_clk_postmux1_parent_table, 2, 0); +const struct clk_parent_data g12a_cpu_clk_dyn_parent_table[] = { + { .clk = &g12a_cpu_clk_postmux0 }, + { .clk = &g12a_cpu_clk_postmux1 }, +}; +static CLK_MUX(g12a_cpu_clk_dyn, HHI_SYS_CPU_CLK_CNTL0, 0x1, 10, 0, CLK_MUX_ROUND_CLOSEST, + g12a_cpu_clk_dyn_parent_table, 2, 0); +const struct clk_parent_data g12a_cpu_clk_parent_table[] = { + { .clk = &g12a_cpu_clk_dyn }, + { .clk = &g12a_sys_pll }, +}; +static CLK_MUX(g12a_cpu_clk, HHI_SYS_CPU_CLK_CNTL0, 0x1, 11, 0, CLK_MUX_ROUND_CLOSEST, g12a_cpu_clk_parent_table, 2, 0); + +static const struct reg_sequence g12a_gp0_init_regs[] = { + { .reg = HHI_GP0_PLL_CNTL1, .def = 0x00000000 }, { .reg = HHI_GP0_PLL_CNTL2, .def = 0x00000000 }, + { .reg = HHI_GP0_PLL_CNTL3, .def = 0x48681c00 }, { .reg = HHI_GP0_PLL_CNTL4, .def = 0x33771290 }, + { .reg = HHI_GP0_PLL_CNTL5, .def = 0x39272000 }, { .reg = HHI_GP0_PLL_CNTL6, .def = 0x56540000 }, +}; +static struct clk g12a_gp0_pll_dco = { + .data = &(struct meson_clk_pll_data){ + .en = { + .reg_off = HHI_GP0_PLL_CNTL0, + .shift = 28, + .width = 1, + }, + .m = { + .reg_off = HHI_GP0_PLL_CNTL0, + .shift = 0, + .width = 8, + }, + .n = { + .reg_off = HHI_GP0_PLL_CNTL0, + .shift = 10, + .width = 5, + }, + .frac = { + .reg_off = HHI_GP0_PLL_CNTL1, + .shift = 0, + .width = 17, + }, + .l = { + .reg_off = HHI_GP0_PLL_CNTL0, + .shift = 31, + .width = 1, + }, + .rst = { + .reg_off = HHI_GP0_PLL_CNTL0, + .shift = 29, + .width = 1, + }, + .range_min = 125, + .range_max = 255, + .init_regs = g12a_gp0_init_regs, + .init_count = ARRAY_SIZE(g12a_gp0_init_regs), + }, + .hw.init = &(struct clk_init_data){ + .name = "gp0_pll_dco", + .ops = &meson_clk_pll_ops, + .parent_data = &(const struct clk_parent_data) { + .name = "xtal", + }, + .num_parents = 1, + }, +}; +static CLK_DIV(g12a_gp0_pll, HHI_GP0_PLL_CNTL0, 16, 3, (CLK_DIVIDER_POWER_OF_TWO | CLK_DIVIDER_ROUND_CLOSEST), + { &g12a_gp0_pll_dco }, 1, 0); +static struct clk sm1_gp1_pll_dco = { + .data = &(struct meson_clk_pll_data){ + .en = { + .reg_off = HHI_GP1_PLL_CNTL0, + .shift = 28, + .width = 1, + }, + .m = { + .reg_off = HHI_GP1_PLL_CNTL0, + .shift = 0, + .width = 8, + }, + .n = { + .reg_off = HHI_GP1_PLL_CNTL0, + .shift = 10, + .width = 5, + }, + .frac = { + .reg_off = HHI_GP1_PLL_CNTL1, + .shift = 0, + .width = 17, + }, + .l = { + .reg_off = HHI_GP1_PLL_CNTL0, + .shift = 31, + .width = 1, + }, + .rst = { + .reg_off = HHI_GP1_PLL_CNTL0, + .shift = 29, + .width = 1, + }, + }, + .hw.init = &(struct clk_init_data){ + .name = "gp1_pll_dco", + .ops = &meson_clk_pll_ro_ops, + .parent_data = &(const struct clk_parent_data) { + .name = "xtal", + }, + .num_parents = 1, + /* This clock feeds the DSU, avoid disabling it */ + .flags = CLK_IS_CRITICAL, + }, +}; +static CLK_DIV_RO(sm1_gp1_pll, HHI_GP1_PLL_CNTL0, 16, 3, (CLK_DIVIDER_POWER_OF_TWO | CLK_DIVIDER_ROUND_CLOSEST), + { &sm1_gp1_pll_dco }, 1, 0); + +const struct clk_parent_data sm1_dsu_clk_premux0_parent_table[] = { + { + .name = "xtal", + }, + { .clk = &g12a_fclk_div2 }, + { .clk = &g12a_fclk_div3 }, + { .clk = &sm1_gp1_pll }, +}; +static CLK_MUX_RO(sm1_dsu_clk_premux0, HHI_SYS_CPU_CLK_CNTL5, 0x3, 0, 0, 0, sm1_dsu_clk_premux0_parent_table, 4, 0); +const struct clk_parent_data sm1_dsu_clk_premux1_parent_table[] = { + { + .name = "xtal", + }, + { .clk = &g12a_fclk_div2 }, + { .clk = &g12a_fclk_div3 }, + { .clk = &sm1_gp1_pll }, +}; +static CLK_MUX_RO(sm1_dsu_clk_premux1, HHI_SYS_CPU_CLK_CNTL5, 0x3, 16, 0, 0, sm1_dsu_clk_premux1_parent_table, 4, 0); +static CLK_DIV_RO(sm1_dsu_clk_mux0_div, HHI_SYS_CPU_CLK_CNTL5, 4, 6, 0, { &sm1_dsu_clk_premux0 }, 1, 0); +const struct clk_parent_data sm1_dsu_clk_postmux0_parent_table[] = { + { + .clk = &sm1_dsu_clk_premux0, + }, + { .clk = &sm1_dsu_clk_mux0_div }, +}; +static CLK_MUX_RO(sm1_dsu_clk_postmux0, HHI_SYS_CPU_CLK_CNTL5, 0x1, 2, 0, 0, sm1_dsu_clk_postmux0_parent_table, 2, 0); +static CLK_DIV_RO(sm1_dsu_clk_mux1_div, HHI_SYS_CPU_CLK_CNTL5, 20, 6, 0, { &sm1_dsu_clk_premux1 }, 1, 0); + +const struct clk_parent_data sm1_dsu_clk_postmux1_parent_table[] = { + { + .clk = &sm1_dsu_clk_premux1, + }, + { .clk = &sm1_dsu_clk_mux1_div }, +}; +static CLK_MUX_RO(sm1_dsu_clk_postmux1, HHI_SYS_CPU_CLK_CNTL5, 0x1, 18, 0, 0, sm1_dsu_clk_postmux1_parent_table, 2, 0); +const struct clk_parent_data sm1_dsu_clk_dyn_parent_table[] = { + { + .clk = &sm1_dsu_clk_premux0, + }, + { + .clk = &sm1_dsu_clk_postmux1, + }, +}; +static CLK_MUX_RO(sm1_dsu_clk_dyn, HHI_SYS_CPU_CLK_CNTL5, 0x1, 10, 0, 0, sm1_dsu_clk_dyn_parent_table, 2, 0); +const struct clk_parent_data sm1_dsu_final_clk_parent_table[] = { + { + .clk = &sm1_dsu_clk_dyn, + }, + { + .clk = &g12a_sys_pll, + }, +}; +static CLK_MUX_RO(sm1_dsu_final_clk, HHI_SYS_CPU_CLK_CNTL5, 0x1, 11, 0, 0, sm1_dsu_final_clk_parent_table, 2, 0); +const struct clk_parent_data sm1_cpu_clk_parent_table[] = { + { + .clk = &g12a_cpu_clk, + }, +}; +static CLK_MUX_RO(sm1_cpu1_clk, HHI_SYS_CPU_CLK_CNTL6, 0x1, 24, 0, 0, sm1_cpu_clk_parent_table, 1, 0); +static CLK_MUX_RO(sm1_cpu2_clk, HHI_SYS_CPU_CLK_CNTL6, 0x1, 25, 0, 0, sm1_cpu_clk_parent_table, 1, 0); +static CLK_MUX_RO(sm1_cpu3_clk, HHI_SYS_CPU_CLK_CNTL6, 0x1, 26, 0, 0, sm1_cpu_clk_parent_table, 1, 0); +const struct clk_parent_data sm1_dsu_clk_parent_table[] = { + { + .clk = &g12a_cpu_clk, + }, + { + .clk = &sm1_dsu_final_clk, + }, +}; +static CLK_MUX_RO(sm1_dsu_clk, HHI_SYS_CPU_CLK_CNTL6, 0x1, 27, 0, 0, sm1_dsu_clk_parent_table, 2, 0); +static CLK_GATE_RO(g12a_cpu_clk_div16_en, HHI_SYS_CPU_CLK_CNTL1, 1, 0, { &g12a_cpu_clk }, 1, 0); +static CLK_FIXED_FACTOR(g12a_cpu_clk_div16, 1, 16, 0, { &g12a_cpu_clk_div16_en }, 1, 0); +static CLK_DIV_RO(g12a_cpu_clk_apb_div, HHI_SYS_CPU_CLK_CNTL1, 3, 3, CLK_DIVIDER_POWER_OF_TWO, { &g12a_cpu_clk }, 1, 0); +static CLK_GATE_RO(g12a_cpu_clk_apb, HHI_SYS_CPU_CLK_CNTL1, 1, 0, { &g12a_cpu_clk_apb_div }, 1, 0); +static CLK_DIV_RO(g12a_cpu_clk_atb_div, HHI_SYS_CPU_CLK_CNTL1, 6, 3, CLK_DIVIDER_POWER_OF_TWO, { &g12a_cpu_clk }, 1, 0); +static CLK_GATE_RO(g12a_cpu_clk_atb, HHI_SYS_CPU_CLK_CNTL1, 17, 0, { &g12a_cpu_clk_atb_div }, 1, 0); +static CLK_DIV_RO(g12a_cpu_clk_axi_div, HHI_SYS_CPU_CLK_CNTL1, 9, 3, CLK_DIVIDER_POWER_OF_TWO, { &g12a_cpu_clk }, 1, 0); +static CLK_GATE_RO(g12a_cpu_clk_axi, HHI_SYS_CPU_CLK_CNTL1, 18, 0, { &g12a_cpu_clk_axi_div }, 1, 0); +/* TODO: special case, ignore its parent clk at the moment */ +static CLK_DIV_RO(g12a_cpu_clk_trace_div, HHI_SYS_CPU_CLK_CNTL1, 20, 3, CLK_DIVIDER_POWER_OF_TWO, {}, 0, 0); +static CLK_GATE_RO(g12a_cpu_clk_trace, HHI_SYS_CPU_CLK_CNTL1, 23, 0, { &g12a_cpu_clk_trace_div }, 1, 0); + +static const struct reg_sequence g12a_hifi_init_regs[] = { + { .reg = HHI_HIFI_PLL_CNTL1, .def = 0x00000000 }, { .reg = HHI_HIFI_PLL_CNTL2, .def = 0x00000000 }, + { .reg = HHI_HIFI_PLL_CNTL3, .def = 0x6a285c00 }, { .reg = HHI_HIFI_PLL_CNTL4, .def = 0x65771290 }, + { .reg = HHI_HIFI_PLL_CNTL5, .def = 0x39272000 }, { .reg = HHI_HIFI_PLL_CNTL6, .def = 0x56540000 }, +}; + +static struct clk g12a_hifi_pll_dco = { + .data = &(struct meson_clk_pll_data){ + .en = { + .reg_off = HHI_HIFI_PLL_CNTL0, + .shift = 28, + .width = 1, + }, + .m = { + .reg_off = HHI_HIFI_PLL_CNTL0, + .shift = 0, + .width = 8, + }, + .n = { + .reg_off = HHI_HIFI_PLL_CNTL0, + .shift = 10, + .width = 5, + }, + .frac = { + .reg_off = HHI_HIFI_PLL_CNTL1, + .shift = 0, + .width = 17, + }, + .l = { + .reg_off = HHI_HIFI_PLL_CNTL0, + .shift = 31, + .width = 1, + }, + .rst = { + .reg_off = HHI_HIFI_PLL_CNTL0, + .shift = 29, + .width = 1, + }, + .range_min = 125, + .range_max = 255, + .init_regs = g12a_hifi_init_regs, + .init_count = ARRAY_SIZE(g12a_hifi_init_regs), + .flags = CLK_MESON_PLL_ROUND_CLOSEST, + }, + .hw.init = &(struct clk_init_data){ + .name = "hifi_pll_dco", + .ops = &meson_clk_pll_ops, + .parent_data = &(const struct clk_parent_data) { + .name = "xtal", + }, + .num_parents = 1, + }, +}; +static CLK_DIV(g12a_hifi_pll, HHI_HIFI_PLL_CNTL0, 16, 2, (CLK_DIVIDER_POWER_OF_TWO | CLK_DIVIDER_ROUND_CLOSEST), + { &g12a_hifi_pll_dco }, 1, 0); + +/* + * The Meson G12A PCIE PLL is fined tuned to deliver a very precise + * 100MHz reference clock for the PCIe Analog PHY, and thus requires + * a strict register sequence to enable the PLL. + */ +static const struct reg_sequence g12a_pcie_pll_init_regs[] = { + { .reg = HHI_PCIE_PLL_CNTL0, .def = 0x20090496 }, + { .reg = HHI_PCIE_PLL_CNTL0, .def = 0x30090496 }, + { .reg = HHI_PCIE_PLL_CNTL1, .def = 0x00000000 }, + { .reg = HHI_PCIE_PLL_CNTL2, .def = 0x00001100 }, + { .reg = HHI_PCIE_PLL_CNTL3, .def = 0x10058e00 }, + { .reg = HHI_PCIE_PLL_CNTL4, .def = 0x000100c0 }, + { .reg = HHI_PCIE_PLL_CNTL5, .def = 0x68000048 }, + { .reg = HHI_PCIE_PLL_CNTL5, .def = 0x68000068, .delay_us = 20 }, + { .reg = HHI_PCIE_PLL_CNTL4, .def = 0x008100c0, .delay_us = 10 }, + { .reg = HHI_PCIE_PLL_CNTL0, .def = 0x34090496 }, + { .reg = HHI_PCIE_PLL_CNTL0, .def = 0x14090496, .delay_us = 10 }, + { .reg = HHI_PCIE_PLL_CNTL2, .def = 0x00001000 }, +}; +/* Keep a single entry table for recalc/round_rate() ops */ +static const struct pll_params_table g12a_pcie_pll_table[] = { + { .m = 150, .n = 1 }, + { .m = 0, .n = 0 }, +}; +static struct clk g12a_pcie_pll_dco = { + .data = &(struct meson_clk_pll_data){ + .en = { + .reg_off = HHI_PCIE_PLL_CNTL0, + .shift = 28, + .width = 1, + }, + .m = { + .reg_off = HHI_PCIE_PLL_CNTL0, + .shift = 0, + .width = 8, + }, + .n = { + .reg_off = HHI_PCIE_PLL_CNTL0, + .shift = 10, + .width = 5, + }, + .frac = { + .reg_off = HHI_PCIE_PLL_CNTL1, + .shift = 0, + .width = 12, + }, + .l = { + .reg_off = HHI_PCIE_PLL_CNTL0, + .shift = 31, + .width = 1, + }, + .rst = { + .reg_off = HHI_PCIE_PLL_CNTL0, + .shift = 29, + .width = 1, + }, + .table = g12a_pcie_pll_table, + .init_regs = g12a_pcie_pll_init_regs, + .init_count = ARRAY_SIZE(g12a_pcie_pll_init_regs), + }, + .hw.init = &(struct clk_init_data){ + .name = "pcie_pll_dco", + .ops = &meson_clk_pcie_pll_ops, + .parent_data = &(const struct clk_parent_data) { + .name = "xtal", + }, + .num_parents = 1, + }, +}; +static CLK_FIXED_FACTOR(g12a_pcie_pll_dco_div2, 1, 2, 0, { &g12a_pcie_pll_dco }, 1, 0); +static CLK_DIV(g12a_pcie_pll_od, HHI_PCIE_PLL_CNTL0, 16, 5, + CLK_DIVIDER_ROUND_CLOSEST | CLK_DIVIDER_ONE_BASED | CLK_DIVIDER_ALLOW_ZERO, { &g12a_pcie_pll_dco_div2 }, + 1, 0); +static CLK_FIXED_FACTOR(g12a_pcie_pll, 1, 2, 0, { &g12a_pcie_pll_od }, 1, 0); +static struct clk g12a_hdmi_pll_dco = { + .data = &(struct meson_clk_pll_data){ + .en = { + .reg_off = HHI_HDMI_PLL_CNTL0, + .shift = 28, + .width = 1, + }, + .m = { + .reg_off = HHI_HDMI_PLL_CNTL0, + .shift = 0, + .width = 8, + }, + .n = { + .reg_off = HHI_HDMI_PLL_CNTL0, + .shift = 10, + .width = 5, + }, + .frac = { + .reg_off = HHI_HDMI_PLL_CNTL1, + .shift = 0, + .width = 16, + }, + .l = { + .reg_off = HHI_HDMI_PLL_CNTL0, + .shift = 30, + .width = 1, + }, + .rst = { + .reg_off = HHI_HDMI_PLL_CNTL0, + .shift = 29, + .width = 1, + }, + }, + .hw.init = &(struct clk_init_data){ + .name = "hdmi_pll_dco", + .ops = &meson_clk_pll_ro_ops, + .parent_data = &(const struct clk_parent_data) { + .name = "xtal", + }, + .num_parents = 1, + /* + * Display directly handle hdmi pll registers ATM, we need + * NOCACHE to keep our view of the clock as accurate as possible + */ + .flags = CLK_GET_RATE_NOCACHE, + }, +}; +static CLK_DIV_RO(g12a_hdmi_pll_od, HHI_HDMI_PLL_CNTL0, 16, 2, CLK_DIVIDER_POWER_OF_TWO, { &g12a_hdmi_pll_dco }, 1, 0); +static CLK_DIV_RO(g12a_hdmi_pll_od2, HHI_HDMI_PLL_CNTL0, 18, 2, CLK_DIVIDER_POWER_OF_TWO, { &g12a_hdmi_pll_od }, 1, 0); +static CLK_DIV_RO(g12a_hdmi_pll, HHI_HDMI_PLL_CNTL0, 20, 2, CLK_DIVIDER_POWER_OF_TWO, { &g12a_hdmi_pll_od2 }, 1, 0); +static CLK_FIXED_FACTOR(g12a_fclk_div4_div, 1, 4, 0, { &g12a_fixed_pll }, 1, 0); +static CLK_GATE(g12a_fclk_div4, HHI_FIX_PLL_CNTL1, 21, 0, { &g12a_fclk_div4_div }, 1, 0); +static CLK_FIXED_FACTOR(g12a_fclk_div5_div, 1, 5, 0, { &g12a_fixed_pll }, 1, 0); +static CLK_GATE(g12a_fclk_div5, HHI_FIX_PLL_CNTL1, 22, 0, { &g12a_fclk_div5_div }, 1, 0); +static CLK_FIXED_FACTOR(g12a_fclk_div7_div, 1, 7, 0, { &g12a_fixed_pll }, 1, 0); +static CLK_GATE(g12a_fclk_div7, HHI_FIX_PLL_CNTL1, 23, 0, { &g12a_fclk_div7_div }, 1, 0); +static CLK_FIXED_FACTOR(g12a_fclk_div2p5_div, 1, 5, 0, { &g12a_fixed_pll_dco }, 1, 0); +static CLK_GATE(g12a_fclk_div2p5, HHI_FIX_PLL_CNTL1, 25, 0, { &g12a_fclk_div2p5_div }, 1, 0); +static CLK_FIXED_FACTOR(g12a_mpll_50m_div, 1, 80, 0, { &g12a_fixed_pll_dco }, 1, 0); +const static struct clk_parent_data g12a_mpll_50m_parent_table[] = { + { + .name = "xtal", + }, + { .clk = &g12a_mpll_50m_div }, +}; +static CLK_MUX_RO(g12a_mpll_50m, HHI_FIX_PLL_CNTL3, 0x1, 5, 0, 0, g12a_mpll_50m_parent_table, 2, 0); +static CLK_FIXED_FACTOR(g12a_mpll_prediv, 1, 2, 0, { &g12a_fixed_pll_dco }, 1, 0); + +static const struct reg_sequence g12a_mpll0_init_regs[] = { + { .reg = HHI_MPLL_CNTL2, .def = 0x40000033 }, +}; +static struct clk g12a_mpll0_div = { + .data = &(struct meson_clk_mpll_data){ + .sdm = { + .reg_off = HHI_MPLL_CNTL1, + .shift = 0, + .width = 14, + }, + .sdm_en = { + .reg_off = HHI_MPLL_CNTL1, + .shift = 30, + .width = 1, + }, + .n2 = { + .reg_off = HHI_MPLL_CNTL1, + .shift = 20, + .width = 9, + }, + .ssen = { + .reg_off = HHI_MPLL_CNTL1, + .shift = 29, + .width = 1, + }, + /* .lock = &meson_clk_lock, */ + .init_regs = g12a_mpll0_init_regs, + .init_count = ARRAY_SIZE(g12a_mpll0_init_regs), + }, + .hw.init = &(struct clk_init_data){ + .name = "mpll0_div", + .ops = &meson_clk_mpll_ops, + .parent_clks = (const struct clk *[]) { + &g12a_mpll_prediv + }, + .num_parents = 1, + }, +}; +static CLK_GATE(g12a_mpll0, HHI_MPLL_CNTL1, 31, 0, { &g12a_mpll0_div }, 1, 0); +static const struct reg_sequence g12a_mpll1_init_regs[] = { + { .reg = HHI_MPLL_CNTL4, .def = 0x40000033 }, +}; +static struct clk g12a_mpll1_div = { + .data = &(struct meson_clk_mpll_data){ + .sdm = { + .reg_off = HHI_MPLL_CNTL3, + .shift = 0, + .width = 14, + }, + .sdm_en = { + .reg_off = HHI_MPLL_CNTL3, + .shift = 30, + .width = 1, + }, + .n2 = { + .reg_off = HHI_MPLL_CNTL3, + .shift = 20, + .width = 9, + }, + .ssen = { + .reg_off = HHI_MPLL_CNTL3, + .shift = 29, + .width = 1, + }, + /* .lock = &meson_clk_lock, */ + .init_regs = g12a_mpll1_init_regs, + .init_count = ARRAY_SIZE(g12a_mpll1_init_regs), + }, + .hw.init = &(struct clk_init_data){ + .name = "mpll1_div", + .ops = &meson_clk_mpll_ops, + .parent_clks = (const struct clk *[]) { + &g12a_mpll_prediv + }, + .num_parents = 1, + }, +}; +static CLK_GATE(g12a_mpll1, HHI_MPLL_CNTL3, 31, 0, { &g12a_mpll1_div }, 1, 0); +static const struct reg_sequence g12a_mpll2_init_regs[] = { + { .reg = HHI_MPLL_CNTL6, .def = 0x40000033 }, +}; +static struct clk g12a_mpll2_div = { + .data = &(struct meson_clk_mpll_data){ + .sdm = { + .reg_off = HHI_MPLL_CNTL5, + .shift = 0, + .width = 14, + }, + .sdm_en = { + .reg_off = HHI_MPLL_CNTL5, + .shift = 30, + .width = 1, + }, + .n2 = { + .reg_off = HHI_MPLL_CNTL5, + .shift = 20, + .width = 9, + }, + .ssen = { + .reg_off = HHI_MPLL_CNTL5, + .shift = 29, + .width = 1, + }, + /* .lock = &meson_clk_lock, */ + .init_regs = g12a_mpll2_init_regs, + .init_count = ARRAY_SIZE(g12a_mpll2_init_regs), + }, + .hw.init = &(struct clk_init_data){ + .name = "mpll2_div", + .ops = &meson_clk_mpll_ops, + .parent_clks = (const struct clk *[]) { + &g12a_mpll_prediv + }, + .num_parents = 1, + }, +}; +static CLK_GATE(g12a_mpll2, HHI_MPLL_CNTL5, 31, 0, { &g12a_mpll2_div }, 1, 0); +static const struct reg_sequence g12a_mpll3_init_regs[] = { + { .reg = HHI_MPLL_CNTL8, .def = 0x40000033 }, +}; +static struct clk g12a_mpll3_div = { + .data = &(struct meson_clk_mpll_data){ + .sdm = { + .reg_off = HHI_MPLL_CNTL7, + .shift = 0, + .width = 14, + }, + .sdm_en = { + .reg_off = HHI_MPLL_CNTL7, + .shift = 30, + .width = 1, + }, + .n2 = { + .reg_off = HHI_MPLL_CNTL7, + .shift = 20, + .width = 9, + }, + .ssen = { + .reg_off = HHI_MPLL_CNTL7, + .shift = 29, + .width = 1, + }, + /* .lock = &meson_clk_lock, */ + .init_regs = g12a_mpll3_init_regs, + .init_count = ARRAY_SIZE(g12a_mpll3_init_regs), + }, + .hw.init = &(struct clk_init_data){ + .name = "mpll3_div", + .ops = &meson_clk_mpll_ops, + .parent_clks = (const struct clk *[]) { + &g12a_mpll_prediv + }, + .num_parents = 1, + }, +}; +static CLK_GATE(g12a_mpll3, HHI_MPLL_CNTL7, 31, 0, { &g12a_mpll3_div }, 1, 0); + +static uint32_t mux_table_clk81[] = { 0, 2, 3, 4, 5, 6, 7 }; +static const struct clk_parent_data clk81_parent_data[] = { + { + .name = "xtal", + }, + { .clk = &g12a_fclk_div7 }, + { .clk = &g12a_mpll1 }, + { .clk = &g12a_mpll2 }, + { .clk = &g12a_fclk_div4 }, + { .clk = &g12a_fclk_div3 }, + { .clk = &g12a_fclk_div5 }, +}; +static CLK_MUX_RO(g12a_mpeg_clk_sel, HHI_MPEG_CLK_CNTL, 0x7, 12, mux_table_clk81, 0, clk81_parent_data, + ARRAY_SIZE(clk81_parent_data), 0); +static CLK_DIV(g12a_mpeg_clk_div, HHI_MPEG_CLK_CNTL, 0, 7, 0, { &g12a_mpeg_clk_sel }, 1, 0); +static CLK_GATE(g12a_clk81, HHI_MPEG_CLK_CNTL, 7, 0, { &g12a_mpeg_clk_div }, 1, 0); +static const struct clk_parent_data g12a_sd_emmc_clk0_parent_data[] = { + { + .name = "xtal", + }, + { .clk = &g12a_fclk_div2 }, + { .clk = &g12a_fclk_div3 }, + { .clk = &g12a_fclk_div5 }, + { .clk = &g12a_fclk_div7 }, +}; +static CLK_MUX(g12a_sd_emmc_a_clk0_sel, HHI_SD_EMMC_CLK_CNTL, 0x7, 9, 0, 0, g12a_sd_emmc_clk0_parent_data, 5, + CLK_SET_RATE_PARENT); +static CLK_DIV(g12a_sd_emmc_a_clk0_div, HHI_SD_EMMC_CLK_CNTL, 0, 7, 0, { &g12a_sd_emmc_a_clk0_sel }, 1, 0); +static CLK_GATE(g12a_sd_emmc_a_clk0, HHI_SD_EMMC_CLK_CNTL, 7, 0, { &g12a_sd_emmc_a_clk0_div }, 1, 0); +static CLK_MUX(g12a_sd_emmc_b_clk0_sel, HHI_SD_EMMC_CLK_CNTL, 0x7, 25, 0, 0, g12a_sd_emmc_clk0_parent_data, 5, + CLK_SET_RATE_PARENT); +static CLK_DIV(g12a_sd_emmc_b_clk0_div, HHI_SD_EMMC_CLK_CNTL, 16, 7, 0, { &g12a_sd_emmc_b_clk0_sel }, 1, 0); +static CLK_GATE(g12a_sd_emmc_b_clk0, HHI_SD_EMMC_CLK_CNTL, 23, 0, { &g12a_sd_emmc_b_clk0_div }, 1, 0); +static CLK_MUX(g12a_sd_emmc_c_clk0_sel, HHI_NAND_CLK_CNTL, 0x7, 9, 0, 0, g12a_sd_emmc_clk0_parent_data, 5, + CLK_SET_RATE_PARENT); +static CLK_DIV(g12a_sd_emmc_c_clk0_div, HHI_NAND_CLK_CNTL, 0, 7, 0, { &g12a_sd_emmc_c_clk0_sel }, 1, 0); +static CLK_GATE(g12a_sd_emmc_c_clk0, HHI_NAND_CLK_CNTL, 7, 0, { &g12a_sd_emmc_c_clk0_div }, 1, 0); +static struct clk g12a_vid_pll_div = { + .data = &(struct meson_vid_pll_div_data){ + .val = { + .reg_off = HHI_VID_PLL_CLK_DIV, + .shift = 0, + .width = 15, + }, + .sel = { + .reg_off = HHI_VID_PLL_CLK_DIV, + .shift = 16, + .width = 2, + }, + }, + .hw.init = &(struct clk_init_data) { + .name = "vid_pll_div", + .ops = &meson_vid_pll_div_ro_ops, + .parent_clks = (const struct clk *[]) { &g12a_hdmi_pll }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT | CLK_GET_RATE_NOCACHE, + }, +}; +static const struct clk_parent_data g12a_vid_pll_parent_table[] = { + { + .clk = &g12a_vid_pll_div, + }, + { + .clk = &g12a_hdmi_pll, + }, +}; + +static CLK_MUX(g12a_vid_pll_sel, HHI_VID_PLL_CLK_DIV, 0x1, 18, 0, 0, g12a_vid_pll_parent_table, 0, + CLK_SET_RATE_NO_REPARENT | CLK_GET_RATE_NOCACHE); +static CLK_GATE(g12a_vid_pll, HHI_VID_PLL_CLK_DIV, 19, 0, { &g12a_vid_pll_sel }, 1, 0); +const static struct clk_parent_data g12a_vpu_sel_parent_table[] = { + { + .clk = &g12a_fclk_div3, + }, + { + .clk = &g12a_fclk_div4, + }, + { + .clk = &g12a_fclk_div5, + }, + { + .clk = &g12a_fclk_div7, + }, + { + .clk = &g12a_mpll1, + }, + { + .clk = &g12a_vid_pll, + }, + { + .clk = &g12a_hifi_pll, + }, + { + .clk = &g12a_gp0_pll, + }, +}; +static CLK_MUX(g12a_vpu_0_sel, HHI_VPU_CLK_CNTL, 0x7, 9, 0, 0, g12a_vpu_sel_parent_table, 0, CLK_SET_RATE_NO_REPARENT); +static CLK_DIV(g12a_vpu_0_div, HHI_VPU_CLK_CNTL, 0, 7, 0, { &g12a_vpu_0_sel }, 1, 0); +static CLK_GATE(g12a_vpu_0, HHI_VPU_CLK_CNTL, 8, 0, { &g12a_vpu_0_div }, 1, 0); +static CLK_MUX(g12a_vpu_1_sel, HHI_VPU_CLK_CNTL, 0x7, 25, 0, 0, g12a_vpu_sel_parent_table, 0, CLK_SET_RATE_NO_REPARENT); +static CLK_DIV(g12a_vpu_1_div, HHI_VPU_CLK_CNTL, 16, 7, 0, { &g12a_vpu_1_sel }, 1, 0); +static CLK_GATE(g12a_vpu_1, HHI_VPU_CLK_CNTL, 24, 0, { &g12a_vpu_1_div }, 1, 0); +const struct clk_parent_data g12a_vpu_parent_table[] = { + { .clk = &g12a_vpu_0 }, + { .clk = &g12a_vpu_1 }, +}; +static CLK_MUX(g12a_vpu, HHI_VPU_CLK_CNTL, 1, 31, 0, 0, g12a_vpu_parent_table, 2, 0); +static const struct clk_parent_data g12a_vdec_parent_table[] = { + { + .clk = &g12a_fclk_div2p5, + }, + { + .clk = &g12a_fclk_div3, + }, + { + .clk = &g12a_fclk_div4, + }, + { + .clk = &g12a_fclk_div5, + }, + { + .clk = &g12a_fclk_div7, + }, + { + .clk = &g12a_hifi_pll, + }, + { + .clk = &g12a_gp0_pll, + }, +}; + +static CLK_MUX(g12a_vdec_1_sel, HHI_VDEC_CLK_CNTL, 0x7, 9, 0, CLK_MUX_ROUND_CLOSEST, g12a_vdec_parent_table, 7, + CLK_SET_RATE_PARENT); +static CLK_DIV(g12a_vdec_1_div, HHI_VDEC_CLK_CNTL, 0, 7, CLK_DIVIDER_ROUND_CLOSEST, { &g12a_vdec_1_sel }, 1, 0); +static CLK_GATE(g12a_vdec_1, HHI_VDEC_CLK_CNTL, 8, 0, { &g12a_vdec_1_div }, 1, 0); +static CLK_MUX(g12a_vdec_hevcf_sel, HHI_VDEC2_CLK_CNTL, 0x7, 9, 0, CLK_MUX_ROUND_CLOSEST, g12a_vdec_parent_table, 7, + CLK_SET_RATE_PARENT); +static CLK_DIV(g12a_vdec_hevcf_div, HHI_VDEC2_CLK_CNTL, 0, 7, CLK_DIVIDER_ROUND_CLOSEST, { &g12a_vdec_hevcf_sel }, 1, + 0); +static CLK_GATE(g12a_vdec_hevcf, HHI_VDEC2_CLK_CNTL, 8, 0, { &g12a_vdec_hevcf_div }, 1, 0); +static CLK_MUX(g12a_vdec_hevc_sel, HHI_VDEC2_CLK_CNTL, 0x7, 25, 0, CLK_MUX_ROUND_CLOSEST, g12a_vdec_parent_table, 7, + CLK_SET_RATE_PARENT); +static CLK_DIV(g12a_vdec_hevc_div, HHI_VDEC2_CLK_CNTL, 16, 7, CLK_DIVIDER_ROUND_CLOSEST, { &g12a_vdec_hevc_sel }, 1, 0); +static CLK_GATE(g12a_vdec_hevc, HHI_VDEC2_CLK_CNTL, 24, 0, { &g12a_vdec_hevc_div }, 1, 0); +static const struct clk_parent_data g12a_vapb_parent_table[] = { + { + .clk = &g12a_fclk_div4, + }, + { + .clk = &g12a_fclk_div3, + }, + { + .clk = &g12a_fclk_div5, + }, + { + .clk = &g12a_fclk_div7, + }, + { + .clk = &g12a_mpll1, + }, + { + .clk = &g12a_vid_pll, + }, + { + .clk = &g12a_mpll2, + }, + { + .clk = &g12a_fclk_div2p5, + }, +}; +static CLK_MUX(g12a_vapb_0_sel, HHI_VAPBCLK_CNTL, 0x3, 9, 0, 0, g12a_vapb_parent_table, 8, CLK_SET_RATE_NO_REPARENT); +static CLK_DIV(g12a_vapb_0_div, HHI_VAPBCLK_CNTL, 0, 7, 0, { &g12a_vapb_0_sel }, 1, 0); +static CLK_GATE(g12a_vapb_0, HHI_VAPBCLK_CNTL, 8, 0, { &g12a_vapb_0_div }, 1, 0); +static CLK_MUX(g12a_vapb_1_sel, HHI_VAPBCLK_CNTL, 0x3, 25, 0, 0, g12a_vapb_parent_table, 8, CLK_SET_RATE_NO_REPARENT); +static CLK_DIV(g12a_vapb_1_div, HHI_VAPBCLK_CNTL, 16, 7, 0, { &g12a_vapb_1_sel }, 1, 0); +static CLK_GATE(g12a_vapb_1, HHI_VAPBCLK_CNTL, 24, 0, { &g12a_vapb_1_div }, 1, 0); +const struct clk_parent_data g12a_vapb_sel_parent_table[] = { + { .clk = &g12a_vapb_0 }, + { .clk = &g12a_vapb_1 }, +}; +static CLK_MUX(g12a_vapb_sel, HHI_VAPBCLK_CNTL, 1, 31, 0, 0, g12a_vapb_sel_parent_table, 2, 0); +static CLK_GATE(g12a_vapb, HHI_VAPBCLK_CNTL, 30, 0, { &g12a_vapb_sel }, 1, 0); +static const struct clk_parent_data g12a_vclk_parent_table[] = { + { + .clk = &g12a_vid_pll, + }, + { + .clk = &g12a_gp0_pll, + }, + { + .clk = &g12a_hifi_pll, + }, + { + .clk = &g12a_mpll1, + }, + { + .clk = &g12a_fclk_div3, + }, + { + .clk = &g12a_fclk_div4, + }, + { + .clk = &g12a_fclk_div5, + }, + { + .clk = &g12a_fclk_div7, + }, +}; +static CLK_MUX(g12a_vclk_sel, HHI_VID_CLK_CNTL, 0x7, 16, 0, 0, g12a_vclk_parent_table, 8, + CLK_SET_RATE_NO_REPARENT | CLK_GET_RATE_NOCACHE); +static CLK_MUX(g12a_vclk2_sel, HHI_VIID_CLK_CNTL, 0x7, 16, 0, 0, g12a_vclk_parent_table, 8, CLK_SET_RATE_NO_REPARENT); +static CLK_GATE(g12a_vclk_input, HHI_VID_CLK_DIV, 16, 0, { &g12a_vclk_sel }, 1, 0); +static CLK_GATE(g12a_vclk2_input, HHI_VIID_CLK_DIV, 16, 0, { &g12a_vclk2_sel }, 1, 0); +static CLK_DIV(g12a_vclk_div, HHI_VID_CLK_DIV, 0, 8, 0, { &g12a_vclk_input }, 1, 0); +static struct clk g12a_vclk2_div = { + .data = &(struct meson_vclk_div_data){ + .div = { + .reg_off = HHI_VIID_CLK_DIV, + .shift = 0, + .width = 8, + }, + .enable = { + .reg_off = HHI_VIID_CLK_DIV, + .shift = 16, + .width = 1, + }, + .reset = { + .reg_off = HHI_VIID_CLK_DIV, + .shift = 17, + .width = 1, + }, + .flags = CLK_DIVIDER_ROUND_CLOSEST, + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk2_div", + .ops = &meson_vclk_div_ops, + .parent_clks = (const struct clk *[]) { + &g12a_vclk2_input + }, + .num_parents = 1, + .flags = CLK_SET_RATE_GATE, + }, +}; +static CLK_GATE(g12a_vclk, HHI_VID_CLK_CNTL, 19, 0, { &g12a_vclk_div }, 1, 0); +static struct clk g12a_vclk2 = { + .data = &(struct meson_vclk_gate_data){ + .enable = { + .reg_off = HHI_VIID_CLK_CNTL, + .shift = 19, + .width = 1, + }, + .reset = { + .reg_off = HHI_VIID_CLK_CNTL, + .shift = 15, + .width = 1, + }, + }, + .hw.init = &(struct clk_init_data) { + .name = "vclk2", + .ops = &meson_vclk_gate_ops, + .parent_clks = (const struct clk *[]) { &g12a_vclk2_div }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; +static CLK_GATE(g12a_vclk_div1, HHI_VID_CLK_CNTL, 0, 0, { &g12a_vclk }, 1, 0); +static CLK_GATE(g12a_vclk_div2_en, HHI_VID_CLK_CNTL, 1, 0, { &g12a_vclk }, 1, 0); +static CLK_GATE(g12a_vclk_div4_en, HHI_VID_CLK_CNTL, 2, 0, { &g12a_vclk }, 1, 0); +static CLK_GATE(g12a_vclk_div6_en, HHI_VID_CLK_CNTL, 3, 0, { &g12a_vclk }, 1, 0); +static CLK_GATE(g12a_vclk_div12_en, HHI_VID_CLK_CNTL, 4, 0, { &g12a_vclk }, 1, 0); +static CLK_GATE(g12a_vclk2_div1, HHI_VIID_CLK_CNTL, 0, 0, { &g12a_vclk2 }, 1, 0); +static CLK_GATE(g12a_vclk2_div2_en, HHI_VIID_CLK_CNTL, 1, 0, { &g12a_vclk2 }, 1, 0); +static CLK_GATE(g12a_vclk2_div4_en, HHI_VIID_CLK_CNTL, 2, 0, { &g12a_vclk2 }, 1, 0); +static CLK_GATE(g12a_vclk2_div6_en, HHI_VIID_CLK_CNTL, 3, 0, { &g12a_vclk2 }, 1, 0); +static CLK_GATE(g12a_vclk2_div12_en, HHI_VIID_CLK_CNTL, 4, 0, { &g12a_vclk2 }, 1, 0); +static CLK_FIXED_FACTOR(g12a_vclk_div2, 1, 2, 0, { &g12a_vclk_div2_en }, 1, 0); +static CLK_FIXED_FACTOR(g12a_vclk_div4, 1, 4, 0, { &g12a_vclk_div4_en }, 1, 0); +static CLK_FIXED_FACTOR(g12a_vclk_div6, 1, 6, 0, { &g12a_vclk_div6_en }, 1, 0); +static CLK_FIXED_FACTOR(g12a_vclk_div12, 1, 12, 0, { &g12a_vclk_div12_en }, 1, 0); +static CLK_FIXED_FACTOR(g12a_vclk2_div2, 1, 2, 0, { &g12a_vclk2_div2_en }, 1, 0); +static CLK_FIXED_FACTOR(g12a_vclk2_div4, 1, 4, 0, { &g12a_vclk2_div4_en }, 1, 0); +static CLK_FIXED_FACTOR(g12a_vclk2_div6, 1, 6, 0, { &g12a_vclk2_div6_en }, 1, 0); +static CLK_FIXED_FACTOR(g12a_vclk2_div12, 1, 12, 0, { &g12a_vclk2_div12_en }, 1, 0); +static uint32_t mux_table_cts_sel[] = { 0, 1, 2, 3, 4, 8, 9, 10, 11, 12 }; +static const struct clk_parent_data g12a_cts_parent_table[] = { + { + .clk = &g12a_vclk_div1, + }, + { + .clk = &g12a_vclk_div2, + }, + { + .clk = &g12a_vclk_div4, + }, + { + .clk = &g12a_vclk_div6, + }, + { + .clk = &g12a_vclk_div12, + }, + { + .clk = &g12a_vclk2_div1, + }, + { + .clk = &g12a_vclk2_div2, + }, + { + .clk = &g12a_vclk2_div4, + }, + { + .clk = &g12a_vclk2_div6, + }, + { + .clk = &g12a_vclk2_div12, + }, +}; +static CLK_MUX(g12a_cts_enci_sel, HHI_VID_CLK_DIV, 0xf, 28, mux_table_cts_sel, 0, g12a_cts_parent_table, + ARRAY_SIZE(g12a_cts_parent_table), CLK_SET_RATE_NO_REPARENT | CLK_GET_RATE_NOCACHE); +static CLK_MUX(g12a_cts_encp_sel, HHI_VID_CLK_DIV, 0xf, 20, mux_table_cts_sel, 0, g12a_cts_parent_table, + ARRAY_SIZE(g12a_cts_parent_table), CLK_SET_RATE_NO_REPARENT | CLK_GET_RATE_NOCACHE); +static CLK_MUX(g12a_cts_encl_sel, HHI_VIID_CLK_DIV, 0xf, 12, mux_table_cts_sel, 0, g12a_cts_parent_table, + ARRAY_SIZE(g12a_cts_parent_table), CLK_SET_RATE_PARENT | CLK_SET_RATE_NO_REPARENT); +static CLK_MUX(g12a_cts_vdac_sel, HHI_VIID_CLK_DIV, 0xf, 28, mux_table_cts_sel, 0, g12a_cts_parent_table, + ARRAY_SIZE(g12a_cts_parent_table), CLK_SET_RATE_NO_REPARENT | CLK_GET_RATE_NOCACHE); +static uint32_t mux_table_hdmi_tx_sel[] = { 0, 1, 2, 3, 4, 8, 9, 10, 11, 12 }; +static const struct clk_parent_data g12a_cts_hdmi_tx_parent_table[] = { + { + .clk = &g12a_vclk_div1, + }, + { + .clk = &g12a_vclk_div2, + }, + { + .clk = &g12a_vclk_div4, + }, + { + .clk = &g12a_vclk_div6, + }, + { + .clk = &g12a_vclk_div12, + }, + { + .clk = &g12a_vclk2_div1, + }, + { + .clk = &g12a_vclk2_div2, + }, + { + .clk = &g12a_vclk2_div4, + }, + { + .clk = &g12a_vclk2_div6, + }, + { + .clk = &g12a_vclk2_div12, + }, +}; +static CLK_MUX(g12a_hdmi_tx_sel, HHI_HDMI_CLK_CNTL, 0xf, 16, mux_table_hdmi_tx_sel, 0, g12a_cts_hdmi_tx_parent_table, + ARRAY_SIZE(g12a_cts_hdmi_tx_parent_table), CLK_SET_RATE_NO_REPARENT | CLK_GET_RATE_NOCACHE); +static CLK_GATE(g12a_cts_enci, HHI_VID_CLK_CNTL2, 0, 0, { &g12a_cts_enci_sel }, 1, 0); +static CLK_GATE(g12a_cts_encp, HHI_VID_CLK_CNTL2, 2, 0, { &g12a_cts_encp_sel }, 1, 0); +static CLK_GATE(g12a_cts_encl, HHI_VID_CLK_CNTL2, 3, 0, { &g12a_cts_encl_sel }, 1, 0); +static CLK_GATE(g12a_cts_vdac, HHI_VID_CLK_CNTL2, 4, 0, { &g12a_cts_vdac_sel }, 1, 0); +static CLK_GATE(g12a_hdmi_tx, HHI_VID_CLK_CNTL2, 5, 0, { &g12a_hdmi_tx_sel }, 1, 0); +static const struct clk_parent_data g12a_mipi_dsi_pxclk_parent_table[] = { + { + .clk = &g12a_vid_pll, + }, + { + .clk = &g12a_gp0_pll, + }, + { + .clk = &g12a_hifi_pll, + }, + { + .clk = &g12a_mpll1, + }, + { + .clk = &g12a_fclk_div2, + }, + { + .clk = &g12a_fclk_div2p5, + }, + { + .clk = &g12a_fclk_div3, + }, + { + .clk = &g12a_fclk_div7, + }, +}; +static CLK_MUX(g12a_mipi_dsi_pxclk_sel, HHI_MIPIDSI_PHY_CLK_CNTL, 0x7, 12, 0, CLK_MUX_ROUND_CLOSEST, + g12a_mipi_dsi_pxclk_parent_table, ARRAY_SIZE(g12a_mipi_dsi_pxclk_parent_table), + CLK_SET_RATE_NO_REPARENT | CLK_SET_RATE_PARENT); +static CLK_DIV(g12a_mipi_dsi_pxclk_div, HHI_MIPIDSI_PHY_CLK_CNTL, 0, 7, 0, { &g12a_mipi_dsi_pxclk_sel }, 1, 0); +static CLK_GATE(g12a_mipi_dsi_pxclk, HHI_MIPIDSI_PHY_CLK_CNTL, 8, 0, { &g12a_mipi_dsi_pxclk_div }, 1, 0); +static const struct clk_parent_data g12a_hdmi_parent_table[] = { + { + .name = "xtal", + }, + { .clk = &g12a_fclk_div4 }, + { .clk = &g12a_fclk_div3 }, + { .clk = &g12a_fclk_div5 }, +}; +static CLK_MUX(g12a_hdmi_sel, HHI_HDMI_CLK_CNTL, 0x3, 9, 0, CLK_MUX_ROUND_CLOSEST, g12a_hdmi_parent_table, + ARRAY_SIZE(g12a_hdmi_parent_table), CLK_SET_RATE_NO_REPARENT | CLK_GET_RATE_NOCACHE); +static CLK_DIV(g12a_hdmi_div, HHI_HDMI_CLK_CNTL, 0, 7, 0, { &g12a_hdmi_sel }, 1, 0); +static CLK_GATE(g12a_hdmi, HHI_HDMI_CLK_CNTL, 8, 0, { &g12a_hdmi_div }, 1, 0); +static const struct clk_parent_data g12a_mali_0_1_parent_data[] = { + { + .name = "xtal", + }, + { .clk = &g12a_gp0_pll }, + { .clk = &g12a_hifi_pll }, + { .clk = &g12a_fclk_div2p5 }, + { .clk = &g12a_fclk_div3 }, + { .clk = &g12a_fclk_div4 }, + { .clk = &g12a_fclk_div5 }, + { .clk = &g12a_fclk_div7 }, +}; +static CLK_MUX(g12a_mali_0_sel, HHI_MALI_CLK_CNTL, 0x7, 9, 0, 0, g12a_mali_0_1_parent_data, 8, 0); +static CLK_DIV(g12a_mali_0_div, HHI_MALI_CLK_CNTL, 0, 7, 0, { &g12a_mali_0_sel }, 1, 0); +static CLK_GATE(g12a_mali_0, HHI_MALI_CLK_CNTL, 8, 0, { &g12a_mali_0_div }, 1, 0); +static CLK_MUX(g12a_mali_1_sel, HHI_MALI_CLK_CNTL, 0x7, 25, 0, 0, g12a_mali_0_1_parent_data, 8, 0); +static CLK_DIV(g12a_mali_1_div, HHI_MALI_CLK_CNTL, 16, 7, 0, { &g12a_mali_1_sel }, 1, 0); +static CLK_GATE(g12a_mali_1, HHI_MALI_CLK_CNTL, 24, 0, { &g12a_mali_1_div }, 1, 0); +static const struct clk_parent_data g12a_mali_parent_table[] = { + { + .clk = &g12a_mali_0, + }, + { + .clk = &g12a_mali_1, + }, +}; +static CLK_MUX(g12a_mali, HHI_MALI_CLK_CNTL, 1, 31, 0, 0, g12a_mali_parent_table, 2, CLK_SET_RATE_PARENT); +static CLK_DIV_RO(g12a_ts_div, HHI_TS_CLK_CNTL, 0, 8, 0, { &g12a_ts_div }, 1, 0); +static CLK_GATE(g12a_ts, HHI_TS_CLK_CNTL, 8, 0, { &g12a_ts_div }, 1, 0); +static const struct clk_parent_data spicc_sclk_parent_data[] = { + { + .name = "xtal", + }, + { .clk = &g12a_clk81 }, + { .clk = &g12a_fclk_div4 }, + { .clk = &g12a_fclk_div3 }, + { .clk = &g12a_fclk_div5 }, + { .clk = &g12a_fclk_div7 }, +}; + +static CLK_MUX(g12a_spicc0_sclk_sel, HHI_SPICC_CLK_CNTL, 7, 7, 0, 0, spicc_sclk_parent_data, 6, 0); +static CLK_DIV(g12a_spicc0_sclk_div, HHI_SPICC_CLK_CNTL, 0, 6, 0, { &g12a_spicc0_sclk_sel }, 1, 0); +static CLK_GATE(g12a_spicc0_sclk, HHI_SPICC_CLK_CNTL, 6, 0, { &g12a_spicc0_sclk_div }, 1, 0); +static CLK_MUX(g12a_spicc1_sclk_sel, HHI_SPICC_CLK_CNTL, 7, 23, 0, 0, spicc_sclk_parent_data, 6, 0); +static CLK_DIV(g12a_spicc1_sclk_div, HHI_SPICC_CLK_CNTL, 16, 6, 0, { &g12a_spicc1_sclk_sel }, 1, 0); +static CLK_GATE(g12a_spicc1_sclk, HHI_SPICC_CLK_CNTL, 22, 0, { &g12a_spicc1_sclk_div }, 1, 0); +static const struct clk_parent_data nna_clk_parent_data[] = { + { + .name = "xtal", + }, + { + .clk = &g12a_gp0_pll, + }, + { + .clk = &g12a_hifi_pll, + }, + { + .clk = &g12a_fclk_div2p5, + }, + { + .clk = &g12a_fclk_div3, + }, + { + .clk = &g12a_fclk_div4, + }, + { + .clk = &g12a_fclk_div5, + }, + { .clk = &g12a_fclk_div7 }, +}; +static CLK_MUX(sm1_nna_axi_clk_sel, HHI_NNA_CLK_CNTL, 7, 9, 0, 0, nna_clk_parent_data, 8, 0); +static CLK_DIV(sm1_nna_axi_clk_div, HHI_NNA_CLK_CNTL, 0, 7, 0, { &sm1_nna_axi_clk_sel }, 1, 0); +static CLK_GATE(sm1_nna_axi_clk, HHI_NNA_CLK_CNTL, 8, 0, { &sm1_nna_axi_clk_div }, 1, 0); +static CLK_MUX(sm1_nna_core_clk_sel, HHI_NNA_CLK_CNTL, 7, 25, 0, 0, nna_clk_parent_data, 8, 0); +static CLK_DIV(sm1_nna_core_clk_div, HHI_NNA_CLK_CNTL, 16, 7, 0, { &sm1_nna_core_clk_sel }, 1, 0); +static CLK_GATE(sm1_nna_core_clk, HHI_NNA_CLK_CNTL, 24, 0, { &sm1_nna_core_clk_div }, 1, 0); + +/* Everything Else (EE) domain gates */ +static MESON_CLK81_GATE(g12a_ddr, HHI_GCLK_MPEG0, 0); +static MESON_CLK81_GATE(g12a_dos, HHI_GCLK_MPEG0, 1); +static MESON_CLK81_GATE(g12a_audio_locker, HHI_GCLK_MPEG0, 2); +static MESON_CLK81_GATE(g12a_mipi_dsi_host, HHI_GCLK_MPEG0, 3); +static MESON_CLK81_GATE(g12a_eth_phy, HHI_GCLK_MPEG0, 4); +static MESON_CLK81_GATE(g12a_isa, HHI_GCLK_MPEG0, 5); +static MESON_CLK81_GATE(g12a_pl301, HHI_GCLK_MPEG0, 6); +static MESON_CLK81_GATE(g12a_periphs, HHI_GCLK_MPEG0, 7); +static MESON_CLK81_GATE(g12a_spicc_0, HHI_GCLK_MPEG0, 8); +static MESON_CLK81_GATE(g12a_i2c, HHI_GCLK_MPEG0, 9); +static MESON_CLK81_GATE(g12a_sana, HHI_GCLK_MPEG0, 10); +static MESON_CLK81_GATE(g12a_sd, HHI_GCLK_MPEG0, 11); +static MESON_CLK81_GATE(g12a_rng0, HHI_GCLK_MPEG0, 12); +static MESON_CLK81_GATE(g12a_uart0, HHI_GCLK_MPEG0, 13); +static MESON_CLK81_GATE(g12a_spicc_1, HHI_GCLK_MPEG0, 14); +static MESON_CLK81_GATE(g12a_hiu_reg, HHI_GCLK_MPEG0, 19); +static MESON_CLK81_GATE(g12a_mipi_dsi_phy, HHI_GCLK_MPEG0, 20); +static MESON_CLK81_GATE(g12a_assist_misc, HHI_GCLK_MPEG0, 23); +static MESON_CLK81_GATE(g12a_emmc_a, HHI_GCLK_MPEG0, 4); +static MESON_CLK81_GATE(g12a_emmc_b, HHI_GCLK_MPEG0, 25); +static MESON_CLK81_GATE(g12a_emmc_c, HHI_GCLK_MPEG0, 26); +static MESON_CLK81_GATE(g12a_audio_codec, HHI_GCLK_MPEG0, 28); + +static MESON_CLK81_GATE(g12a_audio, HHI_GCLK_MPEG1, 0); +static MESON_CLK81_GATE(g12a_eth_core, HHI_GCLK_MPEG1, 3); +static MESON_CLK81_GATE(g12a_demux, HHI_GCLK_MPEG1, 4); +static MESON_CLK81_GATE(g12a_audio_ififo, HHI_GCLK_MPEG1, 11); +static MESON_CLK81_GATE(g12a_adc, HHI_GCLK_MPEG1, 13); +static MESON_CLK81_GATE(g12a_uart1, HHI_GCLK_MPEG1, 16); +static MESON_CLK81_GATE(g12a_g2d, HHI_GCLK_MPEG1, 20); +static MESON_CLK81_GATE(g12a_reset, HHI_GCLK_MPEG1, 23); +static MESON_CLK81_GATE(g12a_pcie_comb, HHI_GCLK_MPEG1, 24); +static MESON_CLK81_GATE(g12a_parser, HHI_GCLK_MPEG1, 25); +static MESON_CLK81_GATE(g12a_usb_general, HHI_GCLK_MPEG1, 26); +static MESON_CLK81_GATE(g12a_pcie_phy, HHI_GCLK_MPEG1, 27); +static MESON_CLK81_GATE(g12a_ahb_arb0, HHI_GCLK_MPEG1, 29); + +static MESON_CLK81_GATE(g12a_ahb_data_bus, HHI_GCLK_MPEG2, 1); +static MESON_CLK81_GATE(g12a_ahb_ctrl_bus, HHI_GCLK_MPEG2, 2); +static MESON_CLK81_GATE(g12a_htx_hdcp22, HHI_GCLK_MPEG2, 3); +static MESON_CLK81_GATE(g12a_htx_pclk, HHI_GCLK_MPEG2, 4); +static MESON_CLK81_GATE(g12a_bt656, HHI_GCLK_MPEG2, 6); +static MESON_CLK81_GATE(g12a_usb1_to_ddr, HHI_GCLK_MPEG2, 8); +static MESON_CLK81_GATE(g12a_mmc_pclk, HHI_GCLK_MPEG2, 11); +static MESON_CLK81_GATE(g12a_uart2, HHI_GCLK_MPEG2, 15); +static MESON_CLK81_GATE(g12a_vpu_intr, HHI_GCLK_MPEG2, 25); +static MESON_CLK81_GATE(g12a_gic, HHI_GCLK_MPEG2, 30); + +static MESON_CLK81_GATE(g12a_vclk2_venci0, HHI_GCLK_OTHER, 1); +static MESON_CLK81_GATE(g12a_vclk2_venci1, HHI_GCLK_OTHER, 2); +static MESON_CLK81_GATE(g12a_vclk2_vencp0, HHI_GCLK_OTHER, 3); +static MESON_CLK81_GATE(g12a_vclk2_vencp1, HHI_GCLK_OTHER, 4); +static MESON_CLK81_GATE(g12a_vclk2_venct0, HHI_GCLK_OTHER, 5); +static MESON_CLK81_GATE(g12a_vclk2_venct1, HHI_GCLK_OTHER, 6); +static MESON_CLK81_GATE(g12a_vclk2_other, HHI_GCLK_OTHER, 7); +static MESON_CLK81_GATE(g12a_vclk2_enci, HHI_GCLK_OTHER, 8); +static MESON_CLK81_GATE(g12a_vclk2_encp, HHI_GCLK_OTHER, 9); +static MESON_CLK81_GATE(g12a_dac_clk, HHI_GCLK_OTHER, 10); +static MESON_CLK81_GATE(g12a_aoclk_gate, HHI_GCLK_OTHER, 14); +static MESON_CLK81_GATE(g12a_iec958_gate, HHI_GCLK_OTHER, 16); +static MESON_CLK81_GATE(g12a_enc480p, HHI_GCLK_OTHER, 20); +static MESON_CLK81_GATE(g12a_rng1, HHI_GCLK_OTHER, 21); +static MESON_CLK81_GATE(g12a_vclk2_enct, HHI_GCLK_OTHER, 22); +static MESON_CLK81_GATE(g12a_vclk2_encl, HHI_GCLK_OTHER, 23); +static MESON_CLK81_GATE(g12a_vclk2_venclmmc, HHI_GCLK_OTHER, 24); +static MESON_CLK81_GATE(g12a_vclk2_vencl, HHI_GCLK_OTHER, 25); +static MESON_CLK81_GATE(g12a_vclk2_other1, HHI_GCLK_OTHER, 26); + +static MESON_CLK81_GATE_RO(g12a_dma, HHI_GCLK_OTHER2, 0); +static MESON_CLK81_GATE_RO(g12a_efuse, HHI_GCLK_OTHER2, 1); +static MESON_CLK81_GATE_RO(g12a_rom_boot, HHI_GCLK_OTHER2, 2); +static MESON_CLK81_GATE_RO(g12a_reset_sec, HHI_GCLK_OTHER2, 3); +static MESON_CLK81_GATE_RO(g12a_sec_ahb_apb3, HHI_GCLK_OTHER2, 4); + +static struct clk *sm1_clks[] = { + [CLKID_SYS_PLL] = &g12a_sys_pll, + [CLKID_FIXED_PLL] = &g12a_fixed_pll, + [CLKID_FCLK_DIV2] = &g12a_fclk_div2, + [CLKID_FCLK_DIV3] = &g12a_fclk_div3, + [CLKID_FCLK_DIV4] = &g12a_fclk_div4, + [CLKID_FCLK_DIV5] = &g12a_fclk_div5, + [CLKID_FCLK_DIV7] = &g12a_fclk_div7, + [CLKID_FCLK_DIV2P5] = &g12a_fclk_div2p5, + [CLKID_GP0_PLL] = &g12a_gp0_pll, + [CLKID_MPEG_SEL] = &g12a_mpeg_clk_sel, + [CLKID_MPEG_DIV] = &g12a_mpeg_clk_div, + [CLKID_CLK81] = &g12a_clk81, + [CLKID_MPLL0] = &g12a_mpll0, + [CLKID_MPLL1] = &g12a_mpll1, + [CLKID_MPLL2] = &g12a_mpll2, + [CLKID_MPLL3] = &g12a_mpll3, + [CLKID_DDR] = &g12a_ddr, + [CLKID_DOS] = &g12a_dos, + [CLKID_AUDIO_LOCKER] = &g12a_audio_locker, + [CLKID_MIPI_DSI_HOST] = &g12a_mipi_dsi_host, + [CLKID_ETH_PHY] = &g12a_eth_phy, + [CLKID_ISA] = &g12a_isa, + [CLKID_PL301] = &g12a_pl301, + [CLKID_PERIPHS] = &g12a_periphs, + [CLKID_SPICC0] = &g12a_spicc_0, + [CLKID_I2C] = &g12a_i2c, + [CLKID_SANA] = &g12a_sana, + [CLKID_SD] = &g12a_sd, + [CLKID_RNG0] = &g12a_rng0, + [CLKID_UART0] = &g12a_uart0, + [CLKID_SPICC1] = &g12a_spicc_1, + [CLKID_HIU_IFACE] = &g12a_hiu_reg, + [CLKID_MIPI_DSI_PHY] = &g12a_mipi_dsi_phy, + [CLKID_ASSIST_MISC] = &g12a_assist_misc, + [CLKID_SD_EMMC_A] = &g12a_emmc_a, + [CLKID_SD_EMMC_B] = &g12a_emmc_b, + [CLKID_SD_EMMC_C] = &g12a_emmc_c, + [CLKID_AUDIO_CODEC] = &g12a_audio_codec, + [CLKID_AUDIO] = &g12a_audio, + [CLKID_ETH] = &g12a_eth_core, + [CLKID_DEMUX] = &g12a_demux, + [CLKID_AUDIO_IFIFO] = &g12a_audio_ififo, + [CLKID_ADC] = &g12a_adc, + [CLKID_UART1] = &g12a_uart1, + [CLKID_G2D] = &g12a_g2d, + [CLKID_RESET] = &g12a_reset, + [CLKID_PCIE_COMB] = &g12a_pcie_comb, + [CLKID_PARSER] = &g12a_parser, + [CLKID_USB] = &g12a_usb_general, + [CLKID_PCIE_PHY] = &g12a_pcie_phy, + [CLKID_AHB_ARB0] = &g12a_ahb_arb0, + [CLKID_AHB_DATA_BUS] = &g12a_ahb_data_bus, + [CLKID_AHB_CTRL_BUS] = &g12a_ahb_ctrl_bus, + [CLKID_HTX_HDCP22] = &g12a_htx_hdcp22, + [CLKID_HTX_PCLK] = &g12a_htx_pclk, + [CLKID_BT656] = &g12a_bt656, + [CLKID_USB1_DDR_BRIDGE] = &g12a_usb1_to_ddr, + [CLKID_MMC_PCLK] = &g12a_mmc_pclk, + [CLKID_UART2] = &g12a_uart2, + [CLKID_VPU_INTR] = &g12a_vpu_intr, + [CLKID_GIC] = &g12a_gic, + [CLKID_SD_EMMC_A_CLK0_SEL] = &g12a_sd_emmc_a_clk0_sel, + [CLKID_SD_EMMC_A_CLK0_DIV] = &g12a_sd_emmc_a_clk0_div, + [CLKID_SD_EMMC_A_CLK0] = &g12a_sd_emmc_a_clk0, + [CLKID_SD_EMMC_B_CLK0_SEL] = &g12a_sd_emmc_b_clk0_sel, + [CLKID_SD_EMMC_B_CLK0_DIV] = &g12a_sd_emmc_b_clk0_div, + [CLKID_SD_EMMC_B_CLK0] = &g12a_sd_emmc_b_clk0, + [CLKID_SD_EMMC_C_CLK0_SEL] = &g12a_sd_emmc_c_clk0_sel, + [CLKID_SD_EMMC_C_CLK0_DIV] = &g12a_sd_emmc_c_clk0_div, + [CLKID_SD_EMMC_C_CLK0] = &g12a_sd_emmc_c_clk0, + [CLKID_MPLL0_DIV] = &g12a_mpll0_div, + [CLKID_MPLL1_DIV] = &g12a_mpll1_div, + [CLKID_MPLL2_DIV] = &g12a_mpll2_div, + [CLKID_MPLL3_DIV] = &g12a_mpll3_div, + [CLKID_FCLK_DIV2_DIV] = &g12a_fclk_div2_div, + [CLKID_FCLK_DIV3_DIV] = &g12a_fclk_div3_div, + [CLKID_FCLK_DIV4_DIV] = &g12a_fclk_div4_div, + [CLKID_FCLK_DIV5_DIV] = &g12a_fclk_div5_div, + [CLKID_FCLK_DIV7_DIV] = &g12a_fclk_div7_div, + [CLKID_FCLK_DIV2P5_DIV] = &g12a_fclk_div2p5_div, + [CLKID_HIFI_PLL] = &g12a_hifi_pll, + [CLKID_VCLK2_VENCI0] = &g12a_vclk2_venci0, + [CLKID_VCLK2_VENCI1] = &g12a_vclk2_venci1, + [CLKID_VCLK2_VENCP0] = &g12a_vclk2_vencp0, + [CLKID_VCLK2_VENCP1] = &g12a_vclk2_vencp1, + [CLKID_VCLK2_VENCT0] = &g12a_vclk2_venct0, + [CLKID_VCLK2_VENCT1] = &g12a_vclk2_venct1, + [CLKID_VCLK2_OTHER] = &g12a_vclk2_other, + [CLKID_VCLK2_ENCI] = &g12a_vclk2_enci, + [CLKID_VCLK2_ENCP] = &g12a_vclk2_encp, + [CLKID_DAC_CLK] = &g12a_dac_clk, + [CLKID_AOCLK] = &g12a_aoclk_gate, + [CLKID_IEC958] = &g12a_iec958_gate, + [CLKID_ENC480P] = &g12a_enc480p, + [CLKID_RNG1] = &g12a_rng1, + [CLKID_VCLK2_ENCT] = &g12a_vclk2_enct, + [CLKID_VCLK2_ENCL] = &g12a_vclk2_encl, + [CLKID_VCLK2_VENCLMMC] = &g12a_vclk2_venclmmc, + [CLKID_VCLK2_VENCL] = &g12a_vclk2_vencl, + [CLKID_VCLK2_OTHER1] = &g12a_vclk2_other1, + [CLKID_FIXED_PLL_DCO] = &g12a_fixed_pll_dco, + [CLKID_SYS_PLL_DCO] = &g12a_sys_pll_dco, + [CLKID_GP0_PLL_DCO] = &g12a_gp0_pll_dco, + [CLKID_HIFI_PLL_DCO] = &g12a_hifi_pll_dco, + [CLKID_DMA] = &g12a_dma, + [CLKID_EFUSE] = &g12a_efuse, + [CLKID_ROM_BOOT] = &g12a_rom_boot, + [CLKID_RESET_SEC] = &g12a_reset_sec, + [CLKID_SEC_AHB_APB3] = &g12a_sec_ahb_apb3, + [CLKID_MPLL_PREDIV] = &g12a_mpll_prediv, + [CLKID_VPU_0_SEL] = &g12a_vpu_0_sel, + [CLKID_VPU_0_DIV] = &g12a_vpu_0_div, + [CLKID_VPU_0] = &g12a_vpu_0, + [CLKID_VPU_1_SEL] = &g12a_vpu_1_sel, + [CLKID_VPU_1_DIV] = &g12a_vpu_1_div, + [CLKID_VPU_1] = &g12a_vpu_1, + [CLKID_VPU] = &g12a_vpu, + [CLKID_VAPB_0_SEL] = &g12a_vapb_0_sel, + [CLKID_VAPB_0_DIV] = &g12a_vapb_0_div, + [CLKID_VAPB_0] = &g12a_vapb_0, + [CLKID_VAPB_1_SEL] = &g12a_vapb_1_sel, + [CLKID_VAPB_1_DIV] = &g12a_vapb_1_div, + [CLKID_VAPB_1] = &g12a_vapb_1, + [CLKID_VAPB_SEL] = &g12a_vapb_sel, + [CLKID_VAPB] = &g12a_vapb, + [CLKID_HDMI_PLL_DCO] = &g12a_hdmi_pll_dco, + [CLKID_HDMI_PLL_OD] = &g12a_hdmi_pll_od, + [CLKID_HDMI_PLL_OD2] = &g12a_hdmi_pll_od2, + [CLKID_HDMI_PLL] = &g12a_hdmi_pll, + [CLKID_VID_PLL] = &g12a_vid_pll_div, + [CLKID_VID_PLL_SEL] = &g12a_vid_pll_sel, + [CLKID_VID_PLL_DIV] = &g12a_vid_pll, + [CLKID_VCLK_SEL] = &g12a_vclk_sel, + [CLKID_VCLK2_SEL] = &g12a_vclk2_sel, + [CLKID_VCLK_INPUT] = &g12a_vclk_input, + [CLKID_VCLK2_INPUT] = &g12a_vclk2_input, + [CLKID_VCLK_DIV] = &g12a_vclk_div, + [CLKID_VCLK2_DIV] = &g12a_vclk2_div, + [CLKID_VCLK] = &g12a_vclk, + [CLKID_VCLK2] = &g12a_vclk2, + [CLKID_VCLK_DIV1] = &g12a_vclk_div1, + [CLKID_VCLK_DIV2_EN] = &g12a_vclk_div2_en, + [CLKID_VCLK_DIV4_EN] = &g12a_vclk_div4_en, + [CLKID_VCLK_DIV6_EN] = &g12a_vclk_div6_en, + [CLKID_VCLK_DIV12_EN] = &g12a_vclk_div12_en, + [CLKID_VCLK2_DIV1] = &g12a_vclk2_div1, + [CLKID_VCLK2_DIV2_EN] = &g12a_vclk2_div2_en, + [CLKID_VCLK2_DIV4_EN] = &g12a_vclk2_div4_en, + [CLKID_VCLK2_DIV6_EN] = &g12a_vclk2_div6_en, + [CLKID_VCLK2_DIV12_EN] = &g12a_vclk2_div12_en, + [CLKID_VCLK_DIV2] = &g12a_vclk_div2, + [CLKID_VCLK_DIV4] = &g12a_vclk_div4, + [CLKID_VCLK_DIV6] = &g12a_vclk_div6, + [CLKID_VCLK_DIV12] = &g12a_vclk_div12, + [CLKID_VCLK2_DIV2] = &g12a_vclk2_div2, + [CLKID_VCLK2_DIV4] = &g12a_vclk2_div4, + [CLKID_VCLK2_DIV6] = &g12a_vclk2_div6, + [CLKID_VCLK2_DIV12] = &g12a_vclk2_div12, + [CLKID_CTS_ENCI_SEL] = &g12a_cts_enci_sel, + [CLKID_CTS_ENCP_SEL] = &g12a_cts_encp_sel, + [CLKID_CTS_ENCL_SEL] = &g12a_cts_encl_sel, + [CLKID_CTS_VDAC_SEL] = &g12a_cts_vdac_sel, + [CLKID_HDMI_TX_SEL] = &g12a_hdmi_tx_sel, + [CLKID_CTS_ENCI] = &g12a_cts_enci, + [CLKID_CTS_ENCP] = &g12a_cts_encp, + [CLKID_CTS_ENCL] = &g12a_cts_encl, + [CLKID_CTS_VDAC] = &g12a_cts_vdac, + [CLKID_HDMI_TX] = &g12a_hdmi_tx, + [CLKID_HDMI_SEL] = &g12a_hdmi_sel, + [CLKID_HDMI_DIV] = &g12a_hdmi_div, + [CLKID_HDMI] = &g12a_hdmi, + [CLKID_MALI_0_SEL] = &g12a_mali_0_sel, + [CLKID_MALI_0_DIV] = &g12a_mali_0_div, + [CLKID_MALI_0] = &g12a_mali_0, + [CLKID_MALI_1_SEL] = &g12a_mali_1_sel, + [CLKID_MALI_1_DIV] = &g12a_mali_1_div, + [CLKID_MALI_1] = &g12a_mali_1, + [CLKID_MALI] = &g12a_mali, + [CLKID_MPLL_50M_DIV] = &g12a_mpll_50m_div, + [CLKID_MPLL_50M] = &g12a_mpll_50m, + [CLKID_SYS_PLL_DIV16_EN] = &g12a_sys_pll_div16_en, + [CLKID_SYS_PLL_DIV16] = &g12a_sys_pll_div16, + [CLKID_CPU_CLK_DYN0_SEL] = &g12a_cpu_clk_premux0, + [CLKID_CPU_CLK_DYN0_DIV] = &g12a_cpu_clk_mux0_div, + [CLKID_CPU_CLK_DYN0] = &g12a_cpu_clk_postmux0, + [CLKID_CPU_CLK_DYN1_SEL] = &g12a_cpu_clk_premux1, + [CLKID_CPU_CLK_DYN1_DIV] = &g12a_cpu_clk_mux1_div, + [CLKID_CPU_CLK_DYN1] = &g12a_cpu_clk_postmux1, + [CLKID_CPU_CLK_DYN] = &g12a_cpu_clk_dyn, + [CLKID_CPU_CLK] = &g12a_cpu_clk, + [CLKID_CPU_CLK_DIV16_EN] = &g12a_cpu_clk_div16_en, + [CLKID_CPU_CLK_DIV16] = &g12a_cpu_clk_div16, + [CLKID_CPU_CLK_APB_DIV] = &g12a_cpu_clk_apb_div, + [CLKID_CPU_CLK_APB] = &g12a_cpu_clk_apb, + [CLKID_CPU_CLK_ATB_DIV] = &g12a_cpu_clk_atb_div, + [CLKID_CPU_CLK_ATB] = &g12a_cpu_clk_atb, + [CLKID_CPU_CLK_AXI_DIV] = &g12a_cpu_clk_axi_div, + [CLKID_CPU_CLK_AXI] = &g12a_cpu_clk_axi, + [CLKID_CPU_CLK_TRACE_DIV] = &g12a_cpu_clk_trace_div, + [CLKID_CPU_CLK_TRACE] = &g12a_cpu_clk_trace, + [CLKID_PCIE_PLL_DCO] = &g12a_pcie_pll_dco, + [CLKID_PCIE_PLL_DCO_DIV2] = &g12a_pcie_pll_dco_div2, + [CLKID_PCIE_PLL_OD] = &g12a_pcie_pll_od, + [CLKID_PCIE_PLL] = &g12a_pcie_pll, + [CLKID_VDEC_1_SEL] = &g12a_vdec_1_sel, + [CLKID_VDEC_1_DIV] = &g12a_vdec_1_div, + [CLKID_VDEC_1] = &g12a_vdec_1, + [CLKID_VDEC_HEVC_SEL] = &g12a_vdec_hevc_sel, + [CLKID_VDEC_HEVC_DIV] = &g12a_vdec_hevc_div, + [CLKID_VDEC_HEVC] = &g12a_vdec_hevc, + [CLKID_VDEC_HEVCF_SEL] = &g12a_vdec_hevcf_sel, + [CLKID_VDEC_HEVCF_DIV] = &g12a_vdec_hevcf_div, + [CLKID_VDEC_HEVCF] = &g12a_vdec_hevcf, + [CLKID_TS_DIV] = &g12a_ts_div, + [CLKID_TS] = &g12a_ts, + [CLKID_GP1_PLL_DCO] = &sm1_gp1_pll_dco, + [CLKID_GP1_PLL] = &sm1_gp1_pll, + [CLKID_DSU_CLK_DYN0_SEL] = &sm1_dsu_clk_premux0, + [CLKID_DSU_CLK_DYN0_DIV] = &sm1_dsu_clk_premux1, + [CLKID_DSU_CLK_DYN0] = &sm1_dsu_clk_mux0_div, + [CLKID_DSU_CLK_DYN1_SEL] = &sm1_dsu_clk_postmux0, + [CLKID_DSU_CLK_DYN1_DIV] = &sm1_dsu_clk_mux1_div, + [CLKID_DSU_CLK_DYN1] = &sm1_dsu_clk_postmux1, + [CLKID_DSU_CLK_DYN] = &sm1_dsu_clk_dyn, + [CLKID_DSU_CLK_FINAL] = &sm1_dsu_final_clk, + [CLKID_DSU_CLK] = &sm1_dsu_clk, + [CLKID_CPU1_CLK] = &sm1_cpu1_clk, + [CLKID_CPU2_CLK] = &sm1_cpu2_clk, + [CLKID_CPU3_CLK] = &sm1_cpu3_clk, + [CLKID_SPICC0_SCLK_SEL] = &g12a_spicc0_sclk_sel, + [CLKID_SPICC0_SCLK_DIV] = &g12a_spicc0_sclk_div, + [CLKID_SPICC0_SCLK] = &g12a_spicc0_sclk, + [CLKID_SPICC1_SCLK_SEL] = &g12a_spicc1_sclk_sel, + [CLKID_SPICC1_SCLK_DIV] = &g12a_spicc1_sclk_div, + [CLKID_SPICC1_SCLK] = &g12a_spicc1_sclk, + [CLKID_NNA_AXI_CLK_SEL] = &sm1_nna_axi_clk_sel, + [CLKID_NNA_AXI_CLK_DIV] = &sm1_nna_axi_clk_div, + [CLKID_NNA_AXI_CLK] = &sm1_nna_axi_clk, + [CLKID_NNA_CORE_CLK_SEL] = &sm1_nna_core_clk_sel, + [CLKID_NNA_CORE_CLK_DIV] = &sm1_nna_core_clk_div, + [CLKID_NNA_CORE_CLK] = &sm1_nna_core_clk, + [CLKID_MIPI_DSI_PXCLK_SEL] = &g12a_mipi_dsi_pxclk_sel, + [CLKID_MIPI_DSI_PXCLK_DIV] = &g12a_mipi_dsi_pxclk_div, + [CLKID_MIPI_DSI_PXCLK] = &g12a_mipi_dsi_pxclk, + [CLKID_G12A_XTAL] = &g12a_xtal, +}; + +int clk_msr_stat(struct clk *clk_list[]) +{ +#ifdef DEBUG_DRIVER + unsigned long clk_freq; + int i = 0; + uint64_t rate = 0; + int err; + + const char *const *clk_msr_list = get_msr_clk_list(); + + LOG_DRIVER("-------Expected clock rates------\n"); + for (i = 0; i < NUM_CLK_LIST; i++) { + err = clk_get_rate(clk_list[i], &rate); + if (err) { + LOG_DRIVER("Failed to get rate of %s: -%u\n", clk_list[i]->hw.init->name, err); + } + LOG_DRIVER("[%4d][%4luHz] %s\n", i, rate, clk_list[i]->hw.init->name); + } + LOG_DRIVER("---------------------------------\n"); + LOG_DRIVER("---------Clock measurement-------\n"); + for (i = 0; i < 128; i++) { + clk_freq = clk_msr(i); + LOG_DRIVER("[%4d][%4ldHz] %s\n", i, clk_freq, clk_msr_list[i]); + } + LOG_DRIVER("-----------------------------\n"); + +#endif + + return 0; +} + +/* TODO: Should be configured with init_regs */ +/* static struct clk_cfg fixed_clk_configs[] = { */ +/* { .clk_id = CLKID_FCLK_DIV2_DIV, .frequency = 1000000000 }, */ +/* { .clk_id = CLKID_FCLK_DIV3_DIV, .frequency = 666666667 }, */ +/* { .clk_id = CLKID_FCLK_DIV4_DIV, .frequency = 500000000 }, */ +/* { .clk_id = CLKID_FCLK_DIV5_DIV, .frequency = 400000000 }, */ +/* { .clk_id = CLKID_FCLK_DIV7_DIV, .frequency = 285700000 }, */ +/* } */ + +void clk_probe(struct clk *clk_list[]) +{ + int i; + for (i = 0; i < NUM_CLK_LIST; i++) { + if (!clk_list[i]) + continue; + clk_list[i]->base = (uint64_t)clk_regs; + if (clk_list[i]->hw.init->ops->init) { + clk_list[i]->hw.init->ops->init(clk_list[i]); + LOG_DRIVER("Initialise %s\n", clk_list[i]->hw.init->name); + } + } +} + +struct clk **get_clk_list(void) +{ + return sm1_clks; +} diff --git a/examples/clk/Makefile b/examples/clk/Makefile new file mode 100644 index 000000000..d5c67ed4b --- /dev/null +++ b/examples/clk/Makefile @@ -0,0 +1,35 @@ +# +# Copyright 2026, UNSW +# +# SPDX-License-Identifier: BSD-2-Clause +# + +ifeq ($(strip $(MICROKIT_SDK)),) +$(error MICROKIT_SDK must be specified) +endif +override MICROKIT_SDK := $(abspath ${MICROKIT_SDK}) + +BUILD_DIR ?= build +override BUILD_DIR := $(abspath ${BUILD_DIR}) +export BUILD_DIR +export MICROKIT_CONFIG ?= debug +export MICROKIT_BOARD ?= odroidc4 + +export SDDF := $(abspath ../../) + +IMAGE_FILE := $(BUILD_DIR)/loader.img +REPORT_FILE := $(BUILD_DIR)/report.txt + +all: clean ${IMAGE_FILE} + +${IMAGE_FILE}: ${BUILD_DIR}/Makefile + ${MAKE} -C ${BUILD_DIR} MICROKIT_SDK=${MICROKIT_SDK} $(notdir $@) + +${BUILD_DIR}/Makefile: clk.mk + mkdir -p ${BUILD_DIR} + cp clk.mk ${BUILD_DIR}/Makefile + +clean: + rm -rfd $(BUILD_DIR) + +FORCE: diff --git a/examples/clk/board/maaxboard/clk.system b/examples/clk/board/maaxboard/clk.system new file mode 100644 index 000000000..5d6f69373 --- /dev/null +++ b/examples/clk/board/maaxboard/clk.system @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/clk/board/odroidc4/clk.system b/examples/clk/board/odroidc4/clk.system new file mode 100644 index 000000000..c9d24da0c --- /dev/null +++ b/examples/clk/board/odroidc4/clk.system @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/clk/build.zig b/examples/clk/build.zig new file mode 100644 index 000000000..ce7a75462 --- /dev/null +++ b/examples/clk/build.zig @@ -0,0 +1,147 @@ +// +// Copyright 2024, UNSW +// SPDX-License-Identifier: BSD-2-Clause +// +const std = @import("std"); + +const MicrokitBoard = enum { odroidc4, maaxboard }; + +const Target = struct { + board: MicrokitBoard, + zig_target: std.Target.Query, +}; + +const targets = [_]Target{ + .{ + .board = MicrokitBoard.odroidc4, + .zig_target = std.Target.Query{ + .cpu_arch = .aarch64, + .cpu_model = .{ .explicit = &std.Target.arm.cpu.cortex_a55 }, + .os_tag = .freestanding, + .abi = .none, + }, + }, + .{ + .board = MicrokitBoard.maaxboard, + .zig_target = std.Target.Query{ + .cpu_arch = .aarch64, + .cpu_model = .{ .explicit = &std.Target.arm.cpu.cortex_a53 }, + .os_tag = .freestanding, + .abi = .none, + }, + }, +}; + +fn findTarget(board: MicrokitBoard) std.Target.Query { + for (targets) |target| { + if (board == target.board) { + return target.zig_target; + } + } + + std.log.err("Board '{}' is not supported\n", .{board}); + std.posix.exit(1); +} + +const ConfigOptions = enum { debug, release, benchmark }; + +pub fn build(b: *std.Build) void { + const optimize = b.standardOptimizeOption(.{}); + + // Getting the path to the Microkit SDK before doing anything else + const microkit_sdk_arg = b.option([]const u8, "sdk", "Path to Microkit SDK"); + if (microkit_sdk_arg == null) { + std.log.err("Missing -Dsdk=/path/to/sdk argument being passed\n", .{}); + std.posix.exit(1); + } + const microkit_sdk = microkit_sdk_arg.?; + + const microkit_config_option = b.option(ConfigOptions, "config", "Microkit config to build for") orelse ConfigOptions.debug; + const microkit_config = @tagName(microkit_config_option); + + // Get the Microkit SDK board we want to target + const microkit_board_option = b.option(MicrokitBoard, "board", "Microkit board to target"); + + if (microkit_board_option == null) { + std.log.err("Missing -Dboard= argument being passed\n", .{}); + std.posix.exit(1); + } + const target = b.resolveTargetQuery(findTarget(microkit_board_option.?)); + const microkit_board = @tagName(microkit_board_option.?); + + const microkit_board_dir = b.fmt("{s}/board/{s}/{s}", .{ microkit_sdk, microkit_board, microkit_config }); + const microkit_tool = b.fmt("{s}/bin/microkit", .{microkit_sdk}); + const libmicrokit = b.fmt("{s}/lib/libmicrokit.a", .{microkit_board_dir}); + const libmicrokit_include = b.fmt("{s}/include", .{microkit_board_dir}); + const libmicrokit_linker_script = b.fmt("{s}/lib/microkit.ld", .{microkit_board_dir}); + + const dtb_arg = b.option([]const u8, "dtb", "Path to DTB file"); + if (dtb_arg == null) { + std.log.err("Missing -Ddtb=/path/to/dtb argument being passed\n", .{}); + std.posix.exit(1); + } + const dtb_path = std.Build.LazyPath{ .cwd_relative = dtb_arg.? }; + // TODO: Need to define this with built-in output path + const clk_conf_include = std.Build.LazyPath{ .cwd_relative = "zig-out/bin/" }; + const sddf_dep = b.dependency("sddf", .{ + .target = target, + .optimize = optimize, + .libmicrokit = @as([]const u8, libmicrokit), + .libmicrokit_include = @as([]const u8, libmicrokit_include), + .libmicrokit_linker_script = @as([]const u8, libmicrokit_linker_script), + .i2c_client_include = @as([]const u8, ""), + .clk_conf_include = @as([]const u8, clk_conf_include.getPath(b)), + .dtb_path = @as([]const u8, dtb_path.getPath(b) ), + }); + + const timer_driver_class = switch (microkit_board_option.?) { + .odroidc4 => "meson", + .maaxboard => "imx", + }; + + const clk_driver_class = switch (microkit_board_option.?) { + .odroidc4 => "meson", + .maaxboard => "imx", + }; + const clk_driver = sddf_dep.artifact(b.fmt("driver_clk_{s}.elf", .{clk_driver_class})); + const clk_driver_install = b.addInstallArtifact(clk_driver, .{ .dest_sub_path = "clk_driver.elf" }); + + const timer_driver = sddf_dep.artifact(b.fmt("driver_timer_{s}.elf", .{timer_driver_class})); + // This is required because the SDF file is expecting a different name to the artifact we + // are dealing with. + const timer_driver_install = b.addInstallArtifact(timer_driver, .{ .dest_sub_path = "timer_driver.elf" }); + + const client = b.addExecutable(.{ + .name = "client.elf", + .target = target, + .optimize = optimize, + .strip = false, + }); + client.addCSourceFile(.{ .file = b.path("client.c") }); + client.defineCMacro(b.fmt("TEST_BOARD_{s}", .{ microkit_board }), "1"); + client.addIncludePath(sddf_dep.path("include")); + client.linkLibrary(sddf_dep.artifact("util")); + client.linkLibrary(sddf_dep.artifact("util_putchar_debug")); + client.addIncludePath(.{ .cwd_relative = libmicrokit_include }); + client.addObjectFile(.{ .cwd_relative = libmicrokit }); + client.setLinkerScriptPath(.{ .cwd_relative = libmicrokit_linker_script }); + + b.installArtifact(client); + + const system_description_path = b.fmt("board/{s}/clk.system", .{microkit_board}); + const final_image_dest = b.getInstallPath(.bin, "./loader.img"); + const microkit_tool_cmd = b.addSystemCommand(&[_][]const u8{ + microkit_tool, + system_description_path, + "--search-path", b.getInstallPath(.bin, ""), + "--board", microkit_board, + "--config", microkit_config, + "-o", final_image_dest, + "-r", b.getInstallPath(.prefix, "./report.txt") }); + microkit_tool_cmd.step.dependOn(b.getInstallStep()); + microkit_tool_cmd.step.dependOn(&timer_driver_install.step); + microkit_tool_cmd.step.dependOn(&clk_driver_install.step); + const microkit_step = b.step("microkit", "Compile and build the final bootable image"); + microkit_step.dependOn(µkit_tool_cmd.step); + b.default_step = microkit_step; +} diff --git a/examples/clk/build.zig.zon b/examples/clk/build.zig.zon new file mode 100644 index 000000000..6deea19f4 --- /dev/null +++ b/examples/clk/build.zig.zon @@ -0,0 +1,18 @@ +// +// Copyright 2024, UNSW +// SPDX-License-Identifier: BSD-2-Clause +// +.{ + .name = "sddf_clk_example", + .version = "1.0.0", + + .dependencies = .{ + .sddf = .{ + .path = "../../" + }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + } +} diff --git a/examples/clk/client.c b/examples/clk/client.c new file mode 100644 index 000000000..bba9b5e56 --- /dev/null +++ b/examples/clk/client.c @@ -0,0 +1,155 @@ +/* + * Copyright 2024, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include + +#define CLK_DRIVER_CH 0 + +#ifdef TEST_BOARD_odroidc4 +#include +#elif TEST_BOARD_maaxboard +#include +#else +#error "The target board is not supported\n" +#endif + +__attribute__((__section__(".serial_client_config"))) serial_client_config_t serial_config; + +void init(void) +{ + sddf_dprintf("--------------------\n"); + sddf_dprintf("Clock Driver Test\n"); + +#ifdef TEST_BOARD_odroidc4 + sddf_dprintf("Test board: odroidc4\n"); + + /** + * CLKID_CLK81 = 10 + * CLKID_I2C = 24 + * CLKID_CPU_CLK = 187 + * + * see `sddf/drivers/clk/meson/include/g12a-bindings.h` for more clock indices. + * + **/ + uint32_t clk_id_to_enable = CLKID_CLK81; + int ret = sddf_clk_enable(CLK_DRIVER_CH, clk_id_to_enable); + if (ret) { + sddf_dprintf("Failed to enable clock %u: err - %d\n", clk_id_to_enable, ret); + } else { + sddf_dprintf("Successfully enabled clock %u\n", clk_id_to_enable); + } + + uint32_t clk_id_to_disable = CLKID_I2C; + ret = sddf_clk_disable(CLK_DRIVER_CH, clk_id_to_disable); + if (ret) { + sddf_dprintf("Failed to disable clock %u: err - %d\n", clk_id_to_enable, ret); + } else { + sddf_dprintf("Successfully disabled clock %u\n", clk_id_to_enable); + } + + uint64_t rate = 0; + uint32_t clk_id_to_set_rate = CLKID_CLK81; + ret = sddf_clk_get_rate(CLK_DRIVER_CH, clk_id_to_set_rate, &rate); + if (ret) { + sddf_dprintf("Failed to get the rate of clock %u: err - %d\n", clk_id_to_set_rate, ret); + } else { + sddf_dprintf("The rate of clock %u: %lu\n", clk_id_to_set_rate, rate); + } + + ret = sddf_clk_set_rate(CLK_DRIVER_CH, clk_id_to_set_rate, 150000000, &rate); + if (ret) { + sddf_dprintf("Failed to set the rate of clock %u: err - %d\n", clk_id_to_set_rate, ret); + } else { + sddf_dprintf("Set the rate of clock %u to %lu\n", ret, rate); + } + + uint32_t clk_id_to_get_rate = CLKID_CPU_CLK; + ret = sddf_clk_get_rate(CLK_DRIVER_CH, clk_id_to_get_rate, &rate); + if (ret) { + sddf_dprintf("Failed to get the rate of clock %u: err - %d\n", clk_id_to_get_rate, ret); + } else { + sddf_dprintf("The rate of clock %u: %lu\n", clk_id_to_get_rate, rate); + } + + uint32_t clk_id_to_test_parent = CLKID_SD_EMMC_A_CLK0_SEL; + uint32_t parent_id = 0; + ret = sddf_clk_get_parent(CLK_DRIVER_CH, clk_id_to_test_parent, &parent_id); + if (ret) { + sddf_dprintf("Failed to get the parent of clock %u: err - %d\n", clk_id_to_test_parent, ret); + } else { + sddf_dprintf("The parent of clock %u: %u\n", clk_id_to_test_parent, parent_id); + } + + uint32_t pclk_idx = 3; + ret = sddf_clk_set_parent(CLK_DRIVER_CH, clk_id_to_test_parent, pclk_idx); + if (ret) { + sddf_dprintf("Failed to set the parent of clock %u: err - %d\n", clk_id_to_test_parent, ret); + } else { + ret = sddf_clk_get_parent(CLK_DRIVER_CH, clk_id_to_test_parent, &parent_id); + sddf_dprintf("The parent of clock %u has been set to: %u\n", clk_id_to_test_parent, parent_id); + } + +#elif TEST_BOARD_maaxboard + sddf_dprintf("Test board: maaxboard\n"); + int ret = 0; + + /** + * IMX8MQ_CLK_SAI1_ROOT = 196 + * IMX8MQ_CLK_I2C1 = 144 + * IMX8MQ_CLK_A53_DIV = 90 + * + * see `sddf/drivers/clk/imx/include/imx8mq-bindings.h` for more clock indices. + * */ + + uint32_t clk_id_to_enable = IMX8MQ_CLK_SAI1_ROOT; + ret = sddf_clk_enable(CLK_DRIVER_CH, clk_id_to_enable); + if (ret) { + sddf_dprintf("Failed to enable clock %u: err - %d\n", clk_id_to_enable, ret); + } else { + sddf_dprintf("Successfully enabled clock %u\n", clk_id_to_enable); + } + + uint32_t clk_id_to_test_parent = IMX8MQ_CLK_I2C1; + uint32_t parent_id = 0; + ret = sddf_clk_get_parent(CLK_DRIVER_CH, clk_id_to_test_parent, &parent_id); + if (ret) { + sddf_dprintf("Failed to get the parent of clock %u: err - %d\n", clk_id_to_test_parent, ret); + } else { + sddf_dprintf("The parent of clock %u: %u\n", clk_id_to_test_parent, parent_id); + } + + uint32_t pclk_idx = 3; + ret = sddf_clk_set_parent(CLK_DRIVER_CH, clk_id_to_test_parent, pclk_idx); + if (ret) { + sddf_dprintf("Failed to set the parent of clock %u: err - %d\n", clk_id_to_test_parent, ret); + } else { + ret = sddf_clk_get_parent(CLK_DRIVER_CH, clk_id_to_test_parent, &parent_id); + sddf_dprintf("The parent of clock %u has been set to: %u\n", clk_id_to_test_parent, parent_id); + } + + uint32_t cpu_clk_id = IMX8MQ_CLK_A53_CORE; + uint64_t rate = 0; + ret = sddf_clk_get_rate(CLK_DRIVER_CH, cpu_clk_id, &rate); + sddf_dprintf("cpu frequency: %lu\n", rate); + + ret = sddf_clk_set_rate(CLK_DRIVER_CH, IMX8MQ_CLK_ARM, 150000000, &rate); + sddf_dprintf("Set CPU freq - err: %d\n", ret); + + ret = sddf_clk_get_rate(CLK_DRIVER_CH, cpu_clk_id, &rate); + sddf_dprintf("cpu frequency: %lu\n", rate); +#else + sddf_dprintf("No tests for the target board\n", ret); +#endif + + sddf_dprintf("--------------------\n"); +} + +void notified(microkit_channel ch) +{ +} diff --git a/examples/clk/clk.mk b/examples/clk/clk.mk new file mode 100644 index 000000000..acd3f56a9 --- /dev/null +++ b/examples/clk/clk.mk @@ -0,0 +1,97 @@ +# +# Copyright 2026, UNSW +# +# SPDX-License-Identifier: BSD-2-Clause +# + +ifeq ($(strip $(MICROKIT_SDK)),) +$(error MICROKIT_SDK must be specified) +endif + +ifeq ($(strip $(TOOLCHAIN)),) + TOOLCHAIN := clang +endif + +BOARD_DIR := $(MICROKIT_SDK)/board/$(MICROKIT_BOARD)/$(MICROKIT_CONFIG) +UTIL := $(SDDF)/util + +SUPPORTED_BOARDS := \ + odroidc4 \ + maaxboard + +include ${SDDF}/tools/make/board/common.mk + +SDDF_CUSTOM_LIBC := 1 +UTIL := $(SDDF)/util +LIBCO := $(SDDF)/libco +TOP := ${SDDF}/examples/clk +SERIAL := $(SDDF)/serial +I2C_DRIVER := $(SDDF)/drivers/i2c/${I2C_DRIV_DIR} +SERIAL_DRIVER := $(SDDF)/drivers/serial/${UART_DRIV_DIR} +CLK_DRIVER := $(SDDF)/drivers/clk/${CLK_DRIV_DIR} +TIMER_DRIVER := $(SDDF)/drivers/timer/${TIMER_DRIV_DIR} + +IMAGES := timer_driver.elf \ + clk_driver.elf \ + client.elf \ + serial_driver.elf \ + serial_virt_tx.elf +LDFLAGS := -L$(BOARD_DIR)/lib +LIBS := --start-group -lmicrokit -Tmicrokit.ld libsddf_util_debug.a --end-group + +IMAGE_FILE := loader.img +REPORT_FILE := report.txt +SYSTEM_FILE = clk.system +CLIENT_OBJS := client.o + +DTS := $(SDDF)/dts/$(MICROKIT_BOARD).dts +DTB := $(MICROKIT_BOARD).dtb +METAPROGRAM := $(TOP)/meta.py + +CFLAGS += -I$(BOARD_DIR)/include \ + -I$(SDDF)/include \ + -I$(SDDF)/include/microkit \ + -I$(LIBCO) \ + -MD \ + -MP + +VPATH := ${TOP} +all: $(IMAGE_FILE) + +${IMAGES}: libsddf_util_debug.a + + +client.o: ${TOP}/client.c + $(CC) -c $(CFLAGS) $(CHIP_HEADER_INC) -DTEST_BOARD_${MICROKIT_BOARD} $< -o client.o + +client.elf: client.o + $(LD) $(LDFLAGS) $^ $(LIBS) -o $@ + +$(SYSTEM_FILE): $(METAPROGRAM) $(IMAGES) $(DTB) + $(PYTHON) $(METAPROGRAM) --sddf $(SDDF) --board $(MICROKIT_BOARD) --dtb $(DTB) --output . --sdf $(SYSTEM_FILE) + $(OBJCOPY) --update-section .device_resources=timer_driver_device_resources.data timer_driver.elf + $(OBJCOPY) --update-section .device_resources=serial_driver_device_resources.data serial_driver.elf + $(OBJCOPY) --update-section .serial_driver_config=serial_driver_config.data serial_driver.elf + $(OBJCOPY) --update-section .serial_virt_tx_config=serial_virt_tx.data serial_virt_tx.elf + $(OBJCOPY) --update-section .serial_client_config=serial_client_client.data client.elf + touch $@ + +$(IMAGE_FILE) $(REPORT_FILE): $(IMAGES) $(SYSTEM_FILE) + $(MICROKIT_TOOL) $(SYSTEM_FILE) --search-path $(BUILD_DIR) --board $(MICROKIT_BOARD) --config $(MICROKIT_CONFIG) -o $(IMAGE_FILE) -r $(REPORT_FILE) + +${IMAGES}: libsddf_util_debug.a +.PHONY: all compile clean + +clean:: + rm -f *.elf + find . -name '*.[do]' |xargs --no-run-if-empty rm + +clobber:: clean + rm -f ${REPORT_FILE} ${IMAGE_FILE} *.a .*cflags* + +include ${SDDF}/util/util.mk +include ${SERIAL}/components/serial_components.mk +include ${SERIAL_DRIVER}/serial_driver.mk +include ${TIMER_DRIVER}/timer_driver.mk +include ${LIBCO}/libco.mk +include ${CLK_DRIVER}/clk_driver.mk diff --git a/examples/clk/meta.py b/examples/clk/meta.py new file mode 100644 index 000000000..4d9b49c72 --- /dev/null +++ b/examples/clk/meta.py @@ -0,0 +1,121 @@ +# Copyright 2026, UNSW +# SPDX-License-Identifier: BSD-2-Clause +import os, sys +import argparse +from typing import List +from dataclasses import dataclass +from sdfgen import SystemDescription, Sddf, DeviceTree +from importlib.metadata import version + +sys.path.append( + os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../tools/meta") +) +from board import BOARDS + +assert version("sdfgen").split(".")[1] == "28", "Unexpected sdfgen version" + +ProtectionDomain = SystemDescription.ProtectionDomain +MemoryRegion = SystemDescription.MemoryRegion +Map = SystemDescription.Map +Channel = SystemDescription.Channel + + +def generate(sdf_file: str, output_dir: str, dtb: DeviceTree): + serial_driver = ProtectionDomain("serial_driver", "serial_driver.elf", priority=200) + # Increase the stack size as running with UBSAN uses more stack space than normal. + serial_virt_tx = ProtectionDomain( + "serial_virt_tx", "serial_virt_tx.elf", priority=199, stack_size=0x2000 + ) + + timer_driver = ProtectionDomain("timer_driver", "timer_driver.elf", priority=200) + client = ProtectionDomain("client", "client.elf", priority=1) + clk_driver = ProtectionDomain("clk_driver", "clk_driver.elf", priority=100, passive=True) + + timer_node = dtb.node(board.timer) + assert timer_node is not None + serial_node = dtb.node(board.serial) + assert serial_node is not None + + timer_system = Sddf.Timer(sdf, timer_node, timer_driver) + serial_system = Sddf.Serial( + sdf, serial_node, serial_driver, serial_virt_tx, enable_color=False + ) + serial_system.add_client(client) + + # HACK: sdfgen doesn't support multiple regions for a device resource yet + # or the clk class. This will be removed in the pending sdfgen refactor. + # We can add direct support for the Maaxboard via boards.py, but + # the odroid clk driver depends on numerous maps that aren't in the DTS + # at all, meaning this switch is the best we can do for now. + regions = [] # tuples of (mr, map var name) + if board.name == "maaxboard": + clk_ccm_mr = MemoryRegion(sdf, "clk_ccm", 0xd000, paddr=0x30380000) + clk_ccm_analog_mr = MemoryRegion(sdf, "clk_ccm_analog", 0x1000, paddr=0x30360000) + sdf.add_mr(clk_ccm_mr) + sdf.add_mr(clk_ccm_analog_mr) + + clk_ccm_map = Map(clk_ccm_mr, 0x3200000, "rw", cached=False) + clk_ccm_analog_map = Map(clk_ccm_analog_mr, 0x3300000, "rw", cached=False) + clk_driver.add_map(clk_ccm_map) + clk_driver.add_map(clk_ccm_analog_map) + + elif board.name == "odroidc4": + clk_mr = MemoryRegion(sdf, "clk", 0x1000, paddr=0xFF63C000) + msr_clk_mr = MemoryRegion(sdf, "msr_clk", 0x1000, paddr=0xFFD18000) + sdf.add_mr(clk_mr) + sdf.add_mr(msr_clk_mr) + + clk_map = Map(clk_mr, 0x3200000, "rw", cached=False) + msr_clk_map = Map(msr_clk_mr, 0x3300000, "rw", cached=False) + clk_driver.add_map(clk_map) + clk_driver.add_map(msr_clk_map) + + timer_system.add_client(clk_driver) + + else: + print("Unsupported board!") + exit(-1) + + + + clk_channel = Channel(clk_driver, client, pp_b=True) + sdf.add_channel(clk_channel) + + pds = [ + clk_driver, + serial_driver, + serial_virt_tx, + timer_driver, + client, + ] + for pd in pds: + sdf.add_pd(pd) + + assert serial_system.connect() + assert serial_system.serialise_config(output_dir) + assert timer_system.connect() + assert timer_system.serialise_config(output_dir) + + with open(f"{output_dir}/{sdf_file}", "w+") as f: + f.write(sdf.render()) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--dtb", required=True) + parser.add_argument("--sddf", required=True) + parser.add_argument("--board", required=True, choices=[b.name for b in BOARDS]) + parser.add_argument("--output", required=True) + parser.add_argument("--sdf", required=True) + + args = parser.parse_args() + + board = next(filter(lambda b: b.name == args.board, BOARDS)) + + sdf = SystemDescription(board.arch, board.paddr_top) + sddf = Sddf(args.sddf) + + with open(args.dtb, "rb") as f: + dtb = DeviceTree(f.read()) + + generate(args.sdf, args.output, dtb) diff --git a/flake.nix b/flake.nix index d23f12303..04cc9736a 100644 --- a/flake.nix +++ b/flake.nix @@ -83,6 +83,18 @@ pythonTool = pkgs.python312.withPackages (ps: [ pysdfgen ts_ci + ( + ps.buildPythonPackage rec { + pname = "devicetree"; + version = "0.0.2"; + src = ps.fetchPypi { + inherit pname version; + hash = "sha256-4bHoTmZwXFGQ3dfqq3JC1JSNSt0yC9UXrEdm80zpwMY="; + }; + dependencies = [ ps.pyyaml ]; + pythonImportsCheck = [ "devicetree" ]; + } + ) ]); in { diff --git a/include/sddf/clk/client.h b/include/sddf/clk/client.h new file mode 100644 index 000000000..ec1e22847 --- /dev/null +++ b/include/sddf/clk/client.h @@ -0,0 +1,116 @@ +/* + * Copyright 2024, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +/** + * Send a clock enabling request via PPC into the passive clock driver. + * Use the label to indicate this request. + * @param channel of clock driver. + * @param identifier of target clock. + */ +static inline int sddf_clk_enable(microkit_channel channel, uint32_t clk_id) +{ + microkit_msginfo msginfo = microkit_msginfo_new(SDDF_CLK_ENABLE, 1); + microkit_mr_set(SDDF_CLK_PARAM_ID, clk_id); + + msginfo = microkit_ppcall(channel, msginfo); + + return (int)microkit_msginfo_get_label(msginfo); +} + +/** + * Send a clock disabling request via PPC into the passive clock driver. + * Use the label to indicate this request. + * @param channel of clock driver. + * @param identifier of target clock. + */ +static inline int sddf_clk_disable(microkit_channel channel, uint32_t clk_id) +{ + microkit_msginfo msginfo = microkit_msginfo_new(SDDF_CLK_DISABLE, 1); + microkit_mr_set(SDDF_CLK_PARAM_ID, clk_id); + + msginfo = microkit_ppcall(channel, msginfo); + + return (int)microkit_msginfo_get_label(msginfo); +} + +/** + * Send a clock get_rate request via PPC into the passive clock driver. + * Use the label to indicate this request. + * @param channel of clock driver. + * @param identifier of target clock. + * @param pointer to result variable. + */ +static inline int sddf_clk_get_rate(microkit_channel channel, uint32_t clk_id, uint64_t *rate) +{ + microkit_msginfo msginfo = microkit_msginfo_new(SDDF_CLK_GET_RATE, 1); + microkit_mr_set(SDDF_CLK_PARAM_ID, clk_id); + + msginfo = microkit_ppcall(channel, msginfo); + + *rate = microkit_mr_get(0); + return (int)microkit_msginfo_get_label(msginfo); +} + +/** + * Send a clock set_rate request via PPC into the passive clock driver. + * Use the label to indicate this request. + * @param channel of clock driver. + * @param identifier of target clock. + * @param target clock frequency. + * @param pointer to result variable. + */ +static inline int sddf_clk_set_rate(microkit_channel channel, uint32_t clk_id, uint64_t req_rate, uint64_t *rate) +{ + microkit_msginfo msginfo = microkit_msginfo_new(SDDF_CLK_SET_RATE, 2); + microkit_mr_set(SDDF_CLK_PARAM_ID, clk_id); + microkit_mr_set(SDDF_CLK_PARAM_RATE, req_rate); + + msginfo = microkit_ppcall(channel, msginfo); + + *rate = microkit_mr_get(0); + return (int)microkit_msginfo_get_label(msginfo); +} + +/** + * Send a clock get_parent request via PPC into the passive clock driver. + * Use the label to indicate this request. + * @param channel of clock driver. + * @param pointer to result (i.e. pclk_id) + */ +static inline int sddf_clk_get_parent(microkit_channel channel, uint32_t clk_id, uint32_t *pclk_id) +{ + microkit_msginfo msginfo = microkit_msginfo_new(SDDF_CLK_GET_PARENT, 1); + microkit_mr_set(SDDF_CLK_PARAM_ID, clk_id); + + msginfo = microkit_ppcall(channel, msginfo); + + *pclk_id = microkit_mr_get(0); + return (int)microkit_msginfo_get_label(msginfo); +} + +/** + * Send a clock set_parent request via PPC into the passive clock driver. + * Use the label to indicate this request. + * @param channel of clock driver. + * @param identifier of target clock. + * @param indice of target clock. + */ +static inline int sddf_clk_set_parent(microkit_channel channel, uint32_t clk_id, uint32_t parent_idx) +{ + microkit_msginfo msginfo = microkit_msginfo_new(SDDF_CLK_SET_PARENT, 2); + microkit_mr_set(SDDF_CLK_PARAM_ID, clk_id); + microkit_mr_set(SDDF_CLK_PARAM_PCLK_IDX, parent_idx); + + msginfo = microkit_ppcall(channel, msginfo); + + return (int)microkit_msginfo_get_label(msginfo); +} diff --git a/include/sddf/clk/g12a-bindings.h b/include/sddf/clk/g12a-bindings.h new file mode 100644 index 000000000..f987c8e5a --- /dev/null +++ b/include/sddf/clk/g12a-bindings.h @@ -0,0 +1,292 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later OR MIT */ +/* + * Meson-G12A clock tree IDs + * + * Copyright (c) 2018 Amlogic, Inc. All rights reserved. + * + * Source: https://github.com/torvalds/linux/blob/20371ba120635d9ab7fc7670497105af8f33eb08/include/dt-bindings/clock/g12a-clkc.h + */ + +#pragma once + +#define CLKID_SYS_PLL 0 +#define CLKID_FIXED_PLL 1 +#define CLKID_FCLK_DIV2 2 +#define CLKID_FCLK_DIV3 3 +#define CLKID_FCLK_DIV4 4 +#define CLKID_FCLK_DIV5 5 +#define CLKID_FCLK_DIV7 6 +#define CLKID_GP0_PLL 7 +#define CLKID_MPEG_SEL 8 +#define CLKID_MPEG_DIV 9 +#define CLKID_CLK81 10 +#define CLKID_MPLL0 11 +#define CLKID_MPLL1 12 +#define CLKID_MPLL2 13 +#define CLKID_MPLL3 14 +#define CLKID_DDR 15 +#define CLKID_DOS 16 +#define CLKID_AUDIO_LOCKER 17 +#define CLKID_MIPI_DSI_HOST 18 +#define CLKID_ETH_PHY 19 +#define CLKID_ISA 20 +#define CLKID_PL301 21 +#define CLKID_PERIPHS 22 +#define CLKID_SPICC0 23 +#define CLKID_I2C 24 +#define CLKID_SANA 25 +#define CLKID_SD 26 +#define CLKID_RNG0 27 +#define CLKID_UART0 28 +#define CLKID_SPICC1 29 +#define CLKID_HIU_IFACE 30 +#define CLKID_MIPI_DSI_PHY 31 +#define CLKID_ASSIST_MISC 32 +#define CLKID_SD_EMMC_A 33 +#define CLKID_SD_EMMC_B 34 +#define CLKID_SD_EMMC_C 35 +#define CLKID_AUDIO_CODEC 36 +#define CLKID_AUDIO 37 +#define CLKID_ETH 38 +#define CLKID_DEMUX 39 +#define CLKID_AUDIO_IFIFO 40 +#define CLKID_ADC 41 +#define CLKID_UART1 42 +#define CLKID_G2D 43 +#define CLKID_RESET 44 +#define CLKID_PCIE_COMB 45 +#define CLKID_PARSER 46 +#define CLKID_USB 47 +#define CLKID_PCIE_PHY 48 +#define CLKID_AHB_ARB0 49 +#define CLKID_AHB_DATA_BUS 50 +#define CLKID_AHB_CTRL_BUS 51 +#define CLKID_HTX_HDCP22 52 +#define CLKID_HTX_PCLK 53 +#define CLKID_BT656 54 +#define CLKID_USB1_DDR_BRIDGE 55 +#define CLKID_MMC_PCLK 56 +#define CLKID_UART2 57 +#define CLKID_VPU_INTR 58 +#define CLKID_GIC 59 +#define CLKID_SD_EMMC_A_CLK0 60 +#define CLKID_SD_EMMC_B_CLK0 61 +#define CLKID_SD_EMMC_C_CLK0 62 +#define CLKID_SD_EMMC_A_CLK0_SEL 63 +#define CLKID_SD_EMMC_A_CLK0_DIV 64 +#define CLKID_SD_EMMC_B_CLK0_SEL 65 +#define CLKID_SD_EMMC_B_CLK0_DIV 66 +#define CLKID_SD_EMMC_C_CLK0_SEL 67 +#define CLKID_SD_EMMC_C_CLK0_DIV 68 +#define CLKID_MPLL0_DIV 69 +#define CLKID_MPLL1_DIV 70 +#define CLKID_MPLL2_DIV 71 +#define CLKID_MPLL3_DIV 72 +#define CLKID_MPLL_PREDIV 73 +#define CLKID_HIFI_PLL 74 +#define CLKID_FCLK_DIV2_DIV 75 +#define CLKID_FCLK_DIV3_DIV 76 +#define CLKID_FCLK_DIV4_DIV 77 +#define CLKID_FCLK_DIV5_DIV 78 +#define CLKID_FCLK_DIV7_DIV 79 +#define CLKID_VCLK2_VENCI0 80 +#define CLKID_VCLK2_VENCI1 81 +#define CLKID_VCLK2_VENCP0 82 +#define CLKID_VCLK2_VENCP1 83 +#define CLKID_VCLK2_VENCT0 84 +#define CLKID_VCLK2_VENCT1 85 +#define CLKID_VCLK2_OTHER 86 +#define CLKID_VCLK2_ENCI 87 +#define CLKID_VCLK2_ENCP 88 +#define CLKID_DAC_CLK 89 +#define CLKID_AOCLK 90 +#define CLKID_IEC958 91 +#define CLKID_ENC480P 92 +#define CLKID_RNG1 93 +#define CLKID_VCLK2_ENCT 94 +#define CLKID_VCLK2_ENCL 95 +#define CLKID_VCLK2_VENCLMMC 96 +#define CLKID_VCLK2_VENCL 97 +#define CLKID_VCLK2_OTHER1 98 +#define CLKID_FCLK_DIV2P5 99 +#define CLKID_FCLK_DIV2P5_DIV 100 +#define CLKID_FIXED_PLL_DCO 101 +#define CLKID_SYS_PLL_DCO 102 +#define CLKID_GP0_PLL_DCO 103 +#define CLKID_HIFI_PLL_DCO 104 +#define CLKID_DMA 105 +#define CLKID_EFUSE 106 +#define CLKID_ROM_BOOT 107 +#define CLKID_RESET_SEC 108 +#define CLKID_SEC_AHB_APB3 109 +#define CLKID_VPU_0_SEL 110 +#define CLKID_VPU_0_DIV 111 +#define CLKID_VPU_0 112 +#define CLKID_VPU_1_SEL 113 +#define CLKID_VPU_1_DIV 114 +#define CLKID_VPU_1 115 +#define CLKID_VPU 116 +#define CLKID_VAPB_0_SEL 117 +#define CLKID_VAPB_0_DIV 118 +#define CLKID_VAPB_0 119 +#define CLKID_VAPB_1_SEL 120 +#define CLKID_VAPB_1_DIV 121 +#define CLKID_VAPB_1 122 +#define CLKID_VAPB_SEL 123 +#define CLKID_VAPB 124 +#define CLKID_HDMI_PLL_DCO 125 +#define CLKID_HDMI_PLL_OD 126 +#define CLKID_HDMI_PLL_OD2 127 +#define CLKID_HDMI_PLL 128 +#define CLKID_VID_PLL 129 +#define CLKID_VID_PLL_SEL 130 +#define CLKID_VID_PLL_DIV 131 +#define CLKID_VCLK_SEL 132 +#define CLKID_VCLK2_SEL 133 +#define CLKID_VCLK_INPUT 134 +#define CLKID_VCLK2_INPUT 135 +#define CLKID_VCLK_DIV 136 +#define CLKID_VCLK2_DIV 137 +#define CLKID_VCLK 138 +#define CLKID_VCLK2 139 +#define CLKID_VCLK_DIV2_EN 140 +#define CLKID_VCLK_DIV4_EN 141 +#define CLKID_VCLK_DIV6_EN 142 +#define CLKID_VCLK_DIV12_EN 143 +#define CLKID_VCLK2_DIV2_EN 144 +#define CLKID_VCLK2_DIV4_EN 145 +#define CLKID_VCLK2_DIV6_EN 146 +#define CLKID_VCLK2_DIV12_EN 147 +#define CLKID_VCLK_DIV1 148 +#define CLKID_VCLK_DIV2 149 +#define CLKID_VCLK_DIV4 150 +#define CLKID_VCLK_DIV6 151 +#define CLKID_VCLK_DIV12 152 +#define CLKID_VCLK2_DIV1 153 +#define CLKID_VCLK2_DIV2 154 +#define CLKID_VCLK2_DIV4 155 +#define CLKID_VCLK2_DIV6 156 +#define CLKID_VCLK2_DIV12 157 +#define CLKID_CTS_ENCI_SEL 158 +#define CLKID_CTS_ENCP_SEL 159 +#define CLKID_CTS_VDAC_SEL 160 +#define CLKID_HDMI_TX_SEL 161 +#define CLKID_CTS_ENCI 162 +#define CLKID_CTS_ENCP 163 +#define CLKID_CTS_VDAC 164 +#define CLKID_HDMI_TX 165 +#define CLKID_HDMI_SEL 166 +#define CLKID_HDMI_DIV 167 +#define CLKID_HDMI 168 +#define CLKID_MALI_0_SEL 169 +#define CLKID_MALI_0_DIV 170 +#define CLKID_MALI_0 171 +#define CLKID_MALI_1_SEL 172 +#define CLKID_MALI_1_DIV 173 +#define CLKID_MALI_1 174 +#define CLKID_MALI 175 +#define CLKID_MPLL_50M_DIV 176 +#define CLKID_MPLL_50M 177 +#define CLKID_SYS_PLL_DIV16_EN 178 +#define CLKID_SYS_PLL_DIV16 179 +#define CLKID_CPU_CLK_DYN0_SEL 180 +#define CLKID_CPU_CLK_DYN0_DIV 181 +#define CLKID_CPU_CLK_DYN0 182 +#define CLKID_CPU_CLK_DYN1_SEL 183 +#define CLKID_CPU_CLK_DYN1_DIV 184 +#define CLKID_CPU_CLK_DYN1 185 +#define CLKID_CPU_CLK_DYN 186 +#define CLKID_CPU_CLK 187 +#define CLKID_CPU_CLK_DIV16_EN 188 +#define CLKID_CPU_CLK_DIV16 189 +#define CLKID_CPU_CLK_APB_DIV 190 +#define CLKID_CPU_CLK_APB 191 +#define CLKID_CPU_CLK_ATB_DIV 192 +#define CLKID_CPU_CLK_ATB 193 +#define CLKID_CPU_CLK_AXI_DIV 194 +#define CLKID_CPU_CLK_AXI 195 +#define CLKID_CPU_CLK_TRACE_DIV 196 +#define CLKID_CPU_CLK_TRACE 197 +#define CLKID_PCIE_PLL_DCO 198 +#define CLKID_PCIE_PLL_DCO_DIV2 199 +#define CLKID_PCIE_PLL_OD 200 +#define CLKID_PCIE_PLL 201 +#define CLKID_VDEC_1_SEL 202 +#define CLKID_VDEC_1_DIV 203 +#define CLKID_VDEC_1 204 +#define CLKID_VDEC_HEVC_SEL 205 +#define CLKID_VDEC_HEVC_DIV 206 +#define CLKID_VDEC_HEVC 207 +#define CLKID_VDEC_HEVCF_SEL 208 +#define CLKID_VDEC_HEVCF_DIV 209 +#define CLKID_VDEC_HEVCF 210 +#define CLKID_TS_DIV 211 +#define CLKID_TS 212 +#define CLKID_SYS1_PLL_DCO 213 +#define CLKID_SYS1_PLL 214 +#define CLKID_SYS1_PLL_DIV16_EN 215 +#define CLKID_SYS1_PLL_DIV16 216 +#define CLKID_CPUB_CLK_DYN0_SEL 217 +#define CLKID_CPUB_CLK_DYN0_DIV 218 +#define CLKID_CPUB_CLK_DYN0 219 +#define CLKID_CPUB_CLK_DYN1_SEL 220 +#define CLKID_CPUB_CLK_DYN1_DIV 221 +#define CLKID_CPUB_CLK_DYN1 222 +#define CLKID_CPUB_CLK_DYN 223 +#define CLKID_CPUB_CLK 224 +#define CLKID_CPUB_CLK_DIV16_EN 225 +#define CLKID_CPUB_CLK_DIV16 226 +#define CLKID_CPUB_CLK_DIV2 227 +#define CLKID_CPUB_CLK_DIV3 228 +#define CLKID_CPUB_CLK_DIV4 229 +#define CLKID_CPUB_CLK_DIV5 230 +#define CLKID_CPUB_CLK_DIV6 231 +#define CLKID_CPUB_CLK_DIV7 232 +#define CLKID_CPUB_CLK_DIV8 233 +#define CLKID_CPUB_CLK_APB_SEL 234 +#define CLKID_CPUB_CLK_APB 235 +#define CLKID_CPUB_CLK_ATB_SEL 236 +#define CLKID_CPUB_CLK_ATB 237 +#define CLKID_CPUB_CLK_AXI_SEL 238 +#define CLKID_CPUB_CLK_AXI 239 +#define CLKID_CPUB_CLK_TRACE_SEL 240 +#define CLKID_CPUB_CLK_TRACE 241 +#define CLKID_GP1_PLL_DCO 242 +#define CLKID_GP1_PLL 243 +#define CLKID_DSU_CLK_DYN0_SEL 244 +#define CLKID_DSU_CLK_DYN0_DIV 245 +#define CLKID_DSU_CLK_DYN0 246 +#define CLKID_DSU_CLK_DYN1_SEL 247 +#define CLKID_DSU_CLK_DYN1_DIV 248 +#define CLKID_DSU_CLK_DYN1 249 +#define CLKID_DSU_CLK_DYN 250 +#define CLKID_DSU_CLK_FINAL 251 +#define CLKID_DSU_CLK 252 +#define CLKID_CPU1_CLK 253 +#define CLKID_CPU2_CLK 254 +#define CLKID_CPU3_CLK 255 +#define CLKID_SPICC0_SCLK_SEL 256 +#define CLKID_SPICC0_SCLK_DIV 257 +#define CLKID_SPICC0_SCLK 258 +#define CLKID_SPICC1_SCLK_SEL 259 +#define CLKID_SPICC1_SCLK_DIV 260 +#define CLKID_SPICC1_SCLK 261 +#define CLKID_NNA_AXI_CLK_SEL 262 +#define CLKID_NNA_AXI_CLK_DIV 263 +#define CLKID_NNA_AXI_CLK 264 +#define CLKID_NNA_CORE_CLK_SEL 265 +#define CLKID_NNA_CORE_CLK_DIV 266 +#define CLKID_NNA_CORE_CLK 267 +#define CLKID_MIPI_DSI_PXCLK_DIV 268 +#define CLKID_MIPI_DSI_PXCLK_SEL 269 +#define CLKID_MIPI_DSI_PXCLK 270 +#define CLKID_CTS_ENCL 271 +#define CLKID_CTS_ENCL_SEL 272 +#define CLKID_MIPI_ISP_DIV 273 +#define CLKID_MIPI_ISP_SEL 274 +#define CLKID_MIPI_ISP 275 +#define CLKID_MIPI_ISP_GATE 276 +#define CLKID_MIPI_ISP_CSI_PHY0 277 +#define CLKID_MIPI_ISP_CSI_PHY1 278 + +#define CLKID_G12A_XTAL 279 diff --git a/include/sddf/clk/imx8mq-bindings.h b/include/sddf/clk/imx8mq-bindings.h new file mode 100644 index 000000000..5af8332ce --- /dev/null +++ b/include/sddf/clk/imx8mq-bindings.h @@ -0,0 +1,430 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2016 Freescale Semiconductor, Inc. + * Copyright 2017 NXP + * + * Source: https://github.com/torvalds/linux/blob/master/include/dt-bindings/clock/imx8mq-clock.h + */ + +#pragma once + +#define IMX8MQ_CLK_DUMMY 0 +#define IMX8MQ_CLK_32K 1 +#define IMX8MQ_CLK_25M 2 +#define IMX8MQ_CLK_27M 3 +#define IMX8MQ_CLK_EXT1 4 +#define IMX8MQ_CLK_EXT2 5 +#define IMX8MQ_CLK_EXT3 6 +#define IMX8MQ_CLK_EXT4 7 + +/* ANAMIX PLL clocks */ +/* FRAC PLLs */ +/* ARM PLL */ +#define IMX8MQ_ARM_PLL_REF_SEL 8 +#define IMX8MQ_ARM_PLL_REF_DIV 9 +#define IMX8MQ_ARM_PLL 10 +#define IMX8MQ_ARM_PLL_BYPASS 11 +#define IMX8MQ_ARM_PLL_OUT 12 + +/* GPU PLL */ +#define IMX8MQ_GPU_PLL_REF_SEL 13 +#define IMX8MQ_GPU_PLL_REF_DIV 14 +#define IMX8MQ_GPU_PLL 15 +#define IMX8MQ_GPU_PLL_BYPASS 16 +#define IMX8MQ_GPU_PLL_OUT 17 + +/* VPU PLL */ +#define IMX8MQ_VPU_PLL_REF_SEL 18 +#define IMX8MQ_VPU_PLL_REF_DIV 19 +#define IMX8MQ_VPU_PLL 20 +#define IMX8MQ_VPU_PLL_BYPASS 21 +#define IMX8MQ_VPU_PLL_OUT 22 + +/* AUDIO PLL1 */ +#define IMX8MQ_AUDIO_PLL1_REF_SEL 23 +#define IMX8MQ_AUDIO_PLL1_REF_DIV 24 +#define IMX8MQ_AUDIO_PLL1 25 +#define IMX8MQ_AUDIO_PLL1_BYPASS 26 +#define IMX8MQ_AUDIO_PLL1_OUT 27 + +/* AUDIO PLL2 */ +#define IMX8MQ_AUDIO_PLL2_REF_SEL 28 +#define IMX8MQ_AUDIO_PLL2_REF_DIV 29 +#define IMX8MQ_AUDIO_PLL2 30 +#define IMX8MQ_AUDIO_PLL2_BYPASS 31 +#define IMX8MQ_AUDIO_PLL2_OUT 32 + +/* VIDEO PLL1 */ +#define IMX8MQ_VIDEO_PLL1_REF_SEL 33 +#define IMX8MQ_VIDEO_PLL1_REF_DIV 34 +#define IMX8MQ_VIDEO_PLL1 35 +#define IMX8MQ_VIDEO_PLL1_BYPASS 36 +#define IMX8MQ_VIDEO_PLL1_OUT 37 + +/* SYS1 PLL */ +#define IMX8MQ_SYS1_PLL1_REF_SEL 38 +#define IMX8MQ_SYS1_PLL1_REF_DIV 39 +#define IMX8MQ_SYS1_PLL1 40 +#define IMX8MQ_SYS1_PLL1_OUT 41 +#define IMX8MQ_SYS1_PLL1_OUT_DIV 42 +#define IMX8MQ_SYS1_PLL2 43 +#define IMX8MQ_SYS1_PLL2_DIV 44 +#define IMX8MQ_SYS1_PLL2_OUT 45 + +/* SYS2 PLL */ +#define IMX8MQ_SYS2_PLL1_REF_SEL 46 +#define IMX8MQ_SYS2_PLL1_REF_DIV 47 +#define IMX8MQ_SYS2_PLL1 48 +#define IMX8MQ_SYS2_PLL1_OUT 49 +#define IMX8MQ_SYS2_PLL1_OUT_DIV 50 +#define IMX8MQ_SYS2_PLL2 51 +#define IMX8MQ_SYS2_PLL2_DIV 52 +#define IMX8MQ_SYS2_PLL2_OUT 53 + +/* SYS3 PLL */ +#define IMX8MQ_SYS3_PLL1_REF_SEL 54 +#define IMX8MQ_SYS3_PLL1_REF_DIV 55 +#define IMX8MQ_SYS3_PLL1 56 +#define IMX8MQ_SYS3_PLL1_OUT 57 +#define IMX8MQ_SYS3_PLL1_OUT_DIV 58 +#define IMX8MQ_SYS3_PLL2 59 +#define IMX8MQ_SYS3_PLL2_DIV 60 +#define IMX8MQ_SYS3_PLL2_OUT 61 + +/* DRAM PLL */ +#define IMX8MQ_DRAM_PLL1_REF_SEL 62 +#define IMX8MQ_DRAM_PLL1_REF_DIV 63 +#define IMX8MQ_DRAM_PLL1 64 +#define IMX8MQ_DRAM_PLL1_OUT 65 +#define IMX8MQ_DRAM_PLL1_OUT_DIV 66 +#define IMX8MQ_DRAM_PLL2 67 +#define IMX8MQ_DRAM_PLL2_DIV 68 +#define IMX8MQ_DRAM_PLL2_OUT 69 + +/* SYS PLL DIV */ +#define IMX8MQ_SYS1_PLL_40M 70 +#define IMX8MQ_SYS1_PLL_80M 71 +#define IMX8MQ_SYS1_PLL_100M 72 +#define IMX8MQ_SYS1_PLL_133M 73 +#define IMX8MQ_SYS1_PLL_160M 74 +#define IMX8MQ_SYS1_PLL_200M 75 +#define IMX8MQ_SYS1_PLL_266M 76 +#define IMX8MQ_SYS1_PLL_400M 77 +#define IMX8MQ_SYS1_PLL_800M 78 + +#define IMX8MQ_SYS2_PLL_50M 79 +#define IMX8MQ_SYS2_PLL_100M 80 +#define IMX8MQ_SYS2_PLL_125M 81 +#define IMX8MQ_SYS2_PLL_166M 82 +#define IMX8MQ_SYS2_PLL_200M 83 +#define IMX8MQ_SYS2_PLL_250M 84 +#define IMX8MQ_SYS2_PLL_333M 85 +#define IMX8MQ_SYS2_PLL_500M 86 +#define IMX8MQ_SYS2_PLL_1000M 87 + +/* CCM ROOT clocks */ +/* A53 */ +#define IMX8MQ_CLK_A53_SRC 88 +#define IMX8MQ_CLK_A53_CG 89 +#define IMX8MQ_CLK_A53_DIV 90 +/* M4 */ +#define IMX8MQ_CLK_M4_SRC 91 +#define IMX8MQ_CLK_M4_CG 92 +#define IMX8MQ_CLK_M4_DIV 93 +/* VPU */ +#define IMX8MQ_CLK_VPU_SRC 94 +#define IMX8MQ_CLK_VPU_CG 95 +#define IMX8MQ_CLK_VPU_DIV 96 +/* GPU CORE */ +#define IMX8MQ_CLK_GPU_CORE_SRC 97 +#define IMX8MQ_CLK_GPU_CORE_CG 98 +#define IMX8MQ_CLK_GPU_CORE_DIV 99 +/* GPU SHADER */ +#define IMX8MQ_CLK_GPU_SHADER_SRC 100 +#define IMX8MQ_CLK_GPU_SHADER_CG 101 +#define IMX8MQ_CLK_GPU_SHADER_DIV 102 + +/* BUS TYPE */ +/* MAIN AXI */ +#define IMX8MQ_CLK_MAIN_AXI 103 +/* ENET AXI */ +#define IMX8MQ_CLK_ENET_AXI 104 +/* NAND_USDHC_BUS */ +#define IMX8MQ_CLK_NAND_USDHC_BUS 105 +/* VPU BUS */ +#define IMX8MQ_CLK_VPU_BUS 106 +/* DISP_AXI */ +#define IMX8MQ_CLK_DISP_AXI 107 +/* DISP APB */ +#define IMX8MQ_CLK_DISP_APB 108 +/* DISP RTRM */ +#define IMX8MQ_CLK_DISP_RTRM 109 +/* USB_BUS */ +#define IMX8MQ_CLK_USB_BUS 110 +/* GPU_AXI */ +#define IMX8MQ_CLK_GPU_AXI 111 +/* GPU_AHB */ +#define IMX8MQ_CLK_GPU_AHB 112 +/* NOC */ +#define IMX8MQ_CLK_NOC 113 +/* NOC_APB */ +#define IMX8MQ_CLK_NOC_APB 115 + +/* AHB */ +#define IMX8MQ_CLK_AHB 116 +/* AUDIO AHB */ +#define IMX8MQ_CLK_AUDIO_AHB 117 + +/* DRAM_ALT */ +#define IMX8MQ_CLK_DRAM_ALT 118 +/* DRAM APB */ +#define IMX8MQ_CLK_DRAM_APB 119 +/* VPU_G1 */ +#define IMX8MQ_CLK_VPU_G1 120 +/* VPU_G2 */ +#define IMX8MQ_CLK_VPU_G2 121 +/* DISP_DTRC */ +#define IMX8MQ_CLK_DISP_DTRC 122 +/* DISP_DC8000 */ +#define IMX8MQ_CLK_DISP_DC8000 123 +/* PCIE_CTRL */ +#define IMX8MQ_CLK_PCIE1_CTRL 124 +/* PCIE_PHY */ +#define IMX8MQ_CLK_PCIE1_PHY 125 +/* PCIE_AUX */ +#define IMX8MQ_CLK_PCIE1_AUX 126 +/* DC_PIXEL */ +#define IMX8MQ_CLK_DC_PIXEL 127 +/* LCDIF_PIXEL */ +#define IMX8MQ_CLK_LCDIF_PIXEL 128 +/* SAI1~6 */ +#define IMX8MQ_CLK_SAI1 129 + +#define IMX8MQ_CLK_SAI2 130 + +#define IMX8MQ_CLK_SAI3 131 + +#define IMX8MQ_CLK_SAI4 132 + +#define IMX8MQ_CLK_SAI5 133 + +#define IMX8MQ_CLK_SAI6 134 +/* SPDIF1 */ +#define IMX8MQ_CLK_SPDIF1 135 +/* SPDIF2 */ +#define IMX8MQ_CLK_SPDIF2 136 +/* ENET_REF */ +#define IMX8MQ_CLK_ENET_REF 137 +/* ENET_TIMER */ +#define IMX8MQ_CLK_ENET_TIMER 138 +/* ENET_PHY */ +#define IMX8MQ_CLK_ENET_PHY_REF 139 +/* NAND */ +#define IMX8MQ_CLK_NAND 140 +/* QSPI */ +#define IMX8MQ_CLK_QSPI 141 +/* USDHC1 */ +#define IMX8MQ_CLK_USDHC1 142 +/* USDHC2 */ +#define IMX8MQ_CLK_USDHC2 143 +/* I2C1 */ +#define IMX8MQ_CLK_I2C1 144 +/* I2C2 */ +#define IMX8MQ_CLK_I2C2 145 +/* I2C3 */ +#define IMX8MQ_CLK_I2C3 146 +/* I2C4 */ +#define IMX8MQ_CLK_I2C4 147 +/* UART1 */ +#define IMX8MQ_CLK_UART1 148 +/* UART2 */ +#define IMX8MQ_CLK_UART2 149 +/* UART3 */ +#define IMX8MQ_CLK_UART3 150 +/* UART4 */ +#define IMX8MQ_CLK_UART4 151 +/* USB_CORE_REF */ +#define IMX8MQ_CLK_USB_CORE_REF 152 +/* USB_PHY_REF */ +#define IMX8MQ_CLK_USB_PHY_REF 153 +/* ECSPI1 */ +#define IMX8MQ_CLK_ECSPI1 154 +/* ECSPI2 */ +#define IMX8MQ_CLK_ECSPI2 155 +/* PWM1 */ +#define IMX8MQ_CLK_PWM1 156 +/* PWM2 */ +#define IMX8MQ_CLK_PWM2 157 +/* PWM3 */ +#define IMX8MQ_CLK_PWM3 158 +/* PWM4 */ +#define IMX8MQ_CLK_PWM4 159 +/* GPT1 */ +#define IMX8MQ_CLK_GPT1 160 +/* WDOG */ +#define IMX8MQ_CLK_WDOG 161 +/* WRCLK */ +#define IMX8MQ_CLK_WRCLK 162 +/* DSI_CORE */ +#define IMX8MQ_CLK_DSI_CORE 163 +/* DSI_PHY */ +#define IMX8MQ_CLK_DSI_PHY_REF 164 +/* DSI_DBI */ +#define IMX8MQ_CLK_DSI_DBI 165 +/*DSI_ESC */ +#define IMX8MQ_CLK_DSI_ESC 166 +/* CSI1_CORE */ +#define IMX8MQ_CLK_CSI1_CORE 167 +/* CSI1_PHY */ +#define IMX8MQ_CLK_CSI1_PHY_REF 168 +/* CSI_ESC */ +#define IMX8MQ_CLK_CSI1_ESC 169 +/* CSI2_CORE */ +#define IMX8MQ_CLK_CSI2_CORE 170 +/* CSI2_PHY */ +#define IMX8MQ_CLK_CSI2_PHY_REF 171 +/* CSI2_ESC */ +#define IMX8MQ_CLK_CSI2_ESC 172 +/* PCIE2_CTRL */ +#define IMX8MQ_CLK_PCIE2_CTRL 173 +/* PCIE2_PHY */ +#define IMX8MQ_CLK_PCIE2_PHY 174 +/* PCIE2_AUX */ +#define IMX8MQ_CLK_PCIE2_AUX 175 +/* ECSPI3 */ +#define IMX8MQ_CLK_ECSPI3 176 + +/* CCGR clocks */ +#define IMX8MQ_CLK_A53_ROOT 177 +#define IMX8MQ_CLK_DRAM_ROOT 178 +#define IMX8MQ_CLK_ECSPI1_ROOT 179 +#define IMX8MQ_CLK_ECSPI2_ROOT 180 +#define IMX8MQ_CLK_ECSPI3_ROOT 181 +#define IMX8MQ_CLK_ENET1_ROOT 182 +#define IMX8MQ_CLK_GPT1_ROOT 183 +#define IMX8MQ_CLK_I2C1_ROOT 184 +#define IMX8MQ_CLK_I2C2_ROOT 185 +#define IMX8MQ_CLK_I2C3_ROOT 186 +#define IMX8MQ_CLK_I2C4_ROOT 187 +#define IMX8MQ_CLK_M4_ROOT 188 +#define IMX8MQ_CLK_PCIE1_ROOT 189 +#define IMX8MQ_CLK_PCIE2_ROOT 190 +#define IMX8MQ_CLK_PWM1_ROOT 191 +#define IMX8MQ_CLK_PWM2_ROOT 192 +#define IMX8MQ_CLK_PWM3_ROOT 193 +#define IMX8MQ_CLK_PWM4_ROOT 194 +#define IMX8MQ_CLK_QSPI_ROOT 195 +#define IMX8MQ_CLK_SAI1_ROOT 196 +#define IMX8MQ_CLK_SAI2_ROOT 197 +#define IMX8MQ_CLK_SAI3_ROOT 198 +#define IMX8MQ_CLK_SAI4_ROOT 199 +#define IMX8MQ_CLK_SAI5_ROOT 200 +#define IMX8MQ_CLK_SAI6_ROOT 201 +#define IMX8MQ_CLK_UART1_ROOT 202 +#define IMX8MQ_CLK_UART2_ROOT 203 +#define IMX8MQ_CLK_UART3_ROOT 204 +#define IMX8MQ_CLK_UART4_ROOT 205 +#define IMX8MQ_CLK_USB1_CTRL_ROOT 206 +#define IMX8MQ_CLK_USB2_CTRL_ROOT 207 +#define IMX8MQ_CLK_USB1_PHY_ROOT 208 +#define IMX8MQ_CLK_USB2_PHY_ROOT 209 +#define IMX8MQ_CLK_USDHC1_ROOT 210 +#define IMX8MQ_CLK_USDHC2_ROOT 211 +#define IMX8MQ_CLK_WDOG1_ROOT 212 +#define IMX8MQ_CLK_WDOG2_ROOT 213 +#define IMX8MQ_CLK_WDOG3_ROOT 214 +#define IMX8MQ_CLK_GPU_ROOT 215 +#define IMX8MQ_CLK_HEVC_ROOT 216 +#define IMX8MQ_CLK_AVC_ROOT 217 +#define IMX8MQ_CLK_VP9_ROOT 218 +#define IMX8MQ_CLK_HEVC_INTER_ROOT 219 +#define IMX8MQ_CLK_DISP_ROOT 220 +#define IMX8MQ_CLK_HDMI_ROOT 221 +#define IMX8MQ_CLK_HDMI_PHY_ROOT 222 +#define IMX8MQ_CLK_VPU_DEC_ROOT 223 +#define IMX8MQ_CLK_CSI1_ROOT 224 +#define IMX8MQ_CLK_CSI2_ROOT 225 +#define IMX8MQ_CLK_RAWNAND_ROOT 226 +#define IMX8MQ_CLK_SDMA1_ROOT 227 +#define IMX8MQ_CLK_SDMA2_ROOT 228 +#define IMX8MQ_CLK_VPU_G1_ROOT 229 +#define IMX8MQ_CLK_VPU_G2_ROOT 230 + +/* SCCG PLL GATE */ +#define IMX8MQ_SYS1_PLL_OUT 231 +#define IMX8MQ_SYS2_PLL_OUT 232 +#define IMX8MQ_SYS3_PLL_OUT 233 +#define IMX8MQ_DRAM_PLL_OUT 234 + +#define IMX8MQ_GPT_3M_CLK 235 + +#define IMX8MQ_CLK_IPG_ROOT 236 +#define IMX8MQ_CLK_IPG_AUDIO_ROOT 237 +#define IMX8MQ_CLK_SAI1_IPG 238 +#define IMX8MQ_CLK_SAI2_IPG 239 +#define IMX8MQ_CLK_SAI3_IPG 240 +#define IMX8MQ_CLK_SAI4_IPG 241 +#define IMX8MQ_CLK_SAI5_IPG 242 +#define IMX8MQ_CLK_SAI6_IPG 243 + +/* DSI AHB/IPG clocks */ +/* rxesc clock */ +#define IMX8MQ_CLK_DSI_AHB 244 +/* txesc clock */ +#define IMX8MQ_CLK_DSI_IPG_DIV 245 + +#define IMX8MQ_CLK_TMU_ROOT 246 + +/* Display root clocks */ +#define IMX8MQ_CLK_DISP_AXI_ROOT 247 +#define IMX8MQ_CLK_DISP_APB_ROOT 248 +#define IMX8MQ_CLK_DISP_RTRM_ROOT 249 + +#define IMX8MQ_CLK_OCOTP_ROOT 250 + +#define IMX8MQ_CLK_DRAM_ALT_ROOT 251 +#define IMX8MQ_CLK_DRAM_CORE 252 + +#define IMX8MQ_CLK_MU_ROOT 253 +#define IMX8MQ_VIDEO2_PLL_OUT 254 + +#define IMX8MQ_CLK_CLKO2 255 + +#define IMX8MQ_CLK_NAND_USDHC_BUS_RAWNAND_CLK 256 + +#define IMX8MQ_CLK_CLKO1 257 +#define IMX8MQ_CLK_ARM 258 + +#define IMX8MQ_CLK_GPIO1_ROOT 259 +#define IMX8MQ_CLK_GPIO2_ROOT 260 +#define IMX8MQ_CLK_GPIO3_ROOT 261 +#define IMX8MQ_CLK_GPIO4_ROOT 262 +#define IMX8MQ_CLK_GPIO5_ROOT 263 + +#define IMX8MQ_CLK_SNVS_ROOT 264 +#define IMX8MQ_CLK_GIC 265 + +#define IMX8MQ_VIDEO2_PLL1_REF_SEL 266 + +#define IMX8MQ_CLK_GPU_CORE 285 +#define IMX8MQ_CLK_GPU_SHADER 286 +#define IMX8MQ_CLK_M4_CORE 287 +#define IMX8MQ_CLK_VPU_CORE 288 + +#define IMX8MQ_CLK_A53_CORE 289 + +#define IMX8MQ_CLK_MON_AUDIO_PLL1_DIV 290 +#define IMX8MQ_CLK_MON_AUDIO_PLL2_DIV 291 +#define IMX8MQ_CLK_MON_VIDEO_PLL1_DIV 292 +#define IMX8MQ_CLK_MON_GPU_PLL_DIV 293 +#define IMX8MQ_CLK_MON_VPU_PLL_DIV 294 +#define IMX8MQ_CLK_MON_ARM_PLL_DIV 295 +#define IMX8MQ_CLK_MON_SYS_PLL1_DIV 296 +#define IMX8MQ_CLK_MON_SYS_PLL2_DIV 297 +#define IMX8MQ_CLK_MON_SYS_PLL3_DIV 298 +#define IMX8MQ_CLK_MON_DRAM_PLL_DIV 299 +#define IMX8MQ_CLK_MON_VIDEO_PLL2_DIV 300 +#define IMX8MQ_CLK_MON_SEL 301 +#define IMX8MQ_CLK_MON_CLK2_OUT 302 + +#define IMX8MQ_CLK_END 303 diff --git a/include/sddf/clk/protocol.h b/include/sddf/clk/protocol.h new file mode 100644 index 000000000..781483ced --- /dev/null +++ b/include/sddf/clk/protocol.h @@ -0,0 +1,31 @@ +/* + * Copyright 2024, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once +#include + +/* Shared functionality/definitions between pinctrl drivers and clients */ + +#ifdef ODROID_C4 +#include +#endif + +#define SDDF_CLK_ENABLE 0 +#define SDDF_CLK_DISABLE 1 +#define SDDF_CLK_GET_RATE 2 +#define SDDF_CLK_SET_RATE 3 +#define SDDF_CLK_GET_PARENT 4 +#define SDDF_CLK_SET_PARENT 5 + +#define SDDF_CLK_PARAM_ID 0 +#define SDDF_CLK_PARAM_RATE 1 + +#define SDDF_CLK_PARAM_PCLK_IDX 1 + +struct clk_cfg { + uint32_t clk_id; + uint32_t frequency; + uint32_t pclk_id; +}; diff --git a/tools/make/board/maaxboard.mk b/tools/make/board/maaxboard.mk index 5c9372dfb..8af9eb3fe 100644 --- a/tools/make/board/maaxboard.mk +++ b/tools/make/board/maaxboard.mk @@ -13,5 +13,6 @@ NET_DRIV_DIR := ${PLATFORM} ETH_DRIV := eth_driver_${PLATFORM}.elf TIMER_DRIV_DIR := ${PLATFORM} UART_DRIV_DIR := ${PLATFORM} +CLK_DRIV_DIR := ${PLATFORM} CPU := cortex-a53 diff --git a/tools/make/board/odroidc4.mk b/tools/make/board/odroidc4.mk index 873a547bd..e36a9b6ae 100644 --- a/tools/make/board/odroidc4.mk +++ b/tools/make/board/odroidc4.mk @@ -12,5 +12,6 @@ NET_DRIV_DIR := ${PLATFORM} ETH_DRIV := eth_driver_meson.elf TIMER_DRIV_DIR := ${PLATFORM} UART_DRIV_DIR := ${PLATFORM} +CLK_DRIV_DIR := ${PLATFORM} CPU := cortex-a55 From f657f93fb16a7ad58cc7f2fd4f9339f5238b0d42 Mon Sep 17 00:00:00 2001 From: Julia Vassiliki Date: Tue, 10 Mar 2026 13:31:52 +1100 Subject: [PATCH 2/9] pwm: init example Signed-off-by: Julia Vassiliki --- drivers/pwm/imx/README.md | 43 ++++++++++++++++ drivers/pwm/imx/pwm.c | 48 ++++++++++++++++++ drivers/pwm/imx/pwm.h | 5 ++ drivers/pwm/imx/pwm_driver.mk | 28 +++++++++++ examples/pwm/Makefile | 32 ++++++++++++ examples/pwm/README.md | 15 ++++++ examples/pwm/client.c | 41 ++++++++++++++++ examples/pwm/meta.py | 92 +++++++++++++++++++++++++++++++++++ examples/pwm/pwm.mk | 85 ++++++++++++++++++++++++++++++++ tools/make/board/maaxboard.mk | 1 + 10 files changed, 390 insertions(+) create mode 100644 drivers/pwm/imx/README.md create mode 100644 drivers/pwm/imx/pwm.c create mode 100644 drivers/pwm/imx/pwm.h create mode 100644 drivers/pwm/imx/pwm_driver.mk create mode 100644 examples/pwm/Makefile create mode 100644 examples/pwm/README.md create mode 100644 examples/pwm/client.c create mode 100644 examples/pwm/meta.py create mode 100644 examples/pwm/pwm.mk diff --git a/drivers/pwm/imx/README.md b/drivers/pwm/imx/README.md new file mode 100644 index 000000000..69b1b4646 --- /dev/null +++ b/drivers/pwm/imx/README.md @@ -0,0 +1,43 @@ +# Freescale i.MX PWM controller + +Implementation of `linux/Documentation/devicetree/bindings/pwm/pwm-imx.yaml` driver. + +Specifically the 'fsl,imx8mq-pwm' driver implemented on the MaaXboard. + + +MaaXboard has under `iomuxc@30330000`: + +``` +pwm2_grp { + fsl,pins = <0x5c 0x2c4 0x00 0x05 0x00 0x06>; + phandle = <0x1f>; +}; + +pwm4_grp { + fsl,pins = <0x64 0x2cc 0x00 0x05 0x00 0x06>; + phandle = <0x20>; + }; +``` + + +chapter 8 chip io and pinmux + +PWM + PWM2_OUT I2C4_SCL (ALT1) / SPDIF_RX (ALT1) / GPIO1_IO13 (ALT5) + PWM4_OUT I2C3_SCL (ALT1) / SAI3_MCLK (ALT1) / GPIO1_IO15 (ALT5) + +using pwm2 + +``` + + pwm2: pwm@30670000 { + compatible = "fsl,imx8mq-pwm", "fsl,imx27-pwm"; + reg = <0x30670000 0x10000>; + interrupts = ; + clocks = <&clk IMX8MQ_CLK_PWM2_ROOT>, + <&clk IMX8MQ_CLK_PWM2_ROOT>; + clock-names = "ipg", "per"; + #pwm-cells = <3>; + status = "disabled"; + }; +``` diff --git a/drivers/pwm/imx/pwm.c b/drivers/pwm/imx/pwm.c new file mode 100644 index 000000000..dc3448e85 --- /dev/null +++ b/drivers/pwm/imx/pwm.c @@ -0,0 +1,48 @@ +/* + * Copyright 2026, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "pwm.h" + +#include +#include +#include +#include + +#define DEBUG_DRIVER + +#ifdef DEBUG_DRIVER +#define LOG_DRIVER(...) do{ sddf_dprintf("PWM DRIVER|INFO: "); sddf_dprintf(__VA_ARGS__); }while(0) +#else +#define LOG_DRIVER(...) do{}while(0) +#endif + +#define LOG_DRIVER_ERR(...) do{ sddf_printf("PWM DRIVER|ERROR: "); sddf_printf(__VA_ARGS__); }while(0) + + +#define CH_PWM_IRQ 0 +#define CH_CONTROL_PPC 1 + + +void init(void) +{ + LOG_DRIVER("PWM starting\n"); +} + +void notified(microkit_channel ch) +{ + LOG_DRIVER_ERR("Unexpected notification on channel %u\n", ch); +} + +microkit_msginfo protected(microkit_channel ch, microkit_msginfo msginfo) +{ + LOG_DRIVER("ppc from channel %u\n", ch); + + switch (microkit_msginfo_get_label(msginfo)) { + default: + LOG_DRIVER("Unknown request %lu from channel %u\n", microkit_msginfo_get_label(msginfo), ch); + // XXXX. + return microkit_msginfo_new(0, 0); + } +} diff --git a/drivers/pwm/imx/pwm.h b/drivers/pwm/imx/pwm.h new file mode 100644 index 000000000..74412eef7 --- /dev/null +++ b/drivers/pwm/imx/pwm.h @@ -0,0 +1,5 @@ +/* + * Copyright 2026, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + diff --git a/drivers/pwm/imx/pwm_driver.mk b/drivers/pwm/imx/pwm_driver.mk new file mode 100644 index 000000000..36281b207 --- /dev/null +++ b/drivers/pwm/imx/pwm_driver.mk @@ -0,0 +1,28 @@ +# +# Copyright 2026, UNSW +# SPDX-License-Identifier: BSD-2-Clause +# +# Include this snippet in your project Makefile to build the PWM driver. +# Assumes libsddf_util_debug.a is in ${LIBS}. + +PWM_DRIVER_DIR := $(realpath $(dir $(lastword $(MAKEFILE_LIST)))) + +CHECK_PWM_FLAGS_MD5:=.nvme_cflags-$(shell echo -- ${CFLAGS} | shasum | sed 's/ *-//') + +${CHECK_PWM_FLAGS_MD5}: + -rm -f .pwm_cflags-* + touch $@ + +pwm_driver.elf: pwm_driver.o + $(LD) $(LDFLAGS) $^ $(LIBS) -o $@ + +pwm_driver.o: ${PWM_DRIVER_DIR}/pwm.c ${CHECK_PWM_FLAGS_MD5} + $(CC) -c $(CFLAGS) -I${PWM_DRIVER_DIR} -o $@ $< + +-include pwm_driver.d + +clean:: + rm -f pwm_driver.o + +clobber:: + rm -f pwm_driver.elf diff --git a/examples/pwm/Makefile b/examples/pwm/Makefile new file mode 100644 index 000000000..f06d836b1 --- /dev/null +++ b/examples/pwm/Makefile @@ -0,0 +1,32 @@ +# +# Copyright 2026, UNSW +# +# SPDX-License-Identifier: BSD-2-Clause +# + +ifeq ($(strip $(MICROKIT_SDK)),) +$(error MICROKIT_SDK must be specified) +endif + +ifeq ($(strip $(MICROKIT_BOARD)),) +$(error MICROKIT_BOARD must be specified) +endif +BUILD_DIR ?= build +override BUILD_DIR := $(abspath ${BUILD_DIR}) +export BUILD_DIR +export SDDF := $(abspath ../..) +override MICROKIT_SDK := $(abspath ${MICROKIT_SDK}) + +IMAGE_FILE := ${BUILD_DIR}/loader.img +REPORT_FILE := ${BUILD_DIR}/report.txt + +all: ${IMAGE_FILE} + +qemu ${IMAGE_FILE} ${REPORT_FILE} clean clobber: ${BUILD_DIR}/Makefile FORCE + ${MAKE} -C ${BUILD_DIR} MICROKIT_SDK=${MICROKIT_SDK} $(notdir $@) + +${BUILD_DIR}/Makefile: pwm.mk + mkdir -p ${BUILD_DIR} + cp pwm.mk $@ + +FORCE: diff --git a/examples/pwm/README.md b/examples/pwm/README.md new file mode 100644 index 000000000..7089507c6 --- /dev/null +++ b/examples/pwm/README.md @@ -0,0 +1,15 @@ + +# PWM example + +TODO + +### Make + +```sh +make MICROKIT_SDK= MICROKIT_BOARD= +``` + +After building, the system image to load will be `build/loader.img`. diff --git a/examples/pwm/client.c b/examples/pwm/client.c new file mode 100644 index 000000000..9413e588a --- /dev/null +++ b/examples/pwm/client.c @@ -0,0 +1,41 @@ +/* + * Copyright 2026, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +static serial_queue_handle_t serial_tx_queue_handle; +__attribute__((__section__(".serial_client_config"))) serial_client_config_t serial_config; + +#define LOG_CLIENT(...) do{ sddf_printf("CLIENT|INFO: "); sddf_printf(__VA_ARGS__); }while(0) +#define LOG_CLIENT_ERR(...) do{ sddf_printf("CLIENT|ERROR: "); sddf_printf(__VA_ARGS__); }while(0) + +#define CH_PWM_CONTROL_PPC 0 + +void init(void) +{ + assert(serial_config_check_magic(&serial_config)); + serial_queue_init(&serial_tx_queue_handle, serial_config.tx.queue.vaddr, serial_config.tx.data.size, + serial_config.tx.data.vaddr); + serial_putchar_init(serial_config.tx.id, &serial_tx_queue_handle); + + LOG_CLIENT("starting\n"); + + microkit_msginfo ret = sddf_ppcall(CH_PWM_CONTROL_PPC, microkit_msginfo_new(0, 0)); + (void)ret; + + LOG_CLIENT("done\n"); +} + +void notified(sddf_channel ch) +{ + if (ch == serial_config.tx.id) { + // Nothing to do. + } else { + LOG_CLIENT_ERR("Unknown channel 0x%x!\n", ch); + } +} diff --git a/examples/pwm/meta.py b/examples/pwm/meta.py new file mode 100644 index 000000000..bb7d1a7f0 --- /dev/null +++ b/examples/pwm/meta.py @@ -0,0 +1,92 @@ +# Copyright 2026, UNSW +# SPDX-License-Identifier: BSD-2-Clause +import os, sys +import argparse +from typing import List, Optional +from dataclasses import dataclass +from sdfgen import SystemDescription, Sddf, DeviceTree +from importlib.metadata import version + +sys.path.append( + os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../tools/meta") +) +from board import BOARDS + +assert version("sdfgen").split(".")[1] == "28", "Unexpected sdfgen version" + +ProtectionDomain = SystemDescription.ProtectionDomain +MemoryRegion = SystemDescription.MemoryRegion +Map = SystemDescription.Map +IrqConventional = SystemDescription.IrqConventional +Channel = SystemDescription.Channel + + + +def generate(sdf_file: str, output_dir: str, dtb: DeviceTree): + serial_driver = ProtectionDomain("serial_driver", "serial_driver.elf", priority=200) + # Increase the stack size as running with UBSAN uses more stack space than normal. + serial_virt_tx = ProtectionDomain( + "serial_virt_tx", "serial_virt_tx.elf", priority=199, stack_size=0x2000 + ) + + pwm_driver = ProtectionDomain("pwm_driver", "pwm_driver.elf", priority=150) + + client = ProtectionDomain("client", "client.elf", priority=0) + + serial_node = dtb.node(board.serial) + assert serial_node is not None + # pwm_nodes = [dtb.node(pwm_compatible) for pwm_compatible in board.pwm] + # assert all(pwm_node is not None for pwm_node in pwm_nodes) + + if board.name != "maaxboard": + assert False + + # HACK for sdfgen. "soc@0/bus@30400000/pwm@30670000", + pwm2_mr = MemoryRegion(sdf, "pwm2", 0x10000, paddr=0x30670000) + sdf.add_mr(pwm2_mr) + pwm_driver.add_map(Map(pwm2_mr, 0x30_000_000, "rw", cached=False)) + pwm_driver.add_irq(IrqConventional(82 + 32, IrqConventional.Trigger.EDGE)) + + chan = Channel(pwm_driver, client, pp_b=True, notify_a=False, notify_b=False) + sdf.add_channel(chan) + assert chan.pd_a_id == 1, chan.pd_a_id + assert chan.pd_b_id == 0, chan.pd_b_id + + serial_system = Sddf.Serial(sdf, serial_node, serial_driver, serial_virt_tx) + serial_system.add_client(client) + + pds = [ + serial_driver, + serial_virt_tx, + pwm_driver, + client, + ] + for pd in pds: + sdf.add_pd(pd) + + assert serial_system.connect() + assert serial_system.serialise_config(output_dir) + + with open(f"{output_dir}/{sdf_file}", "w+") as f: + f.write(sdf.render()) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--dtb", required=False) + parser.add_argument("--sddf", required=True) + parser.add_argument("--board", required=True, choices=[b.name for b in BOARDS]) + parser.add_argument("--output", required=True) + parser.add_argument("--sdf", required=True) + + args = parser.parse_args() + + board = next(filter(lambda b: b.name == args.board, BOARDS)) + + sdf = SystemDescription(board.arch, board.paddr_top) + sddf = Sddf(args.sddf) + + with open(args.dtb, "rb") as f: + dtb = DeviceTree(f.read()) + + generate(args.sdf, args.output, dtb) diff --git a/examples/pwm/pwm.mk b/examples/pwm/pwm.mk new file mode 100644 index 000000000..2259970d4 --- /dev/null +++ b/examples/pwm/pwm.mk @@ -0,0 +1,85 @@ +# +# Copyright 2026, UNSW +# +# SPDX-License-Identifier: BSD-2-Clause +# +# This Makefile is copied into the build directory +# and operated on from there. +# + +ifeq ($(strip $(MICROKIT_SDK)),) +$(error MICROKIT_SDK must be specified) +endif + +ifeq ($(strip $(SDDF)),) +$(error SDDF must be specified) +endif + +ifeq ($(strip $(TOOLCHAIN)),) + TOOLCHAIN := clang +endif + +BUILD_DIR ?= build +MICROKIT_CONFIG ?= debug + +IMAGE_FILE := loader.img +REPORT_FILE := report.txt +SYSTEM_FILE := pwm.system + +SUPPORTED_BOARDS := maaxboard + +TOP := ${SDDF}/examples/pwm +CONFIGS_INCLUDE := ${TOP} +SDDF_CUSTOM_LIBC := 1 + +include ${SDDF}/tools/make/board/common.mk + + +IMAGES := pwm_driver.elf client.elf serial_virt_tx.elf serial_driver.elf +CFLAGS += -Wall -Wno-unused-function -Werror -Wno-unused-command-line-argument \ + -I$(SDDF)/include \ + -I$(SDDF)/include/microkit \ + -I$(CONFIGS_INCLUDE) + +LDFLAGS := -L$(BOARD_DIR)/lib +LIBS := --start-group -lmicrokit -Tmicrokit.ld libsddf_util_debug.a --end-group + +METAPROGRAM := $(TOP)/meta.py + +PWM_DRIVER := $(SDDF)/drivers/pwm/${PWM_DRIV_DIR} +SERIAL_DRIVER := $(SDDF)/drivers/serial/${UART_DRIV_DIR} + +all: $(IMAGE_FILE) + +include ${SDDF}/drivers/pwm/${PWM_DRIV_DIR}/pwm_driver.mk +include ${SDDF}/drivers/serial/${UART_DRIV_DIR}/serial_driver.mk + +include ${SDDF}/util/util.mk +# include ${SDDF}/pwm/components/pwm_components.mk +include ${SDDF}/serial/components/serial_components.mk + +${IMAGES}: libsddf_util_debug.a + +client.o: ${TOP}/client.c + $(CC) -c $(CFLAGS) -I. $< -o client.o + +client.elf: client.o libsddf_util.a + $(LD) $(LDFLAGS) $^ $(LIBS) -o $@ + +$(SYSTEM_FILE): $(METAPROGRAM) $(IMAGES) $(DTB) + $(PYTHON) \ + $(METAPROGRAM) --sddf $(SDDF) --board $(MICROKIT_BOARD) \ + --dtb $(DTB) --output . --sdf $(SYSTEM_FILE) + $(OBJCOPY) --update-section .device_resources=serial_driver_device_resources.data serial_driver.elf + $(OBJCOPY) --update-section .serial_driver_config=serial_driver_config.data serial_driver.elf + $(OBJCOPY) --update-section .serial_virt_tx_config=serial_virt_tx.data serial_virt_tx.elf + $(OBJCOPY) --update-section .serial_client_config=serial_client_client.data client.elf + touch $@ + +$(IMAGE_FILE) $(REPORT_FILE): $(IMAGES) $(SYSTEM_FILE) + $(MICROKIT_TOOL) $(SYSTEM_FILE) --search-path $(BUILD_DIR) --board $(MICROKIT_BOARD) --config $(MICROKIT_CONFIG) -o $(IMAGE_FILE) -r $(REPORT_FILE) + +clean:: + rm -f client.o +clobber:: clean + rm -f client.elf ${IMAGE_FILE} ${REPORT_FILE} diff --git a/tools/make/board/maaxboard.mk b/tools/make/board/maaxboard.mk index 8af9eb3fe..049493501 100644 --- a/tools/make/board/maaxboard.mk +++ b/tools/make/board/maaxboard.mk @@ -14,5 +14,6 @@ ETH_DRIV := eth_driver_${PLATFORM}.elf TIMER_DRIV_DIR := ${PLATFORM} UART_DRIV_DIR := ${PLATFORM} CLK_DRIV_DIR := ${PLATFORM} +PWM_DRIV_DIR := imx CPU := cortex-a53 From cb52138f3546afa6546692033d51b9df56fe07c9 Mon Sep 17 00:00:00 2001 From: Julia Vassiliki Date: Tue, 10 Mar 2026 16:28:48 +1100 Subject: [PATCH 3/9] 'basic' maybe-working PWM driver. note that it just hangs the system as we think the peripheral clocks are gated Signed-off-by: Julia Vassiliki --- drivers/pwm/imx/pwm.c | 155 +++++++++++++++++++++++++++++++++++++- drivers/pwm/imx/pwm.h | 64 ++++++++++++++++ examples/pwm/client.c | 5 +- include/sddf/pwm/client.h | 76 +++++++++++++++++++ 4 files changed, 294 insertions(+), 6 deletions(-) create mode 100644 include/sddf/pwm/client.h diff --git a/drivers/pwm/imx/pwm.c b/drivers/pwm/imx/pwm.c index dc3448e85..1df8eec2b 100644 --- a/drivers/pwm/imx/pwm.c +++ b/drivers/pwm/imx/pwm.c @@ -6,9 +6,17 @@ #include "pwm.h" #include +#include +#include #include #include #include +#include + +// TODO: Clock tree +// TODO: Pinmux +// TODO: support for more than one PWM output per client +// TODO: errata ERR051198 #define DEBUG_DRIVER @@ -24,15 +32,130 @@ #define CH_PWM_IRQ 0 #define CH_CONTROL_PPC 1 +// FIXME: hardcoded in meta.py +static volatile imx_pwm_regs_t *pwm_regs = (volatile void *)0x30000000; -void init(void) +static inline uint32_t pwm_mk_control(uint16_t prescalar, uint8_t poutc) { - LOG_DRIVER("PWM starting\n"); + assert(prescalar < BIT(PWMx_PWMCR__PRESCALAR_LEN)); + assert(poutc < BIT(PWMx_PWMCR__POUTC_LEN)); + + return ( \ + /* FIFO empty flag is set when there are more than or equal to 1 empty slots in FIFO */ + ((0b00) << PWMx_PWMCR__FWM_SHIFT) | + /* When this bit is cleared, the input clock is gated off in stop mode. */ + (( 0b0) << PWMx_PWMCR__STOPEN_SHIFT) | + /* When this bit is cleared, the input clock is gated off in stop mode. */ + (( 0b0) << PWMx_PWMCR__DOZEN_SHIFT) | + /* When this bit is cleared, the input clock is gated off in stop mode. */ + (( 0b0) << PWMx_PWMCR__WAITEN_SHIFT) | + /* When this bit is cleared, the input clock is gated off in stop mode. */ + (( 0b0) << PWMx_PWMCR__DBGEN_SHIFT) | + /* 0 = byte ordering remains the same */ + (( 0b0) << PWMx_PWMCR__BCTR_SHIFT) | + /* 0 = half word swapping does not take place */ + (( 0b0) << PWMx_PWMCR__HCTR_SHIFT) | + /* customisable */ + ((poutc) << PWMx_PWMCR__POUTC_SHIFT) | + /* Use the High-frequency reference clock (ipg_clk_highfreq) */ + ((0b10) << PWMx_PWMCR__CLKSRC_SHIFT) | + /* customisable */ + ((prescalar) << PWMx_PWMCR__PRESCALAR_SHIFT) | + /* Use each sample once */ + ((0b00) << PWMx_PWMCR__REPEAT_SHIFT) + ); } -void notified(microkit_channel ch) +static void pwm_sw_reset(void) { - LOG_DRIVER_ERR("Unexpected notification on channel %u\n", ch); + /* disable the PWM by reset. Necessary to clear the 'sample' FIFO. */ + pwm_regs->control = BIT(PWMx_PWMCR__SWR); + // FIXME: timeout + /* wait for self-clearing reset bit. should clear after 1 cycle */ + while (pwm_regs->control & BIT(PWMx_PWMCR__SWR)); + /* assert that it is now not enabled */ + assert(!(pwm_regs->control & BIT(PWMx_PWMCR__EN))); + + /* we don't need any interrupts */ + pwm_regs->interrupt = 0; +} + +/* [IMX8MDQLQRM] Section 12.2.6 Disable Sequence for the PWM */ +static void pwm_disable(void) +{ + /* Can be disabled at any time. Any data in FIFO will remain unless + cleared by a software reset */ + pwm_regs->control &= BIT(PWMx_PWMCR__EN); +} + +static void pwm_enable(void) +{ + pwm_regs->control |= BIT(PWMx_PWMCR__EN); +} + +/* [IMX8MDQLQRM] Section 12.2.4.1 Operation */ +static bool pwm_configure(uint64_t period_ns, uint64_t pulse_width_ns, int flags) +{ + pwm_disable(); + + /* + The PWM hardware has a 16-bit up-counter that counts from 0 to the period register, + and is then reset. The signal starts at 1 (unless you invert the polarity) + and then when the value in the sample FIFO matches the current count the + output signal becomes 0. It then continues counting until the period is reached. + */ + + uint8_t poutc; + if (flags & SDDF_PWM_FLAG_INVERT_POLARITY) { + poutc = PWMx_PWMCR__POUTC_INVERTED; + } else { + poutc = PWMx_PWMCR__POUTC_NORMAL; + } + +#if 0 + /* The peripheral clock ticks up at a certain rate */ + // FIXME hacks? + uint64_t clk_rate = 83333333; + + uint64_t min_ns = clk_rate / /* prescale */ 4096 * 1 /* count */; + uint64_t max_ns = clk_rate / /* prescale */ 1 * 0xFFFE /* count */; +#endif + + uint16_t prescalar = 0; + uint16_t period = 0x4000; + uint16_t sample = 0x1000; + + // Writing 0xFFFF to this register will achieve the same result as writing 0xFFFE. + if (period == 0xFFFF) { + LOG_DRIVER_ERR("period would be out of range\n"); + return false; + } + + // Note: PWMO (Hz) = PCLK(Hz) / (period +2). + + // TODO: Settings PWM_PWMPR to 0xFFFF when PWMx_PWMCR REPEAT bits are set to non-zero values is not allowed. + + pwm_regs->control = pwm_mk_control(prescalar, poutc); + pwm_regs->sample = sample; + pwm_regs->period = period; + + /* 4. Check/Clear status bits (w1c) to ensure they are all zero */ + if (pwm_regs->status & PWMx_PWMSR__InterruptStatus_MASK) { + LOG_DRIVER_ERR("PWM status (error) bits are 1: 0x%x\n", pwm_regs->status); + } + pwm_regs->status = PWMx_PWMSR__InterruptStatus_MASK; + + pwm_enable(); + + return true; +} + + +void init(void) +{ + LOG_DRIVER("PWM starting\n"); + + pwm_sw_reset(); } microkit_msginfo protected(microkit_channel ch, microkit_msginfo msginfo) @@ -40,9 +163,33 @@ microkit_msginfo protected(microkit_channel ch, microkit_msginfo msginfo) LOG_DRIVER("ppc from channel %u\n", ch); switch (microkit_msginfo_get_label(msginfo)) { + case SDDF_PWM_PPC_ID_SET_NS: { + LOG_DRIVER("PPC set NS request\n"); + + seL4_Word channel = sddf_get_mr(SDDF_PWM_PPC_SLOT_CHANNEL); + // TODO: channel unsupported. + assert(channel == 0); + + seL4_Word period_ns = sddf_get_mr(SDDF_PWM_PPC_SLOT_PERIOD); + seL4_Word pulse_width_ns = sddf_get_mr(SDDF_PWM_PPC_SLOT_PULSE_WIDTH); + seL4_Word flags = sddf_get_mr(SDDF_PWM_PPC_SLOT_FLAGS); + + if (period_ns == 0 && pulse_width_ns == 0) { + pwm_disable(); + return microkit_msginfo_new(SDDF_PWM_SUCCESS, 0); + } else { + bool success = pwm_configure(period_ns, pulse_width_ns, flags); + return microkit_msginfo_new(success ? SDDF_PWM_SUCCESS : SDDF_PWM_FAILURE, 0); + } + } default: LOG_DRIVER("Unknown request %lu from channel %u\n", microkit_msginfo_get_label(msginfo), ch); // XXXX. return microkit_msginfo_new(0, 0); } } + +void notified(microkit_channel ch) +{ + LOG_DRIVER_ERR("Unexpected notification on channel %u\n", ch); +} diff --git a/drivers/pwm/imx/pwm.h b/drivers/pwm/imx/pwm.h index 74412eef7..8bacb2cc0 100644 --- a/drivers/pwm/imx/pwm.h +++ b/drivers/pwm/imx/pwm.h @@ -3,3 +3,67 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include + +/* + The driver is based on: + + [IMX8MDQLQRM]: i.MX8 Quad i.MX 8M Dual/8M QuadLite/8M Quad Applications Processors Reference Manual + Document Number: IMX8MDQLQRM, Rev 3.1, 06/2021. + https://www.nxp.com/webapp/Download?colCode=IMX8MDQLQRM +*/ + +/* [IMX8MDQLQRM] Section 12.2.7 PWM Memory Map/Register Definition + + There are 4 instances of this PWM peripheral. +*/ +typedef struct imx_pwm_regs { + uint32_t control; /* Control Register (PWMCR) (RW) */ + uint32_t status; /* Status Register (PWMSR) (w1c) */ + uint32_t interrupt; /* Interrupt Register (PWMIR) (RW) */ + uint32_t sample; /* Sample Register (PWMSAR) (RW) */ + uint32_t period; /* Period Register (PWMSPR) (RW) */ + uint32_t counter; /* Counter Register (PWMSCNR) (RO) */ +} imx_pwm_regs_t; + +#define _LEN(start, end) ((end - start) + 1) +#define _MASK(start, end) ((BIT(_LEN(start, end)) - 1) << (start)) + +/* [IMX8MDQLQRM] Section 12.2.7.1 PWM Control Register */ +#define PWMx_PWMCR__FWM_SHIFT 26 /* FIFO Water Mark */ +#define PWMx_PWMCR__STOPEN_SHIFT 25 /* Stop Mode Enable */ +#define PWMx_PWMCR__DOZEN_SHIFT 24 /* Doze Mode Enable */ +#define PWMx_PWMCR__WAITEN_SHIFT 23 /* Wait Mode Enable */ +#define PWMx_PWMCR__DBGEN_SHIFT 22 /* Debug Mode Enable */ +#define PWMx_PWMCR__BCTR_SHIFT 21 /* Byte Data Swap Control */ +#define PWMx_PWMCR__HCTR_SHIFT 20 /* Half-Word Data Swap Control */ +#define PWMx_PWMCR__CLKSRC_SHIFT 16 /* Select Clock Source */ +#define PWMx_PWMCR__SWR 3 /* Software Reset */ +#define PWMx_PWMCR__REPEAT_SHIFT 1 /* Sample Repeat */ +#define PWMx_PWMCR__EN 0 /* Enable */ + +#define PWMx_PWMCR__PRESCALAR_SHIFT 4 /* Counter Clock Prescalar Value (12 bits) */ +#define PWMx_PWMCR__PRESCALAR_LEN 12 /* Counter Clock Prescalar Value (12 bits) */ +#define PWMx_PWMCR__PRESCALAR_MASK _MASK(4, 15) /* Counter Clock Prescalar Value (12 bits) */ + +#define PWMx_PWMCR__POUTC_SHIFT 18 /* PWM Output Configuration */ +#define PWMx_PWMCR__POUTC_LEN 2 /* PWM Output Configuration */ +#define PWMx_PWMCR__POUTC_MASK _MASK(18, 19) /* PWM Output Configuration */ +#define PWMx_PWMCR__POUTC_NORMAL 0b00 +#define PWMx_PWMCR__POUTC_INVERTED 0b01 + +/* [IMX8MDQLQRM] Section 12.2.7.3 PWM Status Register */ +#define PWMx_PWMSR__FWE 6 /* FIFO Write Error Status */ +#define PWMx_PWMSR__CMP 5 /* Compare Status */ +#define PWMx_PWMSR__ROV 4 /* Roll-over Status */ +#define PWMx_PWMSR__FE 3 /* FIFO Empty Status */ + +#define PWMx_PWMSR__InterruptStatus_MASK ( \ + BIT(PWMx_PWMSR__FWE) | BIT(PWMx_PWMSR__CMP) | \ + BIT(PWMx_PWMSR__ROV) | BIT(PWMx_PWMSR__FE) \ +) + +/* [IMX8MDQLQRM] Section 12.2.7.3 PWM Interrupt Register */ +#define PWMx_PWMIR__CIE 2 /* Compare Interrupt Enable */ +#define PWMx_PWMIR__RIE 1 /* Roll-over Interrupt Enable */ +#define PWMx_PWMIR__FIE 0 /* FIFO Empty Interrupt Enable */ diff --git a/examples/pwm/client.c b/examples/pwm/client.c index 9413e588a..9d2984215 100644 --- a/examples/pwm/client.c +++ b/examples/pwm/client.c @@ -7,6 +7,7 @@ #include #include #include +#include static serial_queue_handle_t serial_tx_queue_handle; __attribute__((__section__(".serial_client_config"))) serial_client_config_t serial_config; @@ -25,8 +26,8 @@ void init(void) LOG_CLIENT("starting\n"); - microkit_msginfo ret = sddf_ppcall(CH_PWM_CONTROL_PPC, microkit_msginfo_new(0, 0)); - (void)ret; + bool success = sddf_pwm_set_ns(CH_PWM_CONTROL_PPC, 0, /* period ns */ 500, /* pulse width ns */ 200, 0); + assert(success); LOG_CLIENT("done\n"); } diff --git a/include/sddf/pwm/client.h b/include/sddf/pwm/client.h new file mode 100644 index 000000000..6e7235111 --- /dev/null +++ b/include/sddf/pwm/client.h @@ -0,0 +1,76 @@ +/* + * Copyright 2026, UNSW + * + * SPDX-License-Identifier: BSD-2-Clause + */ +#pragma once + +#include +#include +#include + +// TODO: might want read-pwm calls +#define SDDF_PWM_PPC_ID_SET_NS (1) + +#define SDDF_PWM_PPC_SLOT_CHANNEL (0) +#define SDDF_PWM_PPC_SLOT_PERIOD (1) +#define SDDF_PWM_PPC_SLOT_PULSE_WIDTH (2) +#define SDDF_PWM_PPC_SLOT_FLAGS (3) + +/* Flip polarity of output signal */ +#define SDDF_PWM_FLAG_INVERT_POLARITY BIT(0) + +#define SDDF_PWM_SUCCESS (0) +#define SDDF_PWM_FAILURE (1) + +static inline bool sddf_pwm_set_ns(microkit_channel ch, seL4_Word channel, seL4_Word period_ns, seL4_Word pulse_width_ns, seL4_Word flags) +{ + microkit_msginfo msginfo; + + msginfo = microkit_msginfo_new(SDDF_PWM_PPC_ID_SET_NS, 4); + sddf_set_mr(SDDF_PWM_PPC_SLOT_CHANNEL, channel); + sddf_set_mr(SDDF_PWM_PPC_SLOT_PERIOD, period_ns); + sddf_set_mr(SDDF_PWM_PPC_SLOT_PULSE_WIDTH, pulse_width_ns); + sddf_set_mr(SDDF_PWM_PPC_SLOT_FLAGS, flags); + + msginfo = sddf_ppcall(ch, msginfo); + + return microkit_msginfo_get_label(msginfo) == SDDF_PWM_SUCCESS; +} + +// duty cycle encoded as a [0, 100) value +static inline bool sddf_pwm_set_freq_duty(microkit_channel ch, seL4_Word channel, seL4_Word freq, seL4_Word duty_cycle, seL4_Word flags) +{ + microkit_msginfo msginfo; + + // TODO: safe division + seL4_Word period_ns = 1000000000ULL / freq; + seL4_Word pulse_width_ns = period_ns * duty_cycle / 1000; + + msginfo = microkit_msginfo_new(SDDF_PWM_PPC_ID_SET_NS, 4); + sddf_set_mr(SDDF_PWM_PPC_SLOT_CHANNEL, channel); + sddf_set_mr(SDDF_PWM_PPC_SLOT_PERIOD, period_ns); + sddf_set_mr(SDDF_PWM_PPC_SLOT_PULSE_WIDTH, pulse_width_ns); + sddf_set_mr(SDDF_PWM_PPC_SLOT_FLAGS, flags); + + msginfo = sddf_ppcall(ch, msginfo); + + return microkit_msginfo_get_label(msginfo) == SDDF_PWM_SUCCESS; +} + +static inline bool sddf_pwm_disable(microkit_channel ch, seL4_Word channel) +{ + microkit_msginfo msginfo; + + /* A PWM disable is encoded as a set with period = 0, pulse width = 0 */ + + msginfo = microkit_msginfo_new(SDDF_PWM_PPC_ID_SET_NS, 4); + sddf_set_mr(SDDF_PWM_PPC_SLOT_CHANNEL, channel); + sddf_set_mr(SDDF_PWM_PPC_SLOT_PERIOD, 0x0); + sddf_set_mr(SDDF_PWM_PPC_SLOT_PULSE_WIDTH, 0x0); + sddf_set_mr(SDDF_PWM_PPC_SLOT_FLAGS, 0x0); + + msginfo = sddf_ppcall(ch, msginfo); + + return microkit_msginfo_get_label(msginfo) == SDDF_PWM_SUCCESS; +} From 156f03b0f12d89943584b9a17e6d8a2f895fe8ea Mon Sep 17 00:00:00 2001 From: Julia Vassiliki Date: Wed, 11 Mar 2026 11:12:05 +1100 Subject: [PATCH 4/9] clk driver for pwm Signed-off-by: Julia Vassiliki --- examples/pwm/client.c | 2 ++ examples/pwm/meta.py | 29 ++++++++++++++++++++++++++++- examples/pwm/pwm.mk | 8 +++++--- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/examples/pwm/client.c b/examples/pwm/client.c index 9d2984215..0da2fcab0 100644 --- a/examples/pwm/client.c +++ b/examples/pwm/client.c @@ -24,6 +24,8 @@ void init(void) serial_config.tx.data.vaddr); serial_putchar_init(serial_config.tx.id, &serial_tx_queue_handle); + for (volatile int i = 0; i < 1000000; i++) {} + LOG_CLIENT("starting\n"); bool success = sddf_pwm_set_ns(CH_PWM_CONTROL_PPC, 0, /* period ns */ 500, /* pulse width ns */ 200, 0); diff --git a/examples/pwm/meta.py b/examples/pwm/meta.py index bb7d1a7f0..0be3c46a9 100644 --- a/examples/pwm/meta.py +++ b/examples/pwm/meta.py @@ -28,8 +28,9 @@ def generate(sdf_file: str, output_dir: str, dtb: DeviceTree): serial_virt_tx = ProtectionDomain( "serial_virt_tx", "serial_virt_tx.elf", priority=199, stack_size=0x2000 ) + clk_driver = ProtectionDomain("clk_driver", "clk_driver.elf", priority=150, passive=True) - pwm_driver = ProtectionDomain("pwm_driver", "pwm_driver.elf", priority=150) + pwm_driver = ProtectionDomain("pwm_driver", "pwm_driver.elf", priority=100) client = ProtectionDomain("client", "client.elf", priority=0) @@ -52,12 +53,38 @@ def generate(sdf_file: str, output_dir: str, dtb: DeviceTree): assert chan.pd_a_id == 1, chan.pd_a_id assert chan.pd_b_id == 0, chan.pd_b_id + # HACK for sdfgen + + # HACK: sdfgen doesn't support multiple regions for a device resource yet + # or the clk class. This will be removed in the pending sdfgen refactor. + # We can add direct support for the Maaxboard via boards.py, but + # the odroid clk driver depends on numerous maps that aren't in the DTS + # at all, meaning this switch is the best we can do for now. + if board.name == "maaxboard": + clk_ccm_mr = MemoryRegion(sdf, "clk_ccm", 0xd000, paddr=0x30380000) + clk_ccm_analog_mr = MemoryRegion(sdf, "clk_ccm_analog", 0x1000, paddr=0x30360000) + sdf.add_mr(clk_ccm_mr) + sdf.add_mr(clk_ccm_analog_mr) + + clk_ccm_map = Map(clk_ccm_mr, 0x3200000, "rw", cached=False) + clk_ccm_analog_map = Map(clk_ccm_analog_mr, 0x3300000, "rw", cached=False) + clk_driver.add_map(clk_ccm_map) + clk_driver.add_map(clk_ccm_analog_map) + else: + print("Unsupported board!") + exit(-1) + + + clk_channel = Channel(clk_driver, client, pp_b=True) + sdf.add_channel(clk_channel) + serial_system = Sddf.Serial(sdf, serial_node, serial_driver, serial_virt_tx) serial_system.add_client(client) pds = [ serial_driver, serial_virt_tx, + clk_driver, pwm_driver, client, ] diff --git a/examples/pwm/pwm.mk b/examples/pwm/pwm.mk index 2259970d4..2e468847e 100644 --- a/examples/pwm/pwm.mk +++ b/examples/pwm/pwm.mk @@ -35,7 +35,7 @@ SDDF_CUSTOM_LIBC := 1 include ${SDDF}/tools/make/board/common.mk -IMAGES := pwm_driver.elf client.elf serial_virt_tx.elf serial_driver.elf +IMAGES := pwm_driver.elf client.elf serial_virt_tx.elf serial_driver.elf clk_driver.elf CFLAGS += -Wall -Wno-unused-function -Werror -Wno-unused-command-line-argument \ -I$(SDDF)/include \ -I$(SDDF)/include/microkit \ @@ -47,12 +47,14 @@ LIBS := --start-group -lmicrokit -Tmicrokit.ld libsddf_util_debug.a --end-group METAPROGRAM := $(TOP)/meta.py PWM_DRIVER := $(SDDF)/drivers/pwm/${PWM_DRIV_DIR} +CLK_DRIVER := $(SDDF)/drivers/clk/${CLK_DRIV_DIR} SERIAL_DRIVER := $(SDDF)/drivers/serial/${UART_DRIV_DIR} all: $(IMAGE_FILE) -include ${SDDF}/drivers/pwm/${PWM_DRIV_DIR}/pwm_driver.mk -include ${SDDF}/drivers/serial/${UART_DRIV_DIR}/serial_driver.mk +include ${CLK_DRIVER}/clk_driver.mk +include ${PWM_DRIVER}/pwm_driver.mk +include ${SERIAL_DRIVER}/serial_driver.mk include ${SDDF}/util/util.mk # include ${SDDF}/pwm/components/pwm_components.mk From 7e41ac2e83c06b8825c128ffa2eaa8b5aba48369 Mon Sep 17 00:00:00 2001 From: Bill Nguyen Date: Wed, 11 Mar 2026 11:15:00 +1100 Subject: [PATCH 5/9] Add pinctrl driver for imx8-based platforms#426 Signed-off-by: Julia Vassiliki --- .reuse/dep5 | 1 + README.md | 12 + ci/examples/pinctrl.py | 35 ++ ci/matrix.py | 17 + docs/drivers.md | 7 + docs/pinctrl/imx/imx.md | 54 +++ docs/pinctrl/pinctrl.md | 139 ++++++ drivers/pinctrl/imx/config.json | 18 + drivers/pinctrl/imx/create_pinctrl_config.py | 423 +++++++++++++++++++ drivers/pinctrl/imx/pinctrl.c | 184 ++++++++ drivers/pinctrl/imx/pinctrl_driver.mk | 48 +++ dts/imx8mm_evk.dts | 2 +- examples/pinctrl/Makefile | 32 ++ examples/pinctrl/README.md | 25 ++ examples/pinctrl/meta.py | 134 ++++++ examples/pinctrl/pinctrl.mk | 121 ++++++ examples/pinctrl/serial_client.c | 45 ++ 17 files changed, 1296 insertions(+), 1 deletion(-) create mode 100755 ci/examples/pinctrl.py create mode 100644 docs/pinctrl/imx/imx.md create mode 100644 docs/pinctrl/pinctrl.md create mode 100644 drivers/pinctrl/imx/config.json create mode 100644 drivers/pinctrl/imx/create_pinctrl_config.py create mode 100644 drivers/pinctrl/imx/pinctrl.c create mode 100644 drivers/pinctrl/imx/pinctrl_driver.mk create mode 100644 examples/pinctrl/Makefile create mode 100644 examples/pinctrl/README.md create mode 100644 examples/pinctrl/meta.py create mode 100644 examples/pinctrl/pinctrl.mk create mode 100644 examples/pinctrl/serial_client.c diff --git a/.reuse/dep5 b/.reuse/dep5 index 0b6cc3052..c94ae190c 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -42,6 +42,7 @@ Files: drivers/timer/bcm2835/config.json drivers/timer/apb_timer/config.json drivers/timer/rk3568/config.json + drivers/pinctrl/imx/config.json Copyright: UNSW License: BSD-2-Clause diff --git a/README.md b/README.md index 4d8a7613a..59f3cd2cc 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,12 @@ instead run: pip3 install --break-system-packages sdfgen==0.28.1 ``` +If you're planning on using the `pinctrl` driver, you will need to run: +```sh +# Note that this depends on PyYAML>=5.1 so you might need to create a venv +pip3 install devicetree +``` + #### Microkit SDK ```sh @@ -84,6 +90,12 @@ instead run: pip3 install --break-system-packages sdfgen==0.28.1 ``` +If you're planning on using the `pinctrl` driver, you will need to run: +```sh +# Note that this depends on PyYAML>=5.1 so you might need to create a venv +pip3 install devicetree +``` + #### Microkit SDK For Apple Silicon: diff --git a/ci/examples/pinctrl.py b/ci/examples/pinctrl.py new file mode 100755 index 000000000..8ca8c5c18 --- /dev/null +++ b/ci/examples/pinctrl.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# Copyright 2025, UNSW +# SPDX-License-Identifier: BSD-2-Clause + +import asyncio +from pathlib import Path +import sys + +sys.path.insert(1, Path(__file__).parents[2].as_posix()) + +from ci.lib.backends import * +from ci.lib.runner import TestConfig, cli, matrix_product +from ci import common, matrix + +TEST_MATRIX = matrix_product( + board=matrix.EXAMPLES["pinctrl"]["boards_test"], + config=matrix.EXAMPLES["pinctrl"]["configs"], + build_system=matrix.EXAMPLES["pinctrl"]["build_systems"], +) + + +async def test(backend: HardwareBackend, test_config: TestConfig): + async with asyncio.timeout(10): + await wait_for_output(backend, b"Begin input\r\n") + await wait_for_output(backend, b"Please give me character!\r\n") + + await send_input(backend, b"1234567890") + await expect_output(backend, b"1234567890") + await wait_for_output( + backend, b"serial_client has received 10 characters so far!\r\n" + ) + + +if __name__ == "__main__": + cli("pinctrl", test, TEST_MATRIX, common.backend_fn, common.loader_img_path) diff --git a/ci/matrix.py b/ci/matrix.py index e327f7b6b..d38775802 100644 --- a/ci/matrix.py +++ b/ci/matrix.py @@ -148,6 +148,23 @@ def listify(s: str | Sequence[str]) -> Sequence[str]: {"board": "rock3b"}, ], }, + "pinctrl": { + # Only works in debug mode so as to not depend on serial + "configs": ["debug", "release"], + "build_systems": ["make"], + "boards_build": [ + "imx8mm_evk", + "imx8mq_evk", + "imx8mp_evk", + "maaxboard", + ], + "boards_test": [ + "imx8mm_evk", + "imx8mq_evk", + "imx8mp_evk", + "maaxboard", + ], + }, } ## Type Hinting + Sanity Checks ## diff --git a/docs/drivers.md b/docs/drivers.md index 6825119b4..4e63dfe44 100644 --- a/docs/drivers.md +++ b/docs/drivers.md @@ -46,6 +46,13 @@ Device Tree compatible strings/platforms it is known to work with. * virtIO GPU (2D only) * `virtio,mmio` +## Pinctrl + +* i.MX8 IOMUXC + * `fsl,imx8mm-iomuxc` + * `fsl,imx8mp-iomuxc` + * `fsl,imx8mq-iomuxc` + ## Serial * ARM UART diff --git a/docs/pinctrl/imx/imx.md b/docs/pinctrl/imx/imx.md new file mode 100644 index 000000000..72f9567dd --- /dev/null +++ b/docs/pinctrl/imx/imx.md @@ -0,0 +1,54 @@ + + +# i.MX8 Platform-specific pinctrl details + +Prerequisite reading: +1. [pinctrl.md](../pinctrl.md) + +Each device's state will appear as a pin configuration node in the pinctrl device. A pin +configuration node will contain all the data necessary for the pin controller configure all required +pins to the correct state. Each pin's configuration data is encoded as an entry of 6 32-bit +integers in the `fsl,pins` prop of the pin configuration node. + +An entry is of the form < mux_reg conf_reg input_reg mux_val input_val pad_setting >. Where: +- "mux_reg" indicates the offset of mux register. +- "conf_reg" indicates the offset of pad configuration register. +- "input_reg" indicates the offset of select input register. +- "mux_val" indicates the mux value to be applied. +- "input_val" indicates the select input value to be applied. +- "pad_setting" indicates the pad configuration value to be applied. + +For example (from `maaxboard.dts`): +``` +uart2grp { + fsl,pins = <0x23c 0x4a4 0x4fc 0x00 0x00 0x49 0x240 0x4a8 0x00 0x00 0x00 0x49>; + phandle = <0x25>; +}; +``` + +So `uart2` requires two pins. The first pin need: +mux_reg @ 0x23c = 0x0 +conf_reg @ 0x4a4 = 0x49 +input_reg @ 0x4fc = 0x0 + +To decode what these numbers mean, we start by consulting the datasheet (i.MX 8M Dual/8M QuadLite/8M +Quad Applications Processors Reference Manual IMX8MDQLQRM Rev 3.1, 06/2021). At page 1581, section +8.2.5.139 we know that the register `mux_reg @ 0x23c` is called `IOMUXC_SW_MUX_CTL_PAD_UART2_RXD`. +Which controls what signal line inside the chip the `UART2_RXD` pad on the chip package will be +connected to. By setting this register to zero, we connect this pad to the RX line of UART2. + +Then, `conf_reg @ 0x4a4` is the `IOMUXC_SW_PAD_CTL_PAD_UART2_RXD` register that controls the +electrical characteristic of the `UART2_RXD` pad. We convert `0x49` to binary `0b1001001`. By +superimposing the binary value onto the register map in section 8.2.5.293 on page 1813. We can +decode that by setting `conf_reg` to 0x49, we achieve: +- Drive strength: 255_OHM — 255 Ohm @3.3V, 240 Ohm @2.5V, 230 Ohm @1.8V, 265 Ohm @1.2V, +- Medium Frequency Slew Rate (100Mhz), +- Open Drain Disabled, +- Pull Up Resistor Disabled, and +- Schmitt Trigger Enabled for this pad. + +While it is not necessary for you to understand what all of this registers mean, it is still useful +to be aware of how they roughly function for debugging purposes. \ No newline at end of file diff --git a/docs/pinctrl/pinctrl.md b/docs/pinctrl/pinctrl.md new file mode 100644 index 000000000..2bc6fda4c --- /dev/null +++ b/docs/pinctrl/pinctrl.md @@ -0,0 +1,139 @@ + + +# Pin control driver +Also known as a pinmux or pinctrl driver + +# Terminology +- Port: refers to an input or output line of a logic instance in the chip (e.g. UART, DDR, HDMI, + I2C,...). For example, an I2C instance has SDA and SCL ports. Not to be confused with pad. +- Pad: refers to the physical pin on the chip package (example, ball for BGA packaged chips). +- Client device: a peripheral device on the board that needs pinctrl configuration. + +# Overview + +A chip contains a limited number of pads as it is not feasable to have a one-to-one mapping between +all pads and ports. Hence, most of the pads have multiple signal options. I.e. a pad can be +connected to one of multiple ports at a given point in time as appropriate for the intended use +case. These signal-to-pin and pin-to-signal options are selected by the input-output multiplexer +called a pin controller or pinmux. The pin controller is also used to configure other electronic +characteristics of a pin, such as drive strength, bias, etc. All of these configurations can be +programmed in software by writing to the pin controller's registers. + +Before the pinctrl driver is built, a Python script will read the target board's device tree source +file, extracting all the pinctrl settings and encoding them as binary values in an assembly file. +Then the driver is built and linked with the pinctrl data assembly file, creating a complete pinctrl +driver ELF image. + +The pinctrl driver must be exclusively run at the highest priority to ensure that it is the first PD +initialised in the system. So that all pads can be connected to the desired pins before any other +device drivers attempt to initialise. At `init()` time, the driver will read the encoded pinctrl +data and write it into the pinctrl device registers. + +This driver is needed in two cases: +1. When the peripheral device you are using on your board isn't used by the bootloader. Thus some + bootloader implementations may not bother to program the pinctrl registers pertaining to those + client devices. +2. Some device drivers need to be able to dynamically configure the pinctrl characteristics at + run-time. For example, to switch the SD Card into a higher speed state on the i.MX8 and Meson + platforms, the pinctrl registers must be reprogrammed to handle the higher speed. Though this is + future work. + +# Data Structure +This section describe how the pinctrl data is encoded in the DTS and how it get organised by the +Python script for the C driver. This information is universal to all platforms. + +At the top level, we have an array of all the client devices that needs the pinmux. Each device may +have multiple pinctrl states to select from, and each state has an associated set of pin +configurations. + +For example, with the SD Card reader on the MaaXBoard: +``` +mmc@30b40000 { + compatible = "fsl,imx8mq-usdhc\0fsl,imx7d-usdhc"; + reg = <0x30b40000 0x10000>; + interrupts = <0x00 0x16 0x04>; + clocks = <0x02 0xec 0x02 0x69 0x02 0xd2>; + clock-names = "ipg\0ahb\0per"; + assigned-clocks = <0x02 0x8e>; + assigned-clock-rates = <0x17d78400>; + fsl,tuning-start-tap = <0x14>; + fsl,tuning-step = <0x02>; + bus-width = <0x04>; + status = "okay"; + pinctrl-names = "default\0state_100mhz\0state_200mhz"; + pinctrl-0 = <0x3d>; + pinctrl-1 = <0x3e>; + pinctrl-2 = <0x3f>; + non-removable; + no-sdio; + no-1-8-v; +}; + +iomuxc@30330000 { + compatible = "fsl,imx8mq-iomuxc"; + reg = <0x30330000 0x10000>; + pinctrl-names = "default"; + pinctrl-0 = <0x13>; + phandle = <0x11>; + + imx8mq-evk { + ... + usdhc1grp { + fsl,pins = <0xa0 0x308 0x00 0x00 0x00 0x83 + 0xa4 0x30c 0x00 0x00 0x00 0xc3 + 0xa8 0x310 0x00 0x00 0x00 0xc3 + 0xac 0x314 0x00 0x00 0x00 0xc3 + 0xb0 0x318 0x00 0x00 0x00 0xc3 + 0xb4 0x31c 0x00 0x00 0x00 0xc3 + 0x40 0x2a8 0x00 0x00 0x00 0x19>; + phandle = <0x3d>; + }; + + usdhc1grp100mhz { + fsl,pins = <0xa0 0x308 0x00 0x00 0x00 0x85 + 0xa4 0x30c 0x00 0x00 0x00 0xc5 + 0xa8 0x310 0x00 0x00 0x00 0xc5 + 0xac 0x314 0x00 0x00 0x00 0xc5 + 0xb0 0x318 0x00 0x00 0x00 0xc5 + 0xb4 0x31c 0x00 0x00 0x00 0xc5 + 0x40 0x2a8 0x00 0x00 0x00 0x19>; + phandle = <0x3e>; + }; + + usdhc1grp200mhz { + fsl,pins = <0xa0 0x308 0x00 0x00 0x00 0x87 + 0xa4 0x30c 0x00 0x00 0x00 0xc7 + 0xa8 0x310 0x00 0x00 0x00 0xc7 + 0xac 0x314 0x00 0x00 0x00 0xc7 + 0xb0 0x318 0x00 0x00 0x00 0xc7 + 0xb4 0x31c 0x00 0x00 0x00 0xc7 + 0x40 0x2a8 0x00 0x00 0x00 0x19>; + phandle = <0x3f>; + }; + ... + } +} +``` + +The prop `pinctrl-names` tells us how many pinctrl states a client device supports and the names of +the states. + +The order of the state names correspond to the `pinctrl-[0-9]+` prop. So the phandle in `pinctrl-0` +will lead us to the node in the pinctrl device that contains the correct register values that we +need to write for the `default` pinctrl configuration of that client device. + +# Current Implementation +The pinctrl driver will always set the `default` configuration on boot up. Supporting dynamic +setting of pinctrl state at run-time is future work. + +# Supported Platforms +Please see [drivers.md](../drivers.md). + +# Platform specific details +Please see the README.md inside the respective platform's folder. \ No newline at end of file diff --git a/drivers/pinctrl/imx/config.json b/drivers/pinctrl/imx/config.json new file mode 100644 index 000000000..532562205 --- /dev/null +++ b/drivers/pinctrl/imx/config.json @@ -0,0 +1,18 @@ +{ + "compatible": [ + "fsl,imx8mm-iomuxc", + "fsl,imx8mp-iomuxc", + "fsl,imx8mq-iomuxc" + ], + "resources": { + "regions": [ + { + "name": "regs", + "perms": "rw", + "size": 65536, + "dt_index": 0 + } + ], + "irqs": [] + } +} diff --git a/drivers/pinctrl/imx/create_pinctrl_config.py b/drivers/pinctrl/imx/create_pinctrl_config.py new file mode 100644 index 000000000..e6dd1fa15 --- /dev/null +++ b/drivers/pinctrl/imx/create_pinctrl_config.py @@ -0,0 +1,423 @@ +# Copyright 2025, UNSW +# SPDX-License-Identifier: BSD-2-Clause + +import sys +from devicetree import dtlib + +# Document referenced: +# [1] Linux: Documentation/devicetree/bindings/pinctrl/fsl,imx-pinctrl.txt +# [2] Linux: Documentation/devicetree/bindings/pinctrl/fsl,imx8mm-pinctrl.yaml +# [3] Linux: drivers/pinctrl/freescale/pinctrl-imx.c + +# [1] +# Software Input On Field. +# Force the selected mux mode input path no matter of MUX_MODE functionality. +# By default the input path is determined by functionality of the selected +# mux mode (regular). This bit will be set in "pad_setting" +PAD_SION = 1 << 30 +# If the above bit is set, clear it from "pad_setting" then set this bit in "mux_reg" +MUX_SION = 1 << 4 + +# Compatible boards and the path of its pinmux device in the device tree. +compatible_board_to_pinctrl_dev_path: dict[str, str] = { + "fsl,imx8mm-evk": "/soc@0/bus@30000000/pinctrl@30330000", + "fsl,imx8mq-evk": "/soc@0/bus@30000000/pinctrl@30330000", + "avnet/embest,maaxboard": "/soc@0/bus@30000000/iomuxc@30330000", + "fsl,imx8mp-evk": "/soc@0/bus@30000000/pinctrl@30330000", +} + +UINT32_T_SIZE = 4 +PINCTRL_CONFIG_DATA_MAGIC = "0x696D70696E6D7578" # "impinmux" + + +class PinctrlRegisterData: + def __init__( + self, + mux_reg: int, + conf_reg: int, + input_reg: int, + mux_val: int, + input_val: int, + pad_setting: int, + ): + self.mux_reg = mux_reg + self.conf_reg = conf_reg + self.input_reg = input_reg + self.mux_val = mux_val + self.input_val = input_val + self.pad_setting = pad_setting + + def __str__(self): + return f"mux reg @ {hex(self.mux_reg)} = {hex(self.mux_val)}, conf reg @ {hex(self.conf_reg)} = {hex(self.pad_setting)}, input reg @ {hex(self.input_reg)} = {hex(self.input_val)}" + + +class PinctrlStateData: + # Represent a singular pinctrl state of a device. + + def __init__( + self, + name: str, + index: int, + group_names: list[str], + phandles: list[int], + registers: list[PinctrlRegisterData], + ): + self.name = name + self.index = index + self.group_names = group_names + self.phandles = phandles + self.registers = registers + + def __str__(self): + serialised = f"* Config source name `{self.name}` at index {self.index} with group name `{str(self.group_names)}` and phandles {str(self.phandles)} have registers:\n" + for reg in self.registers: + serialised += str(reg) + "\n" + return serialised + + +class PinctrlClientDeviceData: + # Represent a device that requires pinctrl configuration. For example, `mmc@30b40000` in maaxboard.dts. + + def __init__( + self, + aliases_node: dtlib.Node, + pinctrl_node: dtlib.Node, + client_device_node: dtlib.Node, + ): + self.client_device_node: dtlib.Node = client_device_node + # A device can have multiple different pin states it selects from at runtime. + # Though all devices that require pinctrl have a "default" config. + self.pinctrl_configs: list[PinctrlStateData] = [] + self.alias: str = "" + + # Check and record whether this device have an alias + for alias_name in aliases_node.props: + if ( + aliases_node.props.get(alias_name).to_string() + == client_device_node.path + ): + self.alias = alias_name + break + + # Make sure the given device have a need for pinctrl configuration + assert "status" in client_device_node.props.keys() + assert client_device_node.props["status"].to_string() == "okay" + assert "pinctrl-0" in client_device_node.props.keys() + + # Grab and parse all the possible pin configurations of this device. + for config_idx, config_name in enumerate( + client_device_node.props.get("pinctrl-names").to_strings() + ): + target_prop: str = f"pinctrl-{config_idx}" + target_phandles: list[int] = client_device_node.props.get( + target_prop + ).to_nums() + + config_data = get_pinctrl_info( + pinctrl_node, target_phandles, config_name, config_idx + ) + assert config_data is not None + self.pinctrl_configs.append(config_data) + + def __str__(self): + serialised = f"** Device `{self.client_device_node.path}` with alias {self.alias} have the following pinctrl configs:\n" + for config in self.pinctrl_configs: + serialised += str(config) + "\n" + return serialised + + +class AssemblyDataLabelAllocator: + def __init__(self, label_prefix: str): + self.watermark: int = -1 + self.label_prefix: str = label_prefix + + def create_label(self) -> str: + self.watermark += 1 + return self.label_prefix + str(self.watermark) + + +class AssemblyStringAllocator: + # A simple class to manage the allocation of strings in the pinctrl data assembly file. + def __init__(self, label_prefix: str): + self.label_allocator = AssemblyDataLabelAllocator(label_prefix) + # string -> label + self.allocated_strings: dict[str, str] = {} + + # Returns a label to the target string + def create_label_for_str(self, target: str) -> str: + if target not in self.allocated_strings: + self.allocated_strings[target] = self.label_allocator.create_label() + return self.allocated_strings[target] + + def to_assembler(self) -> str: + asm_strs = "" + for string in self.allocated_strings: + asm_strs += f'{self.allocated_strings[string]}: .asciz "{string}"\n' + return asm_strs + + +class AssemblyDataObject: + def __init__(self, label: str): + self.data = f"{label}:\n" + + def add_word(self, value): + self.data += f"\t.word {str(value)}\n" + + def add_quad(self, value): + self.data += f"\t.quad {str(value)}\n" + + def add_ptr_from_label(self, label: str): + self.data += f"\t.quad {label}\n" + + def to_assembler(self) -> str: + return self.data + "\n" + + +class PinctrlData: + def __init__(self): + self.devices_with_pinctrl: list[PinctrlClientDeviceData] = [] + + def __str__(self): + serialised = "" + for dev in self.devices_with_pinctrl: + serialised += str(dev) + return serialised + + def parse(self, dt: dtlib.DT, pinctrl_dev_path: str): + pinctrl_node = dt.get_node( + pinctrl_dev_path + ) # This is the `iomuxc@30330000` node + aliases_node = dt.get_node("/aliases") + # Find all devices that need pinctrl configuration + device_node: dtlib.Node + for device_node in devicetree.node_iter(): + if ( + "status" in device_node.props.keys() + and device_node.props["status"].to_string() == "okay" + ): + if "pinctrl-names" in device_node.props.keys(): + self.devices_with_pinctrl.append( + PinctrlClientDeviceData(aliases_node, pinctrl_node, device_node) + ) + + def write_assembler(self, out_dir: str): + # Gather all the data we need for the assembly + num_devices = 0 + + # Manage allocation of strings and their labels in the assembly file to prevent duplication + str_asm_allocator = AssemblyStringAllocator("pinctrl_config_str_") + + label_pin_regs_array_allocator = AssemblyDataLabelAllocator( + "pinctrl_config_pins_reg_" + ) + asm_pin_regs: list[AssemblyDataObject] = [] + + label_state_object_allocator = AssemblyDataLabelAllocator( + "pinctrl_config_state_obj_" + ) + asm_states: list[AssemblyDataObject] = [] + + label_states_array_allocator = AssemblyDataLabelAllocator( + "pinctrl_config_states_" + ) + asm_states_arrays: list[AssemblyDataObject] = [] + + asm_devices = AssemblyDataObject("pinctrl_client_devices_configs") + + # For every device requiring pinctrl config, write their `pinctrl_client_device_data_t` C struct + for device in self.devices_with_pinctrl: + # For every pinctrl state of this device, write its `pinctrl_client_device_state_t` C struct + this_device_pinctrl_states_labels: list[str] = [] + for state in device.pinctrl_configs: + # For every pin in this state, write its `pinctrl_pin_register_t` C struct + state_pins_label = label_pin_regs_array_allocator.create_label() + asm_pin_regs.append(AssemblyDataObject(state_pins_label)) + + num_pins = 0 + for pin in state.registers: + asm_pin_regs[-1].add_word(pin.mux_reg) + asm_pin_regs[-1].add_word(pin.conf_reg) + asm_pin_regs[-1].add_word(pin.input_reg) + asm_pin_regs[-1].add_word(pin.mux_val) + asm_pin_regs[-1].add_word(pin.input_val) + asm_pin_regs[-1].add_word(pin.pad_setting) + num_pins += 1 + + # Now that we have the label to all the pins in this state, create the `pinctrl_client_device_state_t` + state_label = label_state_object_allocator.create_label() + asm_states.append(AssemblyDataObject(state_label)) + asm_states[-1].add_ptr_from_label( + str_asm_allocator.create_label_for_str(state.name) + ) + asm_states[-1].add_word(num_pins) + asm_states[-1].add_ptr_from_label(state_pins_label) + + this_device_pinctrl_states_labels.append(state_label) + + # We've encoded all the states and have their labels, create a states array (the `**state`) + states_array_label = label_states_array_allocator.create_label() + asm_states_arrays.append(AssemblyDataObject(states_array_label)) + for state_label in this_device_pinctrl_states_labels: + asm_states_arrays[-1].add_ptr_from_label(state_label) + + # Finally create the `pinctrl_client_device_data` + asm_devices.add_ptr_from_label( + str_asm_allocator.create_label_for_str(device.client_device_node.path) + ) + if device.alias is not None: + asm_devices.add_ptr_from_label( + str_asm_allocator.create_label_for_str(device.alias) + ) + else: + asm_devices.add_ptr_from_label( + str_asm_allocator.create_label_for_str("") + ) + asm_devices.add_word(len(this_device_pinctrl_states_labels)) + asm_devices.add_ptr_from_label(states_array_label) + + num_devices += 1 + + # Write all the data to .s file + with open(out_dir + "/pinctrl_config_data.s", "w") as file: + file.write(".section .rodata\n") + + file.write("\t.align 4\n") + file.write("\t.global pinctrl_config_data_magic\n") + file.write("\t.global pinctrl_client_devices_configs\n") + file.write("\t.global num_pinctrl_client_devices_configs\n") + + file.write( + f"pinctrl_config_data_magic:\n\t.quad {PINCTRL_CONFIG_DATA_MAGIC}\n" + ) + file.write(f"num_pinctrl_client_devices_configs:\n\t.word {num_devices}\n") + + file.write(asm_devices.to_assembler()) + file.write(str_asm_allocator.to_assembler()) + + [file.write(asm_pin_reg.to_assembler()) for asm_pin_reg in asm_pin_regs] + [file.write(asm_state.to_assembler()) for asm_state in asm_states] + [ + file.write(asm_states_array.to_assembler()) + for asm_states_array in asm_states_arrays + ] + + +def get_value_from_bytes_array(byte_array: bytes, index: int) -> int: + # Extracts a 4-byte integer value from a 'bytes' array at a certain index + return int.from_bytes( + byte_array[index * UINT32_T_SIZE : (index + 1) * UINT32_T_SIZE], + "big", + signed=False, + ) + + +def get_pinctrl_info( + pinctrl_device: dtlib.Node, + target_pinctrl_config_phandles: list[int], + target_pinctrl_config_name: str, + target_pinctrl_config_index: int, +) -> PinctrlStateData: + # pinctrl_device is the DT node to the pinctrl device that have all the configuration values. + # And pinctrl_config_phandle is the phandle to the specific group inside pinctrl_device + + group_names: list[str] = [] + phandles: list[int] = [] + regs: list[PinctrlRegisterData] = [] + + for pinctrl_config_node in pinctrl_device.node_iter(): + pinctrl_config_node_data = pinctrl_config_node.props.get("fsl,pins") + if pinctrl_config_node_data != None: + pinctrl_config_node_phandle = pinctrl_config_node.props.get( + "phandle" + ).to_num() + if pinctrl_config_node_phandle in target_pinctrl_config_phandles: + # We only support the normal configuration, not the SCU configuration. + assert len(pinctrl_config_node_data.value) % 6 == 0 + + group_names.append(pinctrl_config_node.name) + phandles.append(pinctrl_config_node_phandle) + + # [2] + # At this stage, we have the array of values + # Since each device configuration comes in a set of six values, we'll loop through in sets of 6 + for i in range( + len(pinctrl_config_node_data.value) // (6 * UINT32_T_SIZE) + ): + mux_reg = get_value_from_bytes_array( + pinctrl_config_node_data.value, 6 * i + ) + conf_reg = get_value_from_bytes_array( + pinctrl_config_node_data.value, 6 * i + 1 + ) + input_reg = get_value_from_bytes_array( + pinctrl_config_node_data.value, 6 * i + 2 + ) + mux_val = get_value_from_bytes_array( + pinctrl_config_node_data.value, 6 * i + 3 + ) + input_val = get_value_from_bytes_array( + pinctrl_config_node_data.value, 6 * i + 4 + ) + pad_setting = get_value_from_bytes_array( + pinctrl_config_node_data.value, 6 * i + 5 + ) + + # [3]: checkout tag v6.1 at line 557 + # For pins that have SION bit set in "pad_setting", set it in "mux_val" and clear it from "pad_setting" + if pad_setting & PAD_SION: + mux_val |= MUX_SION + pad_setting &= ~PAD_SION + + regs.append( + PinctrlRegisterData( + mux_reg, + conf_reg, + input_reg, + mux_val, + input_val, + pad_setting, + ) + ) + + if len(regs) > 0: + return PinctrlStateData( + target_pinctrl_config_name, + target_pinctrl_config_index, + group_names, + phandles, + regs, + ) + else: + return None + + +if __name__ == "__main__": + + if len(sys.argv) != 3: + print("Usage: ") + print("\tpython3 create_pinmux_setup.py ") + exit(1) + + # Parse device tree file + devicetree = dtlib.DT(sys.argv[1], force=True) + out_dir = sys.argv[2] + + # Ensure compatibility + target_compats = devicetree.get_node("/").props.get("compatible").to_strings() + matched_compat_str = None + for target_compat in target_compats: + if target_compat in compatible_board_to_pinctrl_dev_path.keys(): + matched_compat_str = target_compat + break + if matched_compat_str is None: + print(f"Your target board {target_compats} isn't compatible with this driver.") + sys.exit(1) + + # Start parsing + pinctrl_data = PinctrlData() + pinctrl_data.parse( + devicetree, compatible_board_to_pinctrl_dev_path[matched_compat_str] + ) + print(pinctrl_data) + + pinctrl_data.write_assembler(out_dir) diff --git a/drivers/pinctrl/imx/pinctrl.c b/drivers/pinctrl/imx/pinctrl.c new file mode 100644 index 000000000..ae448d1dc --- /dev/null +++ b/drivers/pinctrl/imx/pinctrl.c @@ -0,0 +1,184 @@ +/* + * Copyright 2025, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* +Pinctrl driver for the iMX8 SoC based platforms. + +Documents referenced: +[1] Linux: Documentation/devicetree/bindings/pinctrl/fsl,imx-pinctrl.txt +[2] Linux: drivers/pinctrl/freescale/pinctrl-imx.c +[3] U-Boot: drivers/pinctrl/nxp/pinctrl-imx.c +*/ + +#include +#include +#include +#include +#include +#include + +__attribute__((__section__(".device_resources"))) device_resources_t device_resources; + +#define DEBUG_DRIVER + +#ifdef DEBUG_DRIVER +#define LOG_DRIVER(...) do{ sddf_dprintf("PINCTRL DRIVER|INFO: "); sddf_dprintf(__VA_ARGS__); }while(0) +#else +#define LOG_DRIVER(...) do{}while(0) +#endif + +#define LOG_DRIVER_ERR(...) do{ sddf_printf("PINCTRL DRIVER|ERROR: "); sddf_printf(__VA_ARGS__); }while(0) +#define LOG_DRIVER_ERR_FATAL(...) do{ sddf_printf("PINCTRL DRIVER|FATAL ERROR: "); sddf_printf(__VA_ARGS__); }while(0) + +uintptr_t iomuxc_dev_base; + +// [1] +// Special values for pad_setting: +// Indicate this pin does not need config +#define NO_PAD_CTL (1 << 31) + +typedef struct __attribute__((packed)) pinctrl_pin_register { + const uint32_t mux_reg; /* Contains offset of mux registers */ + const uint32_t conf_reg; /* Offset of pad configuration register */ + const uint32_t input_reg; /* Offset of select input register */ + const uint32_t mux_val; /* Mux values to be applied */ + const uint32_t input_val; /* Select input values to be applied */ + const uint32_t pad_setting; /* Pad configuration values to be applied */ +} pinctrl_pin_register_t; + +typedef struct __attribute__((packed)) pinctrl_client_device_state { + const char *state_name; + const uint32_t num_pins; + const pinctrl_pin_register_t *pins_reg; +} pinctrl_client_device_state_t; + +typedef struct __attribute__((packed)) pinctrl_client_device_data { + const char *dev_dt_path; /* Device tree path of this particular device that needs pinctrl configuration */ + const char *dev_dt_alias; + const uint32_t num_states; /* Number of pinctrl states required as defined in the `pinctrl-names` prop */ + const pinctrl_client_device_state_t **states; +} pinctrl_client_device_data_t; + +// Data from Device Tree that is linked during compile time. +#define CONFIG_MAGIC 0x696D70696E6D7578 // "impinmux" +extern const uint64_t pinctrl_config_data_magic; +extern const pinctrl_client_device_data_t pinctrl_client_devices_configs[]; +extern const uint32_t num_pinctrl_client_devices_configs; + +static bool sanity_check_pinctrl_reg_offset(uint32_t offset) +{ + if (offset % 4 == 0) { + return true; + } else { + LOG_DRIVER_ERR_FATAL("offset 0x%x is not 4 bytes aligned\n", offset); + return false; + } +} + +static bool read_mux(uint32_t offset, uint32_t *ret) +{ + if (!sanity_check_pinctrl_reg_offset(offset)) { + return false; + } + + volatile uint32_t *mux_reg_vaddr = (uint32_t *)(iomuxc_dev_base + (uintptr_t)offset); + *ret = *mux_reg_vaddr; + return true; +} + +static bool set_mux(uint32_t offset, uint32_t val) +{ + if (!sanity_check_pinctrl_reg_offset(offset)) { + return false; + } + + volatile uint32_t *mux_reg_vaddr = (uint32_t *)(iomuxc_dev_base + (uintptr_t)offset); + *mux_reg_vaddr = val; + + if (*mux_reg_vaddr != val) { + LOG_DRIVER_ERR_FATAL("write was not completed, real != expected: %x != %x", *mux_reg_vaddr, val); + return false; + } + return true; +} + +static void debug_print_pinctrl_config_data(void) +{ + LOG_DRIVER("STARTING PINCTRL CONFIG DUMP\n"); + LOG_DRIVER("Total %u devices need pinctrl configuration.\n", num_pinctrl_client_devices_configs); + for (int i = 0; i < num_pinctrl_client_devices_configs; i++) { + LOG_DRIVER("** %s with alias %s have the following %d states:\n", pinctrl_client_devices_configs[i].dev_dt_path, + pinctrl_client_devices_configs[i].dev_dt_alias, pinctrl_client_devices_configs[i].num_states); + for (int j = 0; j < pinctrl_client_devices_configs[i].num_states; j++) { + LOG_DRIVER("* State '%s' at index %d have %u pins:\n", + pinctrl_client_devices_configs[i].states[j]->state_name, j, + pinctrl_client_devices_configs[i].states[j]->num_pins); + for (int k = 0; k < pinctrl_client_devices_configs[i].states[j]->num_pins; k++) { + LOG_DRIVER("mux reg: 0x%x = 0x%x, input reg: 0x%x = 0x%x, pad conf reg: 0x%x = 0x%x\n", + pinctrl_client_devices_configs[i].states[j]->pins_reg[k].mux_reg, + pinctrl_client_devices_configs[i].states[j]->pins_reg[k].mux_val, + pinctrl_client_devices_configs[i].states[j]->pins_reg[k].input_reg, + pinctrl_client_devices_configs[i].states[j]->pins_reg[k].input_val, + pinctrl_client_devices_configs[i].states[j]->pins_reg[k].conf_reg, + pinctrl_client_devices_configs[i].states[j]->pins_reg[k].pad_setting); + } + } + } + LOG_DRIVER("---------------------------------------------------------------\n"); +} + +static void pinctrl_set_state(pinctrl_client_device_state_t state) +{ + for (int j = 0; j < state.num_pins; j++) { + assert(set_mux(state.pins_reg[j].mux_reg, state.pins_reg[j].mux_val)); + if (state.pins_reg[j].input_reg) { + // We don't support "quirky" select input values of [2] (checkout tag v6.1, line 196). + // As it was only ever used in vendor kernels of old imx6 and imx7 boards and never made + // it to upstream Linux. + assert(state.pins_reg[j].input_val >> 24 != 0xff); + + assert(set_mux(state.pins_reg[j].input_reg, state.pins_reg[j].input_val)); + } + if (!(state.pins_reg[j].pad_setting & NO_PAD_CTL)) { + assert(set_mux(state.pins_reg[j].conf_reg, state.pins_reg[j].pad_setting)); + } + } +} + +static void pinctrl_reset_all_default(void) +{ + for (int i = 0; i < num_pinctrl_client_devices_configs; i++) { + pinctrl_client_device_data_t client_device = pinctrl_client_devices_configs[i]; + assert(client_device.num_states > 0); + pinctrl_client_device_state_t default_state = *(pinctrl_client_devices_configs[i].states[0]); + assert(strcmp(default_state.state_name, "default") == 0); + + LOG_DRIVER("Setting dev %s to default pinctrl config with total %u pins.\n", client_device.dev_dt_path, + default_state.num_pins); + pinctrl_set_state(default_state); + } +} + +void init(void) +{ + assert(device_resources_check_magic(&device_resources)); + assert(device_resources.num_irqs == 0); + assert(device_resources.num_regions == 1); + + assert(pinctrl_config_data_magic == CONFIG_MAGIC); + assert(num_pinctrl_client_devices_configs >= 1); + + iomuxc_dev_base = (uintptr_t)device_resources.regions[0].region.vaddr; + + debug_print_pinctrl_config_data(); + pinctrl_reset_all_default(); + + LOG_DRIVER("INIT OK\n"); +} + +void notified(microkit_channel ch) +{ + LOG_DRIVER_ERR("received ntfn on unexpected channel %u\n", ch); +} diff --git a/drivers/pinctrl/imx/pinctrl_driver.mk b/drivers/pinctrl/imx/pinctrl_driver.mk new file mode 100644 index 000000000..612491304 --- /dev/null +++ b/drivers/pinctrl/imx/pinctrl_driver.mk @@ -0,0 +1,48 @@ +# +# Copyright 2025, UNSW +# +# SPDX-License-Identifier: BSD-2-Clause +# +# Include this snippet in your project Makefile to build +# the iMX8 pinctrl driver +# Assumes libsddf_util_debug.a is in ${LIBS}. +# +# NOTES +# Generates pinctrl_driver.elf +# Has 3 parameters: +# SDDF: path to sddf root +# PYTHON: python interpreter +# DTS: absolute path to the device tree source file. + +ifndef PYTHON +$(error PYTHON is not set) +endif + +ifndef DTS +$(error DTS is not set) +endif + +PINCTRL_DIR := $(dir $(lastword $(MAKEFILE_LIST))) + +pinctrl/imx/pinctrl_config_data.s: ${DTS} ${PINCTRL_DIR}/create_pinctrl_config.py pinctrl/imx + ${PYTHON} ${PINCTRL_DIR}/create_pinctrl_config.py ${DTS} pinctrl/imx + +pinctrl/imx/pinctrl_config_data.o: pinctrl/imx/pinctrl_config_data.s + ${CC} ${ASFLAGS} -c $< -o $@ + +pinctrl/imx/pinctrl.o: $(PINCTRL_DIR)/pinctrl.c pinctrl/imx + ${CC} ${CFLAGS} -DSOC_$(shell echo $(SOC) | tr a-z A-Z | tr - _) -c $< -o $@ + +pinctrl_driver.elf: pinctrl/imx/pinctrl.o pinctrl/imx/pinctrl_config_data.o + ${LD} ${LDFLAGS} $^ ${LIBS} -o $@ + +-include pinctrl/imx/pinctrl_driver.d + +pinctrl/imx: + mkdir -p $@ + +clean:: + rm -rf pinctrl + +clobber:: + rm -f pinctrl \ No newline at end of file diff --git a/dts/imx8mm_evk.dts b/dts/imx8mm_evk.dts index 7ce5cd141..8701f5d56 100644 --- a/dts/imx8mm_evk.dts +++ b/dts/imx8mm_evk.dts @@ -696,7 +696,7 @@ }; usdhc3grp { - fsl,pins = <0x138 0x3a0 0x00 0x12 0x00 0x190 0x13c 0x3a4 0x00 0x02 0x00 0x1d0 0x11c 0x384 0x00 0x02 0x00 0x1d0 0x120 0x388 0x00 0x02 0x00 0x1d0 0x124 0x38c 0x00 0x02 0x00 0x1d0 0x124 0x38c 0x00 0x02 0x00 0x1d0 0x128 0x390 0x00 0x02 0x00 0x1d0 0x130 0x398 0x00 0x02 0x00 0x1d0 0x100 0x368 0x00 0x02 0x00 0x1d0 0x104 0x36c 0x00 0x02 0x00 0x1d0 0x108 0x370 0x00 0x02 0x00 0x1d0 0xfc 0x364 0x00 0x02 0x00 0x190>; + fsl,pins = <0x138 0x3a0 0x00 0x12 0x00 0x190 0x13c 0x3a4 0x00 0x02 0x00 0x1d0 0x11c 0x384 0x00 0x02 0x00 0x1d0 0x120 0x388 0x00 0x02 0x00 0x1d0 0x124 0x38c 0x00 0x02 0x00 0x1d0 0x128 0x390 0x00 0x02 0x00 0x1d0 0x130 0x398 0x00 0x02 0x00 0x1d0 0x100 0x368 0x00 0x02 0x00 0x1d0 0x104 0x36c 0x00 0x02 0x00 0x1d0 0x108 0x370 0x00 0x02 0x00 0x1d0 0xfc 0x364 0x00 0x02 0x00 0x190>; phandle = <0x3c>; }; diff --git a/examples/pinctrl/Makefile b/examples/pinctrl/Makefile new file mode 100644 index 000000000..a984aa990 --- /dev/null +++ b/examples/pinctrl/Makefile @@ -0,0 +1,32 @@ +# +# Copyright 2025, UNSW +# +# SPDX-License-Identifier: BSD-2-Clause +# + +ifeq ($(strip $(MICROKIT_SDK)),) +$(error MICROKIT_SDK must be specified) +endif + +ifeq ($(strip $(MICROKIT_BOARD)),) +$(error MICROKIT_BOARD must be specified) +endif +export MICROKIT_BOARD +BUILD_DIR ?= build +export SDDF=$(abspath ../..) +export BUILD_DIR:=$(abspath ${BUILD_DIR}) +export override MICROKIT_SDK:=$(abspath ${MICROKIT_SDK}) + +IMAGE_FILE:= ${BUILD_DIR}/loader.img +REPORT_FILE:= ${BUILD_DIR}/report.txt + +all: ${IMAGE_FILE} + +${IMAGE_FILE} ${REPORT_FILE} clean clobber: ${BUILD_DIR}/Makefile FORCE + ${MAKE} -C ${BUILD_DIR} MICROKIT_SDK=${MICROKIT_SDK} $(notdir $@) + +${BUILD_DIR}/Makefile: pinctrl.mk + mkdir -p ${BUILD_DIR} + cp pinctrl.mk $@ + +FORCE: diff --git a/examples/pinctrl/README.md b/examples/pinctrl/README.md new file mode 100644 index 000000000..f1c30bca6 --- /dev/null +++ b/examples/pinctrl/README.md @@ -0,0 +1,25 @@ + + +# Pinctrl example + +This is a copy of the serial example with pinctrl incoporated to demonstrate how the pinctrl +subsystem can be minimally incorporated. + +There's a pinctrl driver running at the highest priority to program all the registers value read +from the device tree before any other device drivers (at a lower priority) can run. + +## Building + +The following platforms are supported: +* imx8mm_evk +* imx8mp_evk +* imx8mq_evk +* maaxboard + +For further details on building and running this example, please see the serial example. + +For further details on the pinctrl driver, please see [pinctrl.md](../../docs/pinctrl/pinctrl.md) diff --git a/examples/pinctrl/meta.py b/examples/pinctrl/meta.py new file mode 100644 index 000000000..8b2772c2c --- /dev/null +++ b/examples/pinctrl/meta.py @@ -0,0 +1,134 @@ +# Copyright 2025, UNSW +# SPDX-License-Identifier: BSD-2-Clause +import argparse +from typing import List +from dataclasses import dataclass +from sdfgen import SystemDescription, Sddf, DeviceTree +from importlib.metadata import version + +# @billn fix +# assert version('sdfgen').split(".")[1] == "24", "Unexpected sdfgen version" + +ProtectionDomain = SystemDescription.ProtectionDomain + + +@dataclass +class RegRegion: + start_paddr: int + size: int + + +@dataclass +class Board: + name: str + arch: SystemDescription.Arch + paddr_top: int + serial: str + pinctrl: str + + +BOARDS: List[Board] = [ + Board( + name="maaxboard", + arch=SystemDescription.Arch.AARCH64, + paddr_top=0xA_000_000, + serial="soc@0/bus@30800000/serial@30860000", + pinctrl="soc@0/bus@30000000/iomuxc@30330000", + ), + Board( + name="imx8mm_evk", + arch=SystemDescription.Arch.AARCH64, + paddr_top=0xA_000_000, + serial="soc@0/bus@30800000/spba-bus@30800000/serial@30890000", + pinctrl="soc@0/bus@30000000/pinctrl@30330000", + ), + Board( + name="imx8mp_evk", + arch=SystemDescription.Arch.AARCH64, + paddr_top=0xA_000_000, + serial="soc@0/bus@30800000/spba-bus@30800000/serial@30890000", + pinctrl="soc@0/bus@30000000/pinctrl@30330000", + ), + Board( + name="imx8mq_evk", + arch=SystemDescription.Arch.AARCH64, + paddr_top=0xA_000_000, + serial="soc@0/bus@30800000/serial@30860000", + pinctrl="soc@0/bus@30000000/pinctrl@30330000", + ), +] + + +def generate(sdf_file: str, output_dir: str, dtb: DeviceTree): + pinctrl_node = dtb.node(board.pinctrl) + assert pinctrl_node is not None + + # Ensure the priority is exclusively the highest as the pinctrl driver must run first! + # This is enforced by sdfgen at the render() step. + pinctrl_driver = ProtectionDomain( + "pinctrl_driver", "pinctrl_driver.elf", priority=253 + ) + pinctrl_system = Sddf.Pinctrl(sdf, pinctrl_node, pinctrl_driver) + + serial_driver = ProtectionDomain("serial_driver", "serial_driver.elf", priority=200) + # Increase the stack size as running with UBSAN uses more stack space than normal. + serial_virt_tx = ProtectionDomain( + "serial_virt_tx", "serial_virt_tx.elf", priority=199, stack_size=0x2000 + ) + serial_virt_rx = ProtectionDomain( + "serial_virt_rx", "serial_virt_rx.elf", priority=199, stack_size=0x2000 + ) + serial_client = ProtectionDomain("serial_client", "serial_client.elf", priority=1) + + serial_node = dtb.node(board.serial) + assert serial_node is not None + + serial_system = Sddf.Serial( + sdf, + serial_node, + serial_driver, + serial_virt_tx, + virt_rx=serial_virt_rx, + enable_color=False, + ) + serial_system.add_client(serial_client) + + pds = [ + pinctrl_driver, + serial_driver, + serial_virt_tx, + serial_virt_rx, + serial_client, + ] + for pd in pds: + sdf.add_pd(pd) + + assert serial_system.connect() + assert serial_system.serialise_config(output_dir) + + assert pinctrl_system.connect() + assert pinctrl_system.serialise_config(output_dir) + + with open(f"{output_dir}/{sdf_file}", "w+") as f: + f.write(sdf.render()) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--dtb", required=True) + parser.add_argument("--sddf", required=True) + parser.add_argument("--board", required=True, choices=[b.name for b in BOARDS]) + parser.add_argument("--output", required=True) + parser.add_argument("--sdf", required=True) + + args = parser.parse_args() + + board = next(filter(lambda b: b.name == args.board, BOARDS)) + + sdf = SystemDescription(board.arch, board.paddr_top) + sddf = Sddf(args.sddf) + + with open(args.dtb, "rb") as f: + dtb = DeviceTree(f.read()) + + generate(args.sdf, args.output, dtb) diff --git a/examples/pinctrl/pinctrl.mk b/examples/pinctrl/pinctrl.mk new file mode 100644 index 000000000..675102752 --- /dev/null +++ b/examples/pinctrl/pinctrl.mk @@ -0,0 +1,121 @@ +# +# Copyright 2023, UNSW +# +# SPDX-License-Identifier: BSD-2-Clause +# +# This Makefile is copied into the build directory +# and operated on from there. +# + +ifeq ($(strip $(MICROKIT_SDK)),) +$(error MICROKIT_SDK must be specified) +endif + +ifeq ($(strip $(SDDF)),) +$(error SDDF must be specified) +endif + +BUILD_DIR ?= build +MICROKIT_CONFIG ?= debug +IMAGE_FILE = loader.img +REPORT_FILE = report.txt + +CC := clang +LD := ld.lld +AS := clang +AR := llvm-ar +RANLIB := llvm-ranlib +OBJCOPY := llvm-objcopy +DTC := dtc +PYTHON ?= python3 + +MICROKIT_TOOL := $(MICROKIT_SDK)/bin/microkit + +ifneq ($(filter $(strip $(MICROKIT_BOARD)),imx8mm_evk imx8mp_evk imx8mq_evk maaxboard),) + DRIVER_DIR := imx + CPU := cortex-a53 +else +$(error Unsupported MICROKIT_BOARD given) +endif + +TOP := ${SDDF}/examples/pinctrl +METAPROGRAM := $(TOP)/meta.py +UTIL := $(SDDF)/util +PINCTRL_DRIVER := $(SDDF)/drivers/pinctrl/$(DRIVER_DIR) +UART_DRIVER := $(SDDF)/drivers/serial/$(DRIVER_DIR) +SERIAL_COMPONENTS := $(SDDF)/serial/components +BOARD_DIR := $(MICROKIT_SDK)/board/$(MICROKIT_BOARD)/$(MICROKIT_CONFIG) +SYSTEM_FILE := pinctrl.system +DTS := $(SDDF)/dts/$(MICROKIT_BOARD).dts +DTB := $(MICROKIT_BOARD).dtb +ARCH := ${shell grep 'CONFIG_SEL4_ARCH ' $(BOARD_DIR)/include/kernel/gen_config.h | cut -d' ' -f4} +SDDF_CUSTOM_LIBC := 1 +QEMU := qemu-system-$(ARCH) + +IMAGES := pinctrl_driver.elf serial_driver.elf \ + serial_client.elf \ + serial_virt_tx.elf serial_virt_rx.elf +CFLAGS := -ffreestanding \ + -g3 -O3 -Wall \ + -Wno-unused-function -Werror \ + -MD +LDFLAGS := -L$(BOARD_DIR)/lib -L$(SDDF)/lib +LIBS := --start-group -lmicrokit -Tmicrokit.ld libsddf_util_debug.a --end-group + +ifeq ($(ARCH),aarch64) + CFLAGS += -mcpu=$(CPU) -mstrict-align -target aarch64-none-elf + ASFLAGS := -target aarch64-none-elf +else ifeq ($(ARCH),riscv64) + CFLAGS += -march=rv64imafdc -target riscv64-none-elf + ASFLAGS := -target riscv64-none-elf +endif +CFLAGS += -I$(BOARD_DIR)/include \ + -I${TOP}/include \ + -I${SDDF}/include \ + -I${SDDF}/include/microkit \ + $(CFLAGS_ARCH) + +CHECK_FLAGS_BOARD_MD5:=.board_cflags-$(shell echo -- ${CFLAGS} ${MICROKIT_SDK} ${MICROKIT_BOARD} ${MICROKIT_CONFIG} | shasum | sed 's/ *-//') + +${CHECK_FLAGS_BOARD_MD5}: + -rm -f .board_cflags-* + touch $@ + +${IMAGES}: libsddf_util_debug.a ${CHECK_FLAGS_BOARD_MD5} + +include ${SDDF}/util/util.mk +include ${PINCTRL_DRIVER}/pinctrl_driver.mk +include ${UART_DRIVER}/serial_driver.mk +include ${SERIAL_COMPONENTS}/serial_components.mk + +%.elf: %.o + ${LD} -o $@ ${LDFLAGS} $< ${LIBS} + +serial_client.elf: serial_client.o libsddf_util.a + $(LD) $(LDFLAGS) $^ $(LIBS) -o $@ + +serial_client.o: ${TOP}/serial_client.c ${CHECK_FLAGS_BOARD_MD5} + $(CC) $(CFLAGS) -c -o $@ $< + +$(DTB): $(DTS) + dtc -q -I dts -O dtb $(DTS) > $(DTB) + +$(SYSTEM_FILE): $(METAPROGRAM) $(IMAGES) $(DTB) + $(PYTHON) $(METAPROGRAM) --sddf $(SDDF) --board $(MICROKIT_BOARD) --dtb $(DTB) --output . --sdf $(SYSTEM_FILE) + $(OBJCOPY) --update-section .device_resources=pinctrl_driver_device_resources.data pinctrl_driver.elf + + $(OBJCOPY) --update-section .device_resources=serial_driver_device_resources.data serial_driver.elf + $(OBJCOPY) --update-section .serial_driver_config=serial_driver_config.data serial_driver.elf + $(OBJCOPY) --update-section .serial_virt_rx_config=serial_virt_rx.data serial_virt_rx.elf + $(OBJCOPY) --update-section .serial_virt_tx_config=serial_virt_tx.data serial_virt_tx.elf + $(OBJCOPY) --update-section .serial_client_config=serial_client_serial_client.data serial_client.elf + +$(IMAGE_FILE) $(REPORT_FILE): $(IMAGES) $(SYSTEM_FILE) + MICROKIT_SDK=${MICROKIT_SDK} $(MICROKIT_TOOL) $(SYSTEM_FILE) --search-path $(BUILD_DIR) --board $(MICROKIT_BOARD) --config $(MICROKIT_CONFIG) -o $(IMAGE_FILE) -r $(REPORT_FILE) + +clean:: + ${RM} -f *.elf + find . -name '*.[od]' | xargs ${RM} -f + +clobber:: clean + ${RM} -f ${IMAGE_FILE} ${REPORT_FILE} diff --git a/examples/pinctrl/serial_client.c b/examples/pinctrl/serial_client.c new file mode 100644 index 000000000..bfbdf3d71 --- /dev/null +++ b/examples/pinctrl/serial_client.c @@ -0,0 +1,45 @@ +/* + * Copyright 2025, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +__attribute__((__section__(".serial_client_config"))) serial_client_config_t config; + +serial_queue_handle_t rx_queue_handle; +serial_queue_handle_t tx_queue_handle; + +uint32_t local_head; + +void init(void) +{ + assert(serial_config_check_magic(&config)); + + serial_queue_init(&rx_queue_handle, config.rx.queue.vaddr, config.rx.data.size, config.rx.data.vaddr); + serial_queue_init(&tx_queue_handle, config.tx.queue.vaddr, config.tx.data.size, config.tx.data.vaddr); + + serial_putchar_init(config.tx.id, &tx_queue_handle); + sddf_printf("Hello world! I am %s.\nPlease give me character!\n", microkit_name); +} + +uint16_t char_count; +void notified(microkit_channel ch) +{ + char c; + while (!serial_dequeue(&rx_queue_handle, &c)) { + if (c == '\r') { + sddf_putchar_unbuffered('\\'); + sddf_putchar_unbuffered('r'); + } else { + sddf_putchar_unbuffered(c); + } + char_count++; + if (char_count % 10 == 0) { + sddf_printf("\n%s has received %u characters so far!\n", microkit_name, char_count); + } + } +} From ec0cd9a8a0a5c9e362aa6b64e4ae0cc87abd0c46 Mon Sep 17 00:00:00 2001 From: Julia Vassiliki Date: Wed, 11 Mar 2026 12:01:36 +1100 Subject: [PATCH 6/9] technically working pwm output Signed-off-by: Julia Vassiliki --- examples/pwm/client.c | 14 ++++++++++++-- examples/pwm/meta.py | 14 +++++++++++++- examples/pwm/pwm.mk | 8 +++++++- flake.lock | 19 ++++++++++--------- tools/make/board/maaxboard.mk | 1 + tools/meta/board.py | 2 ++ 6 files changed, 45 insertions(+), 13 deletions(-) diff --git a/examples/pwm/client.c b/examples/pwm/client.c index 0da2fcab0..ab53f9b37 100644 --- a/examples/pwm/client.c +++ b/examples/pwm/client.c @@ -28,8 +28,18 @@ void init(void) LOG_CLIENT("starting\n"); - bool success = sddf_pwm_set_ns(CH_PWM_CONTROL_PPC, 0, /* period ns */ 500, /* pulse width ns */ 200, 0); - assert(success); + while (1) { + for (volatile int i = 0; i < 100000000; i++) {} + + bool success = sddf_pwm_set_ns(CH_PWM_CONTROL_PPC, 0, /* period ns */ 500, /* pulse width ns */ 200, 0); + assert(success); + + for (volatile int i = 0; i < 100000000; i++) {} + + // disable + success = sddf_pwm_set_ns(CH_PWM_CONTROL_PPC, 0, /* period ns */ 0, /* pulse width ns */ 0, 0); + assert(success); + } LOG_CLIENT("done\n"); } diff --git a/examples/pwm/meta.py b/examples/pwm/meta.py index 0be3c46a9..aecc35330 100644 --- a/examples/pwm/meta.py +++ b/examples/pwm/meta.py @@ -12,7 +12,7 @@ ) from board import BOARDS -assert version("sdfgen").split(".")[1] == "28", "Unexpected sdfgen version" +assert version("sdfgen").split(".")[1] == "29", "Unexpected sdfgen version" ProtectionDomain = SystemDescription.ProtectionDomain MemoryRegion = SystemDescription.MemoryRegion @@ -29,6 +29,14 @@ def generate(sdf_file: str, output_dir: str, dtb: DeviceTree): "serial_virt_tx", "serial_virt_tx.elf", priority=199, stack_size=0x2000 ) clk_driver = ProtectionDomain("clk_driver", "clk_driver.elf", priority=150, passive=True) + # Ensure the priority is exclusively the highest as the pinctrl driver must run first! + # This is enforced by sdfgen at the render() step. + pinctrl_driver = ProtectionDomain( + "pinctrl_driver", "pinctrl_driver.elf", priority=253 + ) + pinctrl_node = dtb.node(board.pinctrl) + assert pinctrl_node is not None + pinctrl_system = Sddf.Pinctrl(sdf, pinctrl_node, pinctrl_driver) pwm_driver = ProtectionDomain("pwm_driver", "pwm_driver.elf", priority=100) @@ -84,6 +92,7 @@ def generate(sdf_file: str, output_dir: str, dtb: DeviceTree): pds = [ serial_driver, serial_virt_tx, + pinctrl_driver, clk_driver, pwm_driver, client, @@ -94,6 +103,9 @@ def generate(sdf_file: str, output_dir: str, dtb: DeviceTree): assert serial_system.connect() assert serial_system.serialise_config(output_dir) + assert pinctrl_system.connect() + assert pinctrl_system.serialise_config(output_dir) + with open(f"{output_dir}/{sdf_file}", "w+") as f: f.write(sdf.render()) diff --git a/examples/pwm/pwm.mk b/examples/pwm/pwm.mk index 2e468847e..2f9652c43 100644 --- a/examples/pwm/pwm.mk +++ b/examples/pwm/pwm.mk @@ -35,18 +35,22 @@ SDDF_CUSTOM_LIBC := 1 include ${SDDF}/tools/make/board/common.mk -IMAGES := pwm_driver.elf client.elf serial_virt_tx.elf serial_driver.elf clk_driver.elf +IMAGES := pwm_driver.elf client.elf serial_virt_tx.elf serial_driver.elf clk_driver.elf pinctrl_driver.elf CFLAGS += -Wall -Wno-unused-function -Werror -Wno-unused-command-line-argument \ -I$(SDDF)/include \ -I$(SDDF)/include/microkit \ -I$(CONFIGS_INCLUDE) +# HACK for Pinctrl +ASFLAGS := -target aarch64-none-elf + LDFLAGS := -L$(BOARD_DIR)/lib LIBS := --start-group -lmicrokit -Tmicrokit.ld libsddf_util_debug.a --end-group METAPROGRAM := $(TOP)/meta.py PWM_DRIVER := $(SDDF)/drivers/pwm/${PWM_DRIV_DIR} +PINCTRL_DRIVER := $(SDDF)/drivers/pinctrl/${PINCTRL_DRIV_DIR} CLK_DRIVER := $(SDDF)/drivers/clk/${CLK_DRIV_DIR} SERIAL_DRIVER := $(SDDF)/drivers/serial/${UART_DRIV_DIR} @@ -55,6 +59,7 @@ all: $(IMAGE_FILE) include ${CLK_DRIVER}/clk_driver.mk include ${PWM_DRIVER}/pwm_driver.mk include ${SERIAL_DRIVER}/serial_driver.mk +include ${PINCTRL_DRIVER}/pinctrl_driver.mk include ${SDDF}/util/util.mk # include ${SDDF}/pwm/components/pwm_components.mk @@ -73,6 +78,7 @@ $(SYSTEM_FILE): $(METAPROGRAM) $(IMAGES) $(DTB) $(METAPROGRAM) --sddf $(SDDF) --board $(MICROKIT_BOARD) \ --dtb $(DTB) --output . --sdf $(SYSTEM_FILE) $(OBJCOPY) --update-section .device_resources=serial_driver_device_resources.data serial_driver.elf + $(OBJCOPY) --update-section .device_resources=pinctrl_driver_device_resources.data pinctrl_driver.elf $(OBJCOPY) --update-section .serial_driver_config=serial_driver_config.data serial_driver.elf $(OBJCOPY) --update-section .serial_virt_tx_config=serial_virt_tx.data serial_virt_tx.elf $(OBJCOPY) --update-section .serial_client_config=serial_client_client.data client.elf diff --git a/flake.lock b/flake.lock index 0f1994f37..c611fca22 100644 --- a/flake.lock +++ b/flake.lock @@ -117,12 +117,13 @@ "zig-overlay": "zig-overlay" }, "locked": { - "lastModified": 1764823094, - "narHash": "sha256-Eeef5J0y31NH/F5Z0fayJ+4mroYfb8JPadu3cw6cNhc=", - "owner": "au-ts", - "repo": "microkit_sdf_gen", - "rev": "13686a373b9189c7b1d778cd749d7d8bbd8a63f6", - "type": "github" + "lastModified": 1773188563, + "narHash": "sha256-rIoi/joPD6Jpm84AnpHESjTvAMQUGamZP2ej5pFr15M=", + "ref": "refs/heads/sddf_pinctrl", + "rev": "1977e83776aae65e558505c9e95168aebe990b95", + "revCount": 779, + "type": "git", + "url": "file:///home/julia/ts/microkit_sdf_gen_pwm" }, "original": { "owner": "au-ts", @@ -218,11 +219,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1739707935, - "narHash": "sha256-YE+Zn03AWqDBmgxNrM50s7tpH+7fQYu1n9ZvXUWhznE=", + "lastModified": 1756037521, + "narHash": "sha256-nLOxILJqZtVCPCWppiDcEodTyguC6/EWvOprRuVoH4Q=", "owner": "mitchellh", "repo": "zig-overlay", - "rev": "18c6ec3d906c8f7e611687ca71705d6130e754c4", + "rev": "25d6cdf56abecb2b6a8e59905948ef41892aa371", "type": "github" }, "original": { diff --git a/tools/make/board/maaxboard.mk b/tools/make/board/maaxboard.mk index 049493501..83bd309f6 100644 --- a/tools/make/board/maaxboard.mk +++ b/tools/make/board/maaxboard.mk @@ -15,5 +15,6 @@ TIMER_DRIV_DIR := ${PLATFORM} UART_DRIV_DIR := ${PLATFORM} CLK_DRIV_DIR := ${PLATFORM} PWM_DRIV_DIR := imx +PINCTRL_DRIV_DIR := imx CPU := cortex-a53 diff --git a/tools/meta/board.py b/tools/meta/board.py index f65ccc854..93e14f27d 100644 --- a/tools/meta/board.py +++ b/tools/meta/board.py @@ -16,6 +16,7 @@ class Board: i2c: Optional[str] = None partition: int = 0 blk: Optional[str] = None + pinctrl: str = None baud_rate: Optional[int] = None @@ -75,6 +76,7 @@ class Board: timer="soc@0/bus@30000000/timer@302d0000", ethernet="soc@0/bus@30800000/ethernet@30be0000", blk="soc@0/bus@30800000/mmc@30b40000", + pinctrl="soc@0/bus@30000000/iomuxc@30330000", partition=2, ), Board( From ef3e6f38527c9893df62497b33ede8dc4ddf5837 Mon Sep 17 00:00:00 2001 From: Julia Vassiliki Date: Wed, 11 Mar 2026 15:30:44 +1100 Subject: [PATCH 7/9] cursed math that doesn't work Signed-off-by: Julia Vassiliki --- drivers/clk/clk.h | 2 +- drivers/pwm/imx/pwm.c | 191 +++++++++++++++++++++++++++++++++--------- examples/pwm/client.c | 21 ++++- examples/pwm/meta.py | 34 ++++++-- 4 files changed, 194 insertions(+), 54 deletions(-) diff --git a/drivers/clk/clk.h b/drivers/clk/clk.h index f6993c806..f5e366193 100644 --- a/drivers/clk/clk.h +++ b/drivers/clk/clk.h @@ -16,7 +16,7 @@ #define TIMER_CH 1 // Logging -/* #define DEBUG_DRIVER */ +#define DEBUG_DRIVER #ifdef DEBUG_DRIVER #define LOG_DRIVER(...) do{ sddf_dprintf("CLK DRIVER|INFO: "); sddf_dprintf(__VA_ARGS__); }while(0) diff --git a/drivers/pwm/imx/pwm.c b/drivers/pwm/imx/pwm.c index 1df8eec2b..e11a2db85 100644 --- a/drivers/pwm/imx/pwm.c +++ b/drivers/pwm/imx/pwm.c @@ -12,11 +12,11 @@ #include #include #include +#include +#include // TODO: Clock tree -// TODO: Pinmux -// TODO: support for more than one PWM output per client -// TODO: errata ERR051198 +// TODO: errata ERR051198? #define DEBUG_DRIVER @@ -28,14 +28,25 @@ #define LOG_DRIVER_ERR(...) do{ sddf_printf("PWM DRIVER|ERROR: "); sddf_printf(__VA_ARGS__); }while(0) - -#define CH_PWM_IRQ 0 +#define CH_CLOCK_PPC 0 #define CH_CONTROL_PPC 1 -// FIXME: hardcoded in meta.py -static volatile imx_pwm_regs_t *pwm_regs = (volatile void *)0x30000000; - -static inline uint32_t pwm_mk_control(uint16_t prescalar, uint8_t poutc) +#define NUM_PWM_CHANNELS 4 + +// FIXME: hardcoded in meta.py. clk_rate unknown til runtime +static struct pwm_channel_state { + volatile imx_pwm_regs_t *regs; + uint64_t clk_rate; + uint32_t clk_id; + uint32_t gate_clk_id; +} pwm_channel_states[NUM_PWM_CHANNELS] = { + [0] = { .regs = (volatile void *)0x30000000, .clk_id = IMX8MQ_CLK_PWM1, .gate_clk_id = IMX8MQ_CLK_PWM1_ROOT }, + [1] = { .regs = (volatile void *)0x30010000, .clk_id = IMX8MQ_CLK_PWM2, .gate_clk_id = IMX8MQ_CLK_PWM2_ROOT }, + [2] = { .regs = (volatile void *)0x30020000, .clk_id = IMX8MQ_CLK_PWM3, .gate_clk_id = IMX8MQ_CLK_PWM3_ROOT }, + [3] = { .regs = (volatile void *)0x30030000, .clk_id = IMX8MQ_CLK_PWM4, .gate_clk_id = IMX8MQ_CLK_PWM4_ROOT }, +}; + +static inline const uint32_t pwm_mk_control(uint16_t prescalar, uint8_t poutc) { assert(prescalar < BIT(PWMx_PWMCR__PRESCALAR_LEN)); assert(poutc < BIT(PWMx_PWMCR__POUTC_LEN)); @@ -66,7 +77,7 @@ static inline uint32_t pwm_mk_control(uint16_t prescalar, uint8_t poutc) ); } -static void pwm_sw_reset(void) +static void pwm_sw_reset(volatile imx_pwm_regs_t *pwm_regs) { /* disable the PWM by reset. Necessary to clear the 'sample' FIFO. */ pwm_regs->control = BIT(PWMx_PWMCR__SWR); @@ -81,22 +92,25 @@ static void pwm_sw_reset(void) } /* [IMX8MDQLQRM] Section 12.2.6 Disable Sequence for the PWM */ -static void pwm_disable(void) +static void pwm_disable(volatile imx_pwm_regs_t *pwm_regs) { /* Can be disabled at any time. Any data in FIFO will remain unless cleared by a software reset */ pwm_regs->control &= BIT(PWMx_PWMCR__EN); } -static void pwm_enable(void) +static void pwm_enable(volatile imx_pwm_regs_t *pwm_regs) { pwm_regs->control |= BIT(PWMx_PWMCR__EN); } /* [IMX8MDQLQRM] Section 12.2.4.1 Operation */ -static bool pwm_configure(uint64_t period_ns, uint64_t pulse_width_ns, int flags) +static bool pwm_configure(int channel, uint64_t period_ns, uint64_t pulse_width_ns, int flags) { - pwm_disable(); + volatile imx_pwm_regs_t *pwm_regs = pwm_channel_states[channel].regs; + uint64_t clk_rate = pwm_channel_states[channel].clk_rate; + + pwm_disable(pwm_regs); /* The PWM hardware has a 16-bit up-counter that counts from 0 to the period register, @@ -112,28 +126,94 @@ static bool pwm_configure(uint64_t period_ns, uint64_t pulse_width_ns, int flags poutc = PWMx_PWMCR__POUTC_NORMAL; } -#if 0 - /* The peripheral clock ticks up at a certain rate */ - // FIXME hacks? - uint64_t clk_rate = 83333333; + uint64_t clk_period_ns = (1 * 1000UL * 1000UL * 1000UL) / clk_rate; - uint64_t min_ns = clk_rate / /* prescale */ 4096 * 1 /* count */; - uint64_t max_ns = clk_rate / /* prescale */ 1 * 0xFFFE /* count */; -#endif + // -1 for value to put in prescale + const uint64_t max_prescale_v = 4096; + const uint64_t min_prescale_v = 1; + + // -2 for value to put in the period + const uint64_t max_period_register_v = 0x10000; + const uint64_t min_period_register_v = 0x2; - uint16_t prescalar = 0; - uint16_t period = 0x4000; - uint16_t sample = 0x1000; + const uint64_t min_period = clk_period_ns * min_prescale_v * min_period_register_v; + const uint64_t max_period = clk_period_ns * max_prescale_v * max_period_register_v; - // Writing 0xFFFF to this register will achieve the same result as writing 0xFFFE. - if (period == 0xFFFF) { - LOG_DRIVER_ERR("period would be out of range\n"); + LOG_DRIVER("clock period %ld\n", clk_period_ns); + LOG_DRIVER("min clock period %ld\n", min_period); + LOG_DRIVER("max clock period %ld\n", max_period); + + // We're also be lazy here + if (period_ns < min_period) { + LOG_DRIVER("requested period too small\n"); + return false; + } else if (period_ns > max_period) { + // FIXME: we could update root clock scales in this case + // we could potentially do it for min if we can increase the clk freq + LOG_DRIVER("requested period too large\n"); return false; } - // Note: PWMO (Hz) = PCLK(Hz) / (period +2). + // _v suffix means the interpretation of the value (for math) not the value you put somewhere. + + // Want: maximise the period register for precision + // Want: minimise the prescale for precision + + // TODO: should be possible to do this just based on the slope or whatever but I seem to be incapable of doing that + // in my head. + + + // XXXX: this is just really wrong for the higher prescales... + // kinda assumes == 0. + + // Find the range that covers `low_period` <... `period_ns` ...= `high_period` + // that has the largest values. Note we do equality on high and inequality on low. + uint64_t period_reg_v = max_period_register_v; + uint64_t high_period_ns = max_period; + uint64_t prescale_v = max_prescale_v; + while (prescale_v > min_prescale_v) { + // lower is considered the "next" + uint64_t low_period_ns = clk_period_ns * (prescale_v - 1) * period_reg_v; + + // LOG_DRIVER("Considering prescale %ld covering range %ld... %ld.... %ld\n", prescale_v, low_period_ns, period_ns, high_period_ns); + + if (low_period_ns < period_ns && period_ns <= high_period_ns) { + // LOG_DRIVER("... it fits\n"); + break; + } + + high_period_ns = low_period_ns; + prescale_v--; + } + + if (prescale_v == min_prescale_v) { + while (period_reg_v > min_period_register_v) { + uint64_t low_period_ns = clk_period_ns * prescale_v * (period_reg_v - 1); + + // LOG_DRIVER("Considering period reg %ld covering range %ld... %ld.... %ld\n", period_reg_v, low_period_ns, period_ns, high_period_ns); - // TODO: Settings PWM_PWMPR to 0xFFFF when PWMx_PWMCR REPEAT bits are set to non-zero values is not allowed. + if (low_period_ns < period_ns && period_ns <= high_period_ns) { + // LOG_DRIVER("... it fits\n"); + break; + } + + high_period_ns = low_period_ns; + period_reg_v--; + } + } + + // Done by the error checks earlier this cannot happen. + assert(period_reg_v != min_period_register_v); + + uint16_t prescalar = prescale_v - 1; + uint16_t period = period_reg_v - 2; + + // The sample (duty cycle) uses the same scale as the period. + uint64_t sample_v = ((clk_period_ns * prescale_v * period_reg_v) / period_ns) * pulse_width_ns; + // TODO: offset does this make sense? + uint16_t sample = sample_v - 1; + + LOG_DRIVER("prescalar in registers: %d, period: %d, sample: %d\n", prescalar, period, sample); pwm_regs->control = pwm_mk_control(prescalar, poutc); pwm_regs->sample = sample; @@ -141,11 +221,12 @@ static bool pwm_configure(uint64_t period_ns, uint64_t pulse_width_ns, int flags /* 4. Check/Clear status bits (w1c) to ensure they are all zero */ if (pwm_regs->status & PWMx_PWMSR__InterruptStatus_MASK) { - LOG_DRIVER_ERR("PWM status (error) bits are 1: 0x%x\n", pwm_regs->status); + pwm_regs->status = PWMx_PWMSR__InterruptStatus_MASK; + // This seems to always happen. We don't care about this, though. + // LOG_DRIVER("PWM status (error) bits are 1: 0x%x\n", pwm_regs->status); } - pwm_regs->status = PWMx_PWMSR__InterruptStatus_MASK; - pwm_enable(); + pwm_enable(pwm_regs); return true; } @@ -155,7 +236,21 @@ void init(void) { LOG_DRIVER("PWM starting\n"); - pwm_sw_reset(); + for (int i = 0; i < NUM_PWM_CHANNELS; i++) { + int ret; + + ret = sddf_clk_get_rate(CH_CLOCK_PPC, pwm_channel_states[i].clk_id, &pwm_channel_states[i].clk_rate); + assert(ret == 0); + + LOG_DRIVER("PWM%d clk rate is %ld Hz\n", i, pwm_channel_states[i].clk_rate); + + ret = sddf_clk_enable(CH_CLOCK_PPC, pwm_channel_states[i].gate_clk_id); + assert(ret == 0); + + pwm_sw_reset(pwm_channel_states[i].regs); + + LOG_DRIVER("PWM%d reset complete\n", i); + } } microkit_msginfo protected(microkit_channel ch, microkit_msginfo msginfo) @@ -164,28 +259,42 @@ microkit_msginfo protected(microkit_channel ch, microkit_msginfo msginfo) switch (microkit_msginfo_get_label(msginfo)) { case SDDF_PWM_PPC_ID_SET_NS: { - LOG_DRIVER("PPC set NS request\n"); + LOG_DRIVER("set period/pulse width ns request\n"); - seL4_Word channel = sddf_get_mr(SDDF_PWM_PPC_SLOT_CHANNEL); - // TODO: channel unsupported. - assert(channel == 0); + // TODO: check msginfo length + seL4_Word channel = sddf_get_mr(SDDF_PWM_PPC_SLOT_CHANNEL); seL4_Word period_ns = sddf_get_mr(SDDF_PWM_PPC_SLOT_PERIOD); seL4_Word pulse_width_ns = sddf_get_mr(SDDF_PWM_PPC_SLOT_PULSE_WIDTH); seL4_Word flags = sddf_get_mr(SDDF_PWM_PPC_SLOT_FLAGS); + LOG_DRIVER("... for pwm channel %ld\n", channel); + LOG_DRIVER("... with period ns %ld\n", period_ns); + LOG_DRIVER("... with pulse width ns %ld\n", pulse_width_ns); + LOG_DRIVER("... with flags 0x%lx\n", flags); + + if (channel >= NUM_PWM_CHANNELS) { + // Invalid channel. + return microkit_msginfo_new(SDDF_PWM_FAILURE, 0); + } + if (period_ns == 0 && pulse_width_ns == 0) { - pwm_disable(); + pwm_disable(pwm_channel_states[channel].regs); return microkit_msginfo_new(SDDF_PWM_SUCCESS, 0); + } else if (period_ns == 0) { + // Cannot have 0 period. + return microkit_msginfo_new(SDDF_PWM_FAILURE, 0); + } else if (pulse_width_ns >= period_ns) { + // Cannot have pulse width >= period as this would be duty > 100% + return microkit_msginfo_new(SDDF_PWM_FAILURE, 0); } else { - bool success = pwm_configure(period_ns, pulse_width_ns, flags); + bool success = pwm_configure(channel, period_ns, pulse_width_ns, flags); return microkit_msginfo_new(success ? SDDF_PWM_SUCCESS : SDDF_PWM_FAILURE, 0); } } default: LOG_DRIVER("Unknown request %lu from channel %u\n", microkit_msginfo_get_label(msginfo), ch); - // XXXX. - return microkit_msginfo_new(0, 0); + return microkit_msginfo_new(SDDF_PWM_FAILURE, 0); } } diff --git a/examples/pwm/client.c b/examples/pwm/client.c index ab53f9b37..d65fc8391 100644 --- a/examples/pwm/client.c +++ b/examples/pwm/client.c @@ -17,6 +17,9 @@ __attribute__((__section__(".serial_client_config"))) serial_client_config_t ser #define CH_PWM_CONTROL_PPC 0 +/* We use PWM2 (channel 1) which is connected to IO_GPIO13 by our pinctrl driver */ +#define PWM_CHANNEL 1 + void init(void) { assert(serial_config_check_magic(&serial_config)); @@ -28,17 +31,29 @@ void init(void) LOG_CLIENT("starting\n"); + bool success; while (1) { for (volatile int i = 0; i < 100000000; i++) {} - bool success = sddf_pwm_set_ns(CH_PWM_CONTROL_PPC, 0, /* period ns */ 500, /* pulse width ns */ 200, 0); + success = sddf_pwm_set_ns(CH_PWM_CONTROL_PPC, PWM_CHANNEL, /* period ns */ 500 * 1000, /* pulse width ns */ 200 * 1000, /* flags */ 0); assert(success); for (volatile int i = 0; i < 100000000; i++) {} - // disable - success = sddf_pwm_set_ns(CH_PWM_CONTROL_PPC, 0, /* period ns */ 0, /* pulse width ns */ 0, 0); + // disable + success = sddf_pwm_set_ns(CH_PWM_CONTROL_PPC, PWM_CHANNEL, /* period ns */ 0, /* pulse width ns */ 0, 0); assert(success); + + for (volatile int i = 0; i < 100000000; i++) {} + + // success = sddf_pwm_set_freq_duty(CH_PWM_CONTROL_PPC, PWM_CHANNEL, /* freq */ 1000, /* duty (/1000) */ 500, /* flags */ 0); + // assert(success); + + // for (volatile int i = 0; i < 100000000; i++) {} + + // // disable + // success = sddf_pwm_set_ns(CH_PWM_CONTROL_PPC, PWM_CHANNEL, /* period ns */ 0, /* pulse width ns */ 0, 0); + // assert(success); } LOG_CLIENT("done\n"); diff --git a/examples/pwm/meta.py b/examples/pwm/meta.py index aecc35330..c7a1e72e3 100644 --- a/examples/pwm/meta.py +++ b/examples/pwm/meta.py @@ -51,15 +51,23 @@ def generate(sdf_file: str, output_dir: str, dtb: DeviceTree): assert False # HACK for sdfgen. "soc@0/bus@30400000/pwm@30670000", - pwm2_mr = MemoryRegion(sdf, "pwm2", 0x10000, paddr=0x30670000) + pwm1_mr = MemoryRegion(sdf, "pwm1", 0x10_000, paddr=0x30660000) + pwm2_mr = MemoryRegion(sdf, "pwm2", 0x10_000, paddr=0x30670000) + pwm3_mr = MemoryRegion(sdf, "pwm3", 0x10_000, paddr=0x30680000) + pwm4_mr = MemoryRegion(sdf, "pwm4", 0x10_000, paddr=0x30690000) + sdf.add_mr(pwm1_mr) sdf.add_mr(pwm2_mr) - pwm_driver.add_map(Map(pwm2_mr, 0x30_000_000, "rw", cached=False)) - pwm_driver.add_irq(IrqConventional(82 + 32, IrqConventional.Trigger.EDGE)) - - chan = Channel(pwm_driver, client, pp_b=True, notify_a=False, notify_b=False) - sdf.add_channel(chan) - assert chan.pd_a_id == 1, chan.pd_a_id - assert chan.pd_b_id == 0, chan.pd_b_id + sdf.add_mr(pwm3_mr) + sdf.add_mr(pwm4_mr) + pwm_driver.add_map(Map(pwm1_mr, 0x30_000_000, "rw", cached=False)) + pwm_driver.add_map(Map(pwm2_mr, 0x30_010_000, "rw", cached=False)) + pwm_driver.add_map(Map(pwm3_mr, 0x30_020_000, "rw", cached=False)) + pwm_driver.add_map(Map(pwm4_mr, 0x30_030_000, "rw", cached=False)) + # Despite having IRQs, we never need them. + # pwm_driver.add_irq(IrqConventional(0x52 + 32, IrqConventional.Trigger.LEVEL)) + # pwm_driver.add_irq(IrqConventional(0x53 + 32, IrqConventional.Trigger.LEVEL)) + # pwm_driver.add_irq(IrqConventional(0x54 + 32, IrqConventional.Trigger.LEVEL)) + # pwm_driver.add_irq(IrqConventional(0x55 + 32, IrqConventional.Trigger.LEVEL)) # HACK for sdfgen @@ -83,8 +91,16 @@ def generate(sdf_file: str, output_dir: str, dtb: DeviceTree): exit(-1) - clk_channel = Channel(clk_driver, client, pp_b=True) + clk_channel = Channel(clk_driver, pwm_driver, pp_b=True) sdf.add_channel(clk_channel) + assert clk_channel.pd_b_id == 0, clk_channel.pd_b_id + + # clients + chan = Channel(pwm_driver, client, pp_b=True, notify_a=False, notify_b=False) + sdf.add_channel(chan) + assert chan.pd_a_id == 1, chan.pd_a_id + assert chan.pd_b_id == 0, chan.pd_b_id + serial_system = Sddf.Serial(sdf, serial_node, serial_driver, serial_virt_tx) serial_system.add_client(client) From 028f2c496bc9a1c2688d46359687b9bb94f2a809 Mon Sep 17 00:00:00 2001 From: Julia Vassiliki Date: Wed, 11 Mar 2026 15:47:10 +1100 Subject: [PATCH 8/9] better calculations stolen from linux Signed-off-by: Julia Vassiliki --- drivers/pwm/imx/pwm.c | 69 +++++++++++-------------------------------- 1 file changed, 17 insertions(+), 52 deletions(-) diff --git a/drivers/pwm/imx/pwm.c b/drivers/pwm/imx/pwm.c index e11a2db85..c206a1048 100644 --- a/drivers/pwm/imx/pwm.c +++ b/drivers/pwm/imx/pwm.c @@ -154,66 +154,31 @@ static bool pwm_configure(int channel, uint64_t period_ns, uint64_t pulse_width_ return false; } - // _v suffix means the interpretation of the value (for math) not the value you put somewhere. + // TODO: I ignore overflow etc - // Want: maximise the period register for precision - // Want: minimise the prescale for precision + uint64_t period_cycles = period_ns / clk_period_ns; + // add 1 to prevent div-by-zero + uint64_t prescale_v = period_cycles / max_period_register_v + 1; + period_cycles /= prescale_v; + uint64_t pulse_width_cycles = pulse_width_ns / clk_period_ns; + pulse_width_cycles /= prescale_v; - // TODO: should be possible to do this just based on the slope or whatever but I seem to be incapable of doing that - // in my head. + LOG_DRIVER("value of prescalar: %ld, period: %ld, sample: %ld\n", prescale_v, period_cycles, pulse_width_cycles); - - // XXXX: this is just really wrong for the higher prescales... - // kinda assumes == 0. - - // Find the range that covers `low_period` <... `period_ns` ...= `high_period` - // that has the largest values. Note we do equality on high and inequality on low. - uint64_t period_reg_v = max_period_register_v; - uint64_t high_period_ns = max_period; - uint64_t prescale_v = max_prescale_v; - while (prescale_v > min_prescale_v) { - // lower is considered the "next" - uint64_t low_period_ns = clk_period_ns * (prescale_v - 1) * period_reg_v; - - // LOG_DRIVER("Considering prescale %ld covering range %ld... %ld.... %ld\n", prescale_v, low_period_ns, period_ns, high_period_ns); - - if (low_period_ns < period_ns && period_ns <= high_period_ns) { - // LOG_DRIVER("... it fits\n"); - break; - } - - high_period_ns = low_period_ns; - prescale_v--; - } - - if (prescale_v == min_prescale_v) { - while (period_reg_v > min_period_register_v) { - uint64_t low_period_ns = clk_period_ns * prescale_v * (period_reg_v - 1); - - // LOG_DRIVER("Considering period reg %ld covering range %ld... %ld.... %ld\n", period_reg_v, low_period_ns, period_ns, high_period_ns); - - if (low_period_ns < period_ns && period_ns <= high_period_ns) { - // LOG_DRIVER("... it fits\n"); - break; - } - - high_period_ns = low_period_ns; - period_reg_v--; - } - } + assert(period_cycles <= max_period_register_v); + assert(period_cycles >= min_period_register_v); + uint16_t period = period_cycles - 2; // Done by the error checks earlier this cannot happen. - assert(period_reg_v != min_period_register_v); - + assert(prescale_v <= max_prescale_v); + assert(prescale_v >= min_prescale_v); uint16_t prescalar = prescale_v - 1; - uint16_t period = period_reg_v - 2; - // The sample (duty cycle) uses the same scale as the period. - uint64_t sample_v = ((clk_period_ns * prescale_v * period_reg_v) / period_ns) * pulse_width_ns; - // TODO: offset does this make sense? - uint16_t sample = sample_v - 1; + assert(pulse_width_cycles < BIT(16)); + assert(pulse_width_cycles >= 0); + uint16_t sample = pulse_width_cycles; - LOG_DRIVER("prescalar in registers: %d, period: %d, sample: %d\n", prescalar, period, sample); + LOG_DRIVER("in registers prescalar: %d, period: %d, sample: %d\n", prescalar, period, sample); pwm_regs->control = pwm_mk_control(prescalar, poutc); pwm_regs->sample = sample; From 1c16ea63c60d6973cfa9339c788cf5c39db2e38f Mon Sep 17 00:00:00 2001 From: Julia Vassiliki Date: Wed, 11 Mar 2026 15:49:26 +1100 Subject: [PATCH 9/9] test the freq + duty interface Signed-off-by: Julia Vassiliki --- examples/pwm/client.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/pwm/client.c b/examples/pwm/client.c index d65fc8391..c9901ef9e 100644 --- a/examples/pwm/client.c +++ b/examples/pwm/client.c @@ -46,14 +46,14 @@ void init(void) for (volatile int i = 0; i < 100000000; i++) {} - // success = sddf_pwm_set_freq_duty(CH_PWM_CONTROL_PPC, PWM_CHANNEL, /* freq */ 1000, /* duty (/1000) */ 500, /* flags */ 0); - // assert(success); + success = sddf_pwm_set_freq_duty(CH_PWM_CONTROL_PPC, PWM_CHANNEL, /* freq */ 1000, /* duty (/1000) */ 500, /* flags */ 0); + assert(success); - // for (volatile int i = 0; i < 100000000; i++) {} + for (volatile int i = 0; i < 100000000; i++) {} - // // disable - // success = sddf_pwm_set_ns(CH_PWM_CONTROL_PPC, PWM_CHANNEL, /* period ns */ 0, /* pulse width ns */ 0, 0); - // assert(success); + // disable + success = sddf_pwm_set_ns(CH_PWM_CONTROL_PPC, PWM_CHANNEL, /* period ns */ 0, /* pulse width ns */ 0, 0); + assert(success); } LOG_CLIENT("done\n");