From 4c6da5c6addb3e23a093a0700286123744b3ee99 Mon Sep 17 00:00:00 2001 From: itycodes Date: Thu, 12 Jun 2025 03:24:37 +0200 Subject: [PATCH] Initial commit --- ...-Windows-line-endings-with-Unix-ones.patch | 5501 +++++++++++++++++ 0002-Fix-build-errors-for-6.10.patch | 56 + 0003-Fix-Makefile-not-respecting-KDIR.patch | 35 + 0004-Update-to-6.12.patch | 27 + flake.nix | 52 + 5 files changed, 5671 insertions(+) create mode 100644 0001-Replace-Windows-line-endings-with-Unix-ones.patch create mode 100644 0002-Fix-build-errors-for-6.10.patch create mode 100644 0003-Fix-Makefile-not-respecting-KDIR.patch create mode 100644 0004-Update-to-6.12.patch create mode 100644 flake.nix diff --git a/0001-Replace-Windows-line-endings-with-Unix-ones.patch b/0001-Replace-Windows-line-endings-with-Unix-ones.patch new file mode 100644 index 0000000..a9dbba5 --- /dev/null +++ b/0001-Replace-Windows-line-endings-with-Unix-ones.patch @@ -0,0 +1,5501 @@ +From d182784a48ae1b86baaf1956e88139068b117e03 Mon Sep 17 00:00:00 2001 +From: itycodes +Date: Thu, 12 Jun 2025 03:02:51 +0200 +Subject: [PATCH 1/4] Replace Windows line endings with Unix ones + +--- + LICENSE.txt | 678 +++++++++++++-------------- + cc1101.h | 104 ++-- + cc1101_chrdev.c | 928 ++++++++++++++++++------------------ + cc1101_chrdev.h | 28 +- + cc1101_config.c | 1146 ++++++++++++++++++++++----------------------- + cc1101_config.h | 42 +- + cc1101_internal.h | 300 ++++++------ + cc1101_main.c | 380 +++++++-------- + cc1101_radio.c | 1030 ++++++++++++++++++++-------------------- + cc1101_radio.h | 30 +- + cc1101_spi.c | 674 +++++++++++++------------- + cc1101_spi.h | 62 +-- + 12 files changed, 2701 insertions(+), 2701 deletions(-) + +diff --git a/LICENSE.txt b/LICENSE.txt +index 89e08fb..d159169 100644 +--- a/LICENSE.txt ++++ b/LICENSE.txt +@@ -1,339 +1,339 @@ +- 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. +- +- GNU GENERAL PUBLIC LICENSE +- 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. +- +- +- Copyright (C) +- +- This program is free software; you can redistribute it and/or modify +- it under the terms of the GNU General Public License as published by +- the Free Software Foundation; either version 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. +- +- , 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. ++ 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. ++ ++ GNU GENERAL PUBLIC LICENSE ++ 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. ++ ++ ++ Copyright (C) ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 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. ++ ++ , 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. +diff --git a/cc1101.h b/cc1101.h +index 1a8d9d2..c50480d 100755 +--- a/cc1101.h ++++ b/cc1101.h +@@ -1,53 +1,53 @@ +-// SPDX-License-Identifier: GPL-2.0-only +-/* +-* Copyright (c) 2021 +-*/ +-#ifndef CC1101_H +-#define CC1101_H +- +-#define CONFIG_REGISTERS_LEN 0x2F +-#define PATABLE_LEN 8 +- +-typedef unsigned char cc1101_device_config_t[CONFIG_REGISTERS_LEN]; +-typedef unsigned char cc1101_patable_t[PATABLE_LEN]; +- +-#define MOD_2FSK 0 +-#define MOD_GFSK 1 +-#define MOD_OOK 3 +-#define MOD_4FSK 4 +-#define MOD_MSK 7 +- +-#define CS_DISABLED 0 +-#define CS_RELATIVE 1 +-#define CS_ABSOLUTE 2 +- +-struct cc1101_common_config { +- __u32 frequency; +- __u8 modulation; +- __u8 baud_rate_mantissa; +- __u8 baud_rate_exponent; +- __u8 deviation_mantissa; +- __u8 deviation_exponent; +- __u32 sync_word; +-}; +- +-// Message sent from userspace via IOCTL containing RX mode configuration parameters +-struct cc1101_rx_config { +- struct cc1101_common_config common; +- __u8 bandwidth_mantissa; +- __u8 bandwidth_exponent; +- __u8 max_lna_gain; +- __u8 max_dvga_gain; +- __u8 magn_target; +- __u8 carrier_sense_mode; +- __s8 carrier_sense; +- __u32 packet_length; +-}; +- +-// Message sent from userspace via IOCTL containing TX mode configuration parameters +-struct cc1101_tx_config { +- struct cc1101_common_config common; +- __u8 tx_power; +-}; +- ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++* Copyright (c) 2021 ++*/ ++#ifndef CC1101_H ++#define CC1101_H ++ ++#define CONFIG_REGISTERS_LEN 0x2F ++#define PATABLE_LEN 8 ++ ++typedef unsigned char cc1101_device_config_t[CONFIG_REGISTERS_LEN]; ++typedef unsigned char cc1101_patable_t[PATABLE_LEN]; ++ ++#define MOD_2FSK 0 ++#define MOD_GFSK 1 ++#define MOD_OOK 3 ++#define MOD_4FSK 4 ++#define MOD_MSK 7 ++ ++#define CS_DISABLED 0 ++#define CS_RELATIVE 1 ++#define CS_ABSOLUTE 2 ++ ++struct cc1101_common_config { ++ __u32 frequency; ++ __u8 modulation; ++ __u8 baud_rate_mantissa; ++ __u8 baud_rate_exponent; ++ __u8 deviation_mantissa; ++ __u8 deviation_exponent; ++ __u32 sync_word; ++}; ++ ++// Message sent from userspace via IOCTL containing RX mode configuration parameters ++struct cc1101_rx_config { ++ struct cc1101_common_config common; ++ __u8 bandwidth_mantissa; ++ __u8 bandwidth_exponent; ++ __u8 max_lna_gain; ++ __u8 max_dvga_gain; ++ __u8 magn_target; ++ __u8 carrier_sense_mode; ++ __s8 carrier_sense; ++ __u32 packet_length; ++}; ++ ++// Message sent from userspace via IOCTL containing TX mode configuration parameters ++struct cc1101_tx_config { ++ struct cc1101_common_config common; ++ __u8 tx_power; ++}; ++ + #endif +\ No newline at end of file +diff --git a/cc1101_chrdev.c b/cc1101_chrdev.c +index 489c569..21779b0 100644 +--- a/cc1101_chrdev.c ++++ b/cc1101_chrdev.c +@@ -1,465 +1,465 @@ +-// SPDX-License-Identifier: GPL-2.0-only +-/* +-* Copyright (c) 2021 +-*/ +-#include +-#include +- +-#include "cc1101_internal.h" +-#include "cc1101_chrdev.h" +-#include "cc1101_radio.h" +-#include "cc1101_spi.h" +-#include "cc1101_config.h" +- +-// IOCTL Definitions +-#define CC1101_BASE 'c' +-// Get the driver IOCTL version +-#define CC1101_GET_VERSION _IOR(CC1101_BASE, 0, uint) +-// Reset the device +-#define CC1101_RESET _IO(CC1101_BASE, 1) +-// Set TX configuration within driver +-#define CC1101_SET_TX_CONF _IOW(CC1101_BASE, 2, struct cc1101_tx_config) +-// Set RX configuration within driver +-#define CC1101_SET_RX_CONF _IOW(CC1101_BASE, 3, struct cc1101_rx_config) +-// Get TX configuration from driver +-#define CC1101_GET_TX_CONF _IOR(CC1101_BASE, 4, struct cc1101_tx_config) +-// Get TX configuration registers from driver +-#define CC1101_GET_TX_RAW_CONF _IOR(CC1101_BASE, 5, cc1101_device_config_t) +-// Get RX configuration from driver +-#define CC1101_GET_RX_CONF _IOR(CC1101_BASE, 6, struct cc1101_rx_config) +-// Get RX configuration registers from driver +-#define CC1101_GET_RX_RAW_CONF _IOR(CC1101_BASE, 7, cc1101_device_config_t) +-// Read configuration registers from hardware +-#define CC1101_GET_DEV_RAW_CONF _IOR(CC1101_BASE, 8, cc1101_device_config_t) +-// Get the current RSSI +-#define CC1101_GET_RSSI _IOR(CC1101_BASE, 9, unsigned char) +-// Get the configured maximum packet size +-#define CC1101_GET_MAX_PACKET_SIZE _IOR(CC1101_BASE, 10, u32) +- +-#define SPI_MAJOR_NUMBER 153 +-#define N_SPI_MINOR_NUMBERS 12 +- +-extern uint max_packet_size; +-extern uint rx_fifo_size; +- +-static struct class *dev_class; +-cc1101_t* device_list[N_SPI_MINOR_NUMBERS] = {0}; +-static DEFINE_MUTEX(device_list_lock); +- +-/* +-* Handler for open events to /dev/cc1101.x.x +-* Only one handle is allowed to transmit, receive or configure the device at a time +-* Operations will block until /dev/cc1101.x.x is closed +-*/ +-static int chrdev_open(struct inode *inode, struct file *file) +-{ +- cc1101_t* cc1101 = NULL; +- int device_index; +- +- // Search the device list for the cc1101 struct relating to the chardev +- if(mutex_lock_interruptible(&device_list_lock) != 0) { +- return -EBUSY; +- } +- +- for(device_index = 0; device_index < N_SPI_MINOR_NUMBERS; device_index++){ +- if (device_list[device_index] != NULL){ +- if (inode->i_rdev == device_list[device_index]->devt) { +- cc1101 = device_list[device_index]; +- } +- } +- } +- +- mutex_unlock(&device_list_lock); +- +- // This should never occur - a chardev shouldn't be created without a CC1101 being present +- if(cc1101 == NULL){ +- return -ENODEV; +- } +- +- // Once found, lock the device and save the pointer to private data for the subsequent functions +- if(mutex_lock_interruptible(&cc1101->chrdev_lock) != 0) { +- return -EBUSY; +- }; +- +- file->private_data = cc1101; +- return 0; +-} +- +-/* +-* Handler for close events to /dev/cc1101.x.x +-* Releases the device lock obtained at open +-*/ +-static int chrdev_release(struct inode *inode, struct file *file) +-{ +- cc1101_t* cc1101 = file->private_data; +- mutex_unlock(&cc1101->chrdev_lock); +- file->private_data = NULL; +- return 0; +-} +- +-/* +-* Handler for iotcl commands to /dev/cc1101.x.x +-* IOCTLs can be used to set and get the TX and RX config, reset the device +-* and retrieve the contents of the CC1101's registers +-*/ +-static long chrdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +-{ +- cc1101_t* cc1101 = file->private_data; +- spi_transaction_t rssi; +- int version = DRIVER_VERSION; +- int ret = 0; +- +- // Temporary holding variables for new TX and RX configs +- cc1101_device_config_t device_config; +-#ifndef RXONLY +- struct cc1101_tx_config tx_config; +-#endif +- struct cc1101_rx_config rx_config; +- +- if(_IOC_TYPE(cmd) != CC1101_BASE){ +- CC1101_ERROR(cc1101, "Invalid IOCTL\n"); +- return -EIO; +- } +- +- // Lock the device for reconfiguration +- if(mutex_lock_interruptible(&cc1101->device_lock) != 0) { +- return -EBUSY; +- }; +- +- switch(cmd){ +- +- // Get the userspace API version +- case CC1101_GET_VERSION: +- ret = copy_to_user((unsigned char*) arg, &version, sizeof(version)); +- break; +- +- // Reset the device and driver state +- case CC1101_RESET: +- CC1101_INFO(cc1101, "Reset"); +- cc1101_reset(cc1101); +- ret = 0; +- break; +- +- // Set the RX config for the device +- case CC1101_SET_RX_CONF: +- +- // Copy the config provided in userspace to the kernel +- if(copy_from_user(&rx_config, (unsigned char*) arg, sizeof(rx_config)) != 0){ +- CC1101_ERROR(cc1101, "Error Copying Device RX Config\n"); +- ret = -EFAULT; +- goto done; +- } +- +- // Validate the provided config +- if(!cc1101_config_validate_rx(cc1101, &rx_config)){ +- ret = -EINVAL; +- goto done; +- } +- +- // Replace the RX FIFO with a new one based on the provided packet size and the maximum number of queued packets +- kfifo_free(&cc1101->rx_fifo); +- if(kfifo_alloc(&cc1101->rx_fifo, rx_config.packet_length * rx_fifo_size, GFP_KERNEL) != 0) { +- CC1101_ERROR(cc1101, "Failed to allocate packet FIFO memory"); +- ret = -ENOMEM; +- goto done; +- } +- +- // Store the new RX config in the device struct +- memcpy(&cc1101->rx_config, &rx_config, sizeof(struct cc1101_rx_config)); +- +- // Set the device to idle before reconfiguring +- cc1101_idle(cc1101); +- +- // Write the configuration to the device +- cc1101_config_apply_rx(cc1101); +- +- // Enter RX mode on the device +- cc1101_rx(cc1101); +- +- ret = 0; +- break; +- +- // Returns the RX config configured in the driver to userspace +- case CC1101_GET_RX_CONF: +- ret = copy_to_user((unsigned char*) arg, &cc1101->rx_config, sizeof(struct cc1101_rx_config)); +- break; +- +- // Returns the register values for the RX configuration to userspace +- case CC1101_GET_RX_RAW_CONF: +- cc1101_config_rx_to_registers(device_config, &cc1101->rx_config); +- ret = copy_to_user((unsigned char*) arg, device_config, sizeof(device_config)); +- break; +- +- // Set the TX config to use for the next packet written to /dev/cc1101.x.x +- case CC1101_SET_TX_CONF: +-#ifndef RXONLY +- // Copy the config provided in userspace to the kernel +- if(copy_from_user(&tx_config, (unsigned char*) arg, sizeof(tx_config)) != 0){ +- CC1101_ERROR(cc1101, "Error Copying Device TX Config\n"); +- ret = -EFAULT; +- goto done; +- } +- +- // Validate the provided config +- if(!cc1101_config_validate_tx(cc1101, &tx_config)){ +- ret = -EINVAL; +- goto done; +- } +- +- // Store the new TX config in the device struct +- memcpy(&cc1101->tx_config, &tx_config, sizeof(struct cc1101_tx_config)); +- +- ret = 0; +-#else +- ret = -EPERM; +-#endif +- break; +- +- // Returns the TX config configured in the driver to userspace +- case CC1101_GET_TX_CONF: +-#ifndef RXONLY +- ret = copy_to_user((unsigned char*) arg, &cc1101->tx_config, sizeof(struct cc1101_tx_config)); +-#else +- ret = -EPERM; +-#endif +- break; +- +- // Returns the register values for the TX configuration to userspace +- case CC1101_GET_TX_RAW_CONF: +-#ifndef RXONLY +- cc1101_config_tx_to_registers(device_config, &cc1101->tx_config); +- ret = copy_to_user((unsigned char*) arg, device_config, sizeof(device_config)); +-#else +- ret = -EPERM; +-#endif +- break; +- +- // Reads the current state of the CC1101's registers and returns them to userspace +- case CC1101_GET_DEV_RAW_CONF: +- cc1101_spi_read_config_registers(cc1101, device_config, sizeof(device_config)); +- ret = copy_to_user((unsigned char*) arg, device_config, sizeof(device_config)); +- break; +- +- case CC1101_GET_RSSI: +- rssi = cc1101_spi_read_status_register_once(cc1101, RSSI); +- ret = copy_to_user((unsigned char*) arg, &rssi.data, sizeof(rssi.data)); +- break; +- +- case CC1101_GET_MAX_PACKET_SIZE: +- ret = copy_to_user((unsigned char*) arg, &max_packet_size, sizeof(max_packet_size)); +- break; +- +- default: +- CC1101_ERROR(cc1101, "Unknown Command %d, %zu, %zu\n", cmd, sizeof(struct cc1101_rx_config), sizeof(struct cc1101_tx_config)); +- ret = -EIO; +- break; +- } +- +-done: +- mutex_unlock(&cc1101->device_lock); +- return ret; +-} +- +-/* +-* Handler for read events to /dev/cc1101.x.x +-* A read request will return one packet from the receive buffer, if present +-*/ +-static ssize_t chrdev_read(struct file *file, char __user *buf, size_t len, loff_t *off) +-{ +- cc1101_t* cc1101 = file->private_data; +- ssize_t ret; +- unsigned int out_bytes; +- +- // Check a RX config has been set and that the out buffer is the correct size +- if (cc1101->rx_config.packet_length == 0 || len != cc1101->rx_config.packet_length) { +- return -EMSGSIZE; +- } +- +- if(mutex_lock_interruptible(&cc1101->device_lock) != 0) { +- return -EBUSY; +- }; +- +- // Check there is at least one packet in the RX FIFO +- if (kfifo_len(&cc1101->rx_fifo) < cc1101->rx_config.packet_length) { +- ret = -ENOMSG; +- goto done; +- } +- +- // Copy the packet out to userspace +- if (kfifo_to_user(&cc1101->rx_fifo, buf, len, &out_bytes) != 0) { +- ret = -EFAULT; +- goto done; +- } +- +- // Check the number of bytes copied out matches the expected number +- if (out_bytes == cc1101->rx_config.packet_length) { +- ret = out_bytes; +- } +- else { +- ret = -EFAULT; +- } +- +-done: +- mutex_unlock(&cc1101->device_lock); +- return ret; +-} +- +-/* +-* Handler for write events to /dev/cc1101.x.x +-* Written bytes are transmitted by the CC1101 according the TX config +-*/ +-static ssize_t chrdev_write(struct file *file, const char __user *buf, size_t len, loff_t *off) +-{ +-#ifndef RXONLY +- cc1101_t* cc1101 = file->private_data; +- ssize_t ret; +- unsigned char *tx_bytes; +- +- +- // Check the number of bytes to be transmitted are allowed +- if (len > max_packet_size) { +- ret = -EMSGSIZE; +- goto done; +- } +- +- // Allocate a temporary buffer for the bytes to be transmitted +- tx_bytes = kmalloc(len, GFP_KERNEL); +- if(tx_bytes == NULL) { +- ret = -ENOMEM; +- goto done; +- } +- +- // Copy from userspace to temporary buffer in kernel space +- if(copy_from_user(tx_bytes, buf, len) != 0) { +- ret = -EFAULT; +- goto err_copy; +- } +- +- // Lock the device for reconfiguration +- if(mutex_lock_interruptible(&cc1101->device_lock) != 0) { +- ret = -EBUSY; +- goto err_lock; +- }; +- +- // Set the device to idle before configuring +- cc1101_idle(cc1101); +- +- // Apply the TX config +- cc1101_config_apply_tx(cc1101); +- +- // Transmit bytes using the device and return the number of transmitted bytes +- cc1101_tx(cc1101, tx_bytes, len); +- ret = len; +- +- mutex_unlock(&cc1101->device_lock); +-err_lock: +-err_copy: +- kfree(tx_bytes); +-done: +- return ret; +-#else +- return -EPERM; +-#endif +-} +- +-/* +-* Add a character device for a cc1101 +-*/ +-int cc1101_chrdev_add_device(cc1101_t * cc1101) { +- int ret; +- int device_index; +- +- mutex_lock(&device_list_lock); +- +- // Search for a free minor number +- for(device_index = 0; device_index < N_SPI_MINOR_NUMBERS; device_index++){ +- if(device_list[device_index] == NULL){ +- // Allocate the minor number +- cc1101->devt = MKDEV(SPI_MAJOR_NUMBER, device_index); +- +- // Create a /dev/cc1101.x.x character device +- if(IS_ERR(device_create(dev_class, &cc1101->spi->dev, cc1101->devt, cc1101, "cc1101.%d.%d", cc1101->spi->master->bus_num, cc1101->spi->chip_select))) { +- ret = -ENODEV; +- goto done; +- } +- +- // Insert the device in the device list +- device_list[device_index] = cc1101; +- ret = 0; +- goto done; +- } +- } +- ret = -ENODEV; +- +-done: +- mutex_unlock(&device_list_lock); +- return ret; +-} +- +-/* +-* Remove a cc1101 character device +-*/ +-void cc1101_chrdev_remove_device(cc1101_t * cc1101) { +- mutex_lock(&device_list_lock); +- +- // Destroy the character device and remove the entry from the device list +- device_destroy(dev_class, cc1101->devt); +- device_list[MINOR(cc1101->devt)] = NULL; +- +- mutex_unlock(&device_list_lock); +-} +- +-// Chardev operation functions +-static struct file_operations fops = +-{ +- .owner = THIS_MODULE, +- .read = chrdev_read, +- .write = chrdev_write, +- .unlocked_ioctl = chrdev_ioctl, +- .open = chrdev_open, +- .release = chrdev_release, +-}; +- +-/* +-* Setup the CC1101 character device class +-*/ +-int cc1101_chrdev_setup(struct spi_driver* cc1101_driver) +-{ +- int ret; +- +- ret = register_chrdev(SPI_MAJOR_NUMBER, "spi", &fops); +- if (ret < 0) { +- goto err_register; +- } +- +- dev_class = class_create(THIS_MODULE, "cc1101"); +- if (IS_ERR(dev_class)) { +- ret = PTR_ERR(dev_class); +- goto err_class_create; +- } +- +- ret = spi_register_driver(cc1101_driver); +- if (ret < 0) { +- goto err_register_driver; +- } +- +- goto done; +- +-err_register_driver: +- class_destroy(dev_class); +-err_class_create: +- unregister_chrdev(SPI_MAJOR_NUMBER, cc1101_driver->driver.name); +-err_register: +-done: +- return ret; +-} +- +-/* +-* Remove the CC1101 character device class +-*/ +-void cc1101_chrdev_teardown(struct spi_driver* cc1101_driver) +-{ +- spi_unregister_driver(cc1101_driver); +- class_destroy(dev_class); +- unregister_chrdev(SPI_MAJOR_NUMBER, cc1101_driver->driver.name); ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++* Copyright (c) 2021 ++*/ ++#include ++#include ++ ++#include "cc1101_internal.h" ++#include "cc1101_chrdev.h" ++#include "cc1101_radio.h" ++#include "cc1101_spi.h" ++#include "cc1101_config.h" ++ ++// IOCTL Definitions ++#define CC1101_BASE 'c' ++// Get the driver IOCTL version ++#define CC1101_GET_VERSION _IOR(CC1101_BASE, 0, uint) ++// Reset the device ++#define CC1101_RESET _IO(CC1101_BASE, 1) ++// Set TX configuration within driver ++#define CC1101_SET_TX_CONF _IOW(CC1101_BASE, 2, struct cc1101_tx_config) ++// Set RX configuration within driver ++#define CC1101_SET_RX_CONF _IOW(CC1101_BASE, 3, struct cc1101_rx_config) ++// Get TX configuration from driver ++#define CC1101_GET_TX_CONF _IOR(CC1101_BASE, 4, struct cc1101_tx_config) ++// Get TX configuration registers from driver ++#define CC1101_GET_TX_RAW_CONF _IOR(CC1101_BASE, 5, cc1101_device_config_t) ++// Get RX configuration from driver ++#define CC1101_GET_RX_CONF _IOR(CC1101_BASE, 6, struct cc1101_rx_config) ++// Get RX configuration registers from driver ++#define CC1101_GET_RX_RAW_CONF _IOR(CC1101_BASE, 7, cc1101_device_config_t) ++// Read configuration registers from hardware ++#define CC1101_GET_DEV_RAW_CONF _IOR(CC1101_BASE, 8, cc1101_device_config_t) ++// Get the current RSSI ++#define CC1101_GET_RSSI _IOR(CC1101_BASE, 9, unsigned char) ++// Get the configured maximum packet size ++#define CC1101_GET_MAX_PACKET_SIZE _IOR(CC1101_BASE, 10, u32) ++ ++#define SPI_MAJOR_NUMBER 153 ++#define N_SPI_MINOR_NUMBERS 12 ++ ++extern uint max_packet_size; ++extern uint rx_fifo_size; ++ ++static struct class *dev_class; ++cc1101_t* device_list[N_SPI_MINOR_NUMBERS] = {0}; ++static DEFINE_MUTEX(device_list_lock); ++ ++/* ++* Handler for open events to /dev/cc1101.x.x ++* Only one handle is allowed to transmit, receive or configure the device at a time ++* Operations will block until /dev/cc1101.x.x is closed ++*/ ++static int chrdev_open(struct inode *inode, struct file *file) ++{ ++ cc1101_t* cc1101 = NULL; ++ int device_index; ++ ++ // Search the device list for the cc1101 struct relating to the chardev ++ if(mutex_lock_interruptible(&device_list_lock) != 0) { ++ return -EBUSY; ++ } ++ ++ for(device_index = 0; device_index < N_SPI_MINOR_NUMBERS; device_index++){ ++ if (device_list[device_index] != NULL){ ++ if (inode->i_rdev == device_list[device_index]->devt) { ++ cc1101 = device_list[device_index]; ++ } ++ } ++ } ++ ++ mutex_unlock(&device_list_lock); ++ ++ // This should never occur - a chardev shouldn't be created without a CC1101 being present ++ if(cc1101 == NULL){ ++ return -ENODEV; ++ } ++ ++ // Once found, lock the device and save the pointer to private data for the subsequent functions ++ if(mutex_lock_interruptible(&cc1101->chrdev_lock) != 0) { ++ return -EBUSY; ++ }; ++ ++ file->private_data = cc1101; ++ return 0; ++} ++ ++/* ++* Handler for close events to /dev/cc1101.x.x ++* Releases the device lock obtained at open ++*/ ++static int chrdev_release(struct inode *inode, struct file *file) ++{ ++ cc1101_t* cc1101 = file->private_data; ++ mutex_unlock(&cc1101->chrdev_lock); ++ file->private_data = NULL; ++ return 0; ++} ++ ++/* ++* Handler for iotcl commands to /dev/cc1101.x.x ++* IOCTLs can be used to set and get the TX and RX config, reset the device ++* and retrieve the contents of the CC1101's registers ++*/ ++static long chrdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) ++{ ++ cc1101_t* cc1101 = file->private_data; ++ spi_transaction_t rssi; ++ int version = DRIVER_VERSION; ++ int ret = 0; ++ ++ // Temporary holding variables for new TX and RX configs ++ cc1101_device_config_t device_config; ++#ifndef RXONLY ++ struct cc1101_tx_config tx_config; ++#endif ++ struct cc1101_rx_config rx_config; ++ ++ if(_IOC_TYPE(cmd) != CC1101_BASE){ ++ CC1101_ERROR(cc1101, "Invalid IOCTL\n"); ++ return -EIO; ++ } ++ ++ // Lock the device for reconfiguration ++ if(mutex_lock_interruptible(&cc1101->device_lock) != 0) { ++ return -EBUSY; ++ }; ++ ++ switch(cmd){ ++ ++ // Get the userspace API version ++ case CC1101_GET_VERSION: ++ ret = copy_to_user((unsigned char*) arg, &version, sizeof(version)); ++ break; ++ ++ // Reset the device and driver state ++ case CC1101_RESET: ++ CC1101_INFO(cc1101, "Reset"); ++ cc1101_reset(cc1101); ++ ret = 0; ++ break; ++ ++ // Set the RX config for the device ++ case CC1101_SET_RX_CONF: ++ ++ // Copy the config provided in userspace to the kernel ++ if(copy_from_user(&rx_config, (unsigned char*) arg, sizeof(rx_config)) != 0){ ++ CC1101_ERROR(cc1101, "Error Copying Device RX Config\n"); ++ ret = -EFAULT; ++ goto done; ++ } ++ ++ // Validate the provided config ++ if(!cc1101_config_validate_rx(cc1101, &rx_config)){ ++ ret = -EINVAL; ++ goto done; ++ } ++ ++ // Replace the RX FIFO with a new one based on the provided packet size and the maximum number of queued packets ++ kfifo_free(&cc1101->rx_fifo); ++ if(kfifo_alloc(&cc1101->rx_fifo, rx_config.packet_length * rx_fifo_size, GFP_KERNEL) != 0) { ++ CC1101_ERROR(cc1101, "Failed to allocate packet FIFO memory"); ++ ret = -ENOMEM; ++ goto done; ++ } ++ ++ // Store the new RX config in the device struct ++ memcpy(&cc1101->rx_config, &rx_config, sizeof(struct cc1101_rx_config)); ++ ++ // Set the device to idle before reconfiguring ++ cc1101_idle(cc1101); ++ ++ // Write the configuration to the device ++ cc1101_config_apply_rx(cc1101); ++ ++ // Enter RX mode on the device ++ cc1101_rx(cc1101); ++ ++ ret = 0; ++ break; ++ ++ // Returns the RX config configured in the driver to userspace ++ case CC1101_GET_RX_CONF: ++ ret = copy_to_user((unsigned char*) arg, &cc1101->rx_config, sizeof(struct cc1101_rx_config)); ++ break; ++ ++ // Returns the register values for the RX configuration to userspace ++ case CC1101_GET_RX_RAW_CONF: ++ cc1101_config_rx_to_registers(device_config, &cc1101->rx_config); ++ ret = copy_to_user((unsigned char*) arg, device_config, sizeof(device_config)); ++ break; ++ ++ // Set the TX config to use for the next packet written to /dev/cc1101.x.x ++ case CC1101_SET_TX_CONF: ++#ifndef RXONLY ++ // Copy the config provided in userspace to the kernel ++ if(copy_from_user(&tx_config, (unsigned char*) arg, sizeof(tx_config)) != 0){ ++ CC1101_ERROR(cc1101, "Error Copying Device TX Config\n"); ++ ret = -EFAULT; ++ goto done; ++ } ++ ++ // Validate the provided config ++ if(!cc1101_config_validate_tx(cc1101, &tx_config)){ ++ ret = -EINVAL; ++ goto done; ++ } ++ ++ // Store the new TX config in the device struct ++ memcpy(&cc1101->tx_config, &tx_config, sizeof(struct cc1101_tx_config)); ++ ++ ret = 0; ++#else ++ ret = -EPERM; ++#endif ++ break; ++ ++ // Returns the TX config configured in the driver to userspace ++ case CC1101_GET_TX_CONF: ++#ifndef RXONLY ++ ret = copy_to_user((unsigned char*) arg, &cc1101->tx_config, sizeof(struct cc1101_tx_config)); ++#else ++ ret = -EPERM; ++#endif ++ break; ++ ++ // Returns the register values for the TX configuration to userspace ++ case CC1101_GET_TX_RAW_CONF: ++#ifndef RXONLY ++ cc1101_config_tx_to_registers(device_config, &cc1101->tx_config); ++ ret = copy_to_user((unsigned char*) arg, device_config, sizeof(device_config)); ++#else ++ ret = -EPERM; ++#endif ++ break; ++ ++ // Reads the current state of the CC1101's registers and returns them to userspace ++ case CC1101_GET_DEV_RAW_CONF: ++ cc1101_spi_read_config_registers(cc1101, device_config, sizeof(device_config)); ++ ret = copy_to_user((unsigned char*) arg, device_config, sizeof(device_config)); ++ break; ++ ++ case CC1101_GET_RSSI: ++ rssi = cc1101_spi_read_status_register_once(cc1101, RSSI); ++ ret = copy_to_user((unsigned char*) arg, &rssi.data, sizeof(rssi.data)); ++ break; ++ ++ case CC1101_GET_MAX_PACKET_SIZE: ++ ret = copy_to_user((unsigned char*) arg, &max_packet_size, sizeof(max_packet_size)); ++ break; ++ ++ default: ++ CC1101_ERROR(cc1101, "Unknown Command %d, %zu, %zu\n", cmd, sizeof(struct cc1101_rx_config), sizeof(struct cc1101_tx_config)); ++ ret = -EIO; ++ break; ++ } ++ ++done: ++ mutex_unlock(&cc1101->device_lock); ++ return ret; ++} ++ ++/* ++* Handler for read events to /dev/cc1101.x.x ++* A read request will return one packet from the receive buffer, if present ++*/ ++static ssize_t chrdev_read(struct file *file, char __user *buf, size_t len, loff_t *off) ++{ ++ cc1101_t* cc1101 = file->private_data; ++ ssize_t ret; ++ unsigned int out_bytes; ++ ++ // Check a RX config has been set and that the out buffer is the correct size ++ if (cc1101->rx_config.packet_length == 0 || len != cc1101->rx_config.packet_length) { ++ return -EMSGSIZE; ++ } ++ ++ if(mutex_lock_interruptible(&cc1101->device_lock) != 0) { ++ return -EBUSY; ++ }; ++ ++ // Check there is at least one packet in the RX FIFO ++ if (kfifo_len(&cc1101->rx_fifo) < cc1101->rx_config.packet_length) { ++ ret = -ENOMSG; ++ goto done; ++ } ++ ++ // Copy the packet out to userspace ++ if (kfifo_to_user(&cc1101->rx_fifo, buf, len, &out_bytes) != 0) { ++ ret = -EFAULT; ++ goto done; ++ } ++ ++ // Check the number of bytes copied out matches the expected number ++ if (out_bytes == cc1101->rx_config.packet_length) { ++ ret = out_bytes; ++ } ++ else { ++ ret = -EFAULT; ++ } ++ ++done: ++ mutex_unlock(&cc1101->device_lock); ++ return ret; ++} ++ ++/* ++* Handler for write events to /dev/cc1101.x.x ++* Written bytes are transmitted by the CC1101 according the TX config ++*/ ++static ssize_t chrdev_write(struct file *file, const char __user *buf, size_t len, loff_t *off) ++{ ++#ifndef RXONLY ++ cc1101_t* cc1101 = file->private_data; ++ ssize_t ret; ++ unsigned char *tx_bytes; ++ ++ ++ // Check the number of bytes to be transmitted are allowed ++ if (len > max_packet_size) { ++ ret = -EMSGSIZE; ++ goto done; ++ } ++ ++ // Allocate a temporary buffer for the bytes to be transmitted ++ tx_bytes = kmalloc(len, GFP_KERNEL); ++ if(tx_bytes == NULL) { ++ ret = -ENOMEM; ++ goto done; ++ } ++ ++ // Copy from userspace to temporary buffer in kernel space ++ if(copy_from_user(tx_bytes, buf, len) != 0) { ++ ret = -EFAULT; ++ goto err_copy; ++ } ++ ++ // Lock the device for reconfiguration ++ if(mutex_lock_interruptible(&cc1101->device_lock) != 0) { ++ ret = -EBUSY; ++ goto err_lock; ++ }; ++ ++ // Set the device to idle before configuring ++ cc1101_idle(cc1101); ++ ++ // Apply the TX config ++ cc1101_config_apply_tx(cc1101); ++ ++ // Transmit bytes using the device and return the number of transmitted bytes ++ cc1101_tx(cc1101, tx_bytes, len); ++ ret = len; ++ ++ mutex_unlock(&cc1101->device_lock); ++err_lock: ++err_copy: ++ kfree(tx_bytes); ++done: ++ return ret; ++#else ++ return -EPERM; ++#endif ++} ++ ++/* ++* Add a character device for a cc1101 ++*/ ++int cc1101_chrdev_add_device(cc1101_t * cc1101) { ++ int ret; ++ int device_index; ++ ++ mutex_lock(&device_list_lock); ++ ++ // Search for a free minor number ++ for(device_index = 0; device_index < N_SPI_MINOR_NUMBERS; device_index++){ ++ if(device_list[device_index] == NULL){ ++ // Allocate the minor number ++ cc1101->devt = MKDEV(SPI_MAJOR_NUMBER, device_index); ++ ++ // Create a /dev/cc1101.x.x character device ++ if(IS_ERR(device_create(dev_class, &cc1101->spi->dev, cc1101->devt, cc1101, "cc1101.%d.%d", cc1101->spi->master->bus_num, cc1101->spi->chip_select))) { ++ ret = -ENODEV; ++ goto done; ++ } ++ ++ // Insert the device in the device list ++ device_list[device_index] = cc1101; ++ ret = 0; ++ goto done; ++ } ++ } ++ ret = -ENODEV; ++ ++done: ++ mutex_unlock(&device_list_lock); ++ return ret; ++} ++ ++/* ++* Remove a cc1101 character device ++*/ ++void cc1101_chrdev_remove_device(cc1101_t * cc1101) { ++ mutex_lock(&device_list_lock); ++ ++ // Destroy the character device and remove the entry from the device list ++ device_destroy(dev_class, cc1101->devt); ++ device_list[MINOR(cc1101->devt)] = NULL; ++ ++ mutex_unlock(&device_list_lock); ++} ++ ++// Chardev operation functions ++static struct file_operations fops = ++{ ++ .owner = THIS_MODULE, ++ .read = chrdev_read, ++ .write = chrdev_write, ++ .unlocked_ioctl = chrdev_ioctl, ++ .open = chrdev_open, ++ .release = chrdev_release, ++}; ++ ++/* ++* Setup the CC1101 character device class ++*/ ++int cc1101_chrdev_setup(struct spi_driver* cc1101_driver) ++{ ++ int ret; ++ ++ ret = register_chrdev(SPI_MAJOR_NUMBER, "spi", &fops); ++ if (ret < 0) { ++ goto err_register; ++ } ++ ++ dev_class = class_create(THIS_MODULE, "cc1101"); ++ if (IS_ERR(dev_class)) { ++ ret = PTR_ERR(dev_class); ++ goto err_class_create; ++ } ++ ++ ret = spi_register_driver(cc1101_driver); ++ if (ret < 0) { ++ goto err_register_driver; ++ } ++ ++ goto done; ++ ++err_register_driver: ++ class_destroy(dev_class); ++err_class_create: ++ unregister_chrdev(SPI_MAJOR_NUMBER, cc1101_driver->driver.name); ++err_register: ++done: ++ return ret; ++} ++ ++/* ++* Remove the CC1101 character device class ++*/ ++void cc1101_chrdev_teardown(struct spi_driver* cc1101_driver) ++{ ++ spi_unregister_driver(cc1101_driver); ++ class_destroy(dev_class); ++ unregister_chrdev(SPI_MAJOR_NUMBER, cc1101_driver->driver.name); + } +\ No newline at end of file +diff --git a/cc1101_chrdev.h b/cc1101_chrdev.h +index be190d2..9360142 100644 +--- a/cc1101_chrdev.h ++++ b/cc1101_chrdev.h +@@ -1,15 +1,15 @@ +-// SPDX-License-Identifier: GPL-2.0-only +-/* +-* Copyright (c) 2021 +-*/ +-#ifndef CC1101_CHRDEV_H +-#define CC1101_CHRDEV_H +- +-#include +- +-int cc1101_chrdev_setup(struct spi_driver *cc1101_driver); +-void cc1101_chrdev_teardown(struct spi_driver *cc1101_driver); +-int cc1101_chrdev_add_device(cc1101_t *cc1101); +-void cc1101_chrdev_remove_device(cc1101_t *cc1101); +- ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++* Copyright (c) 2021 ++*/ ++#ifndef CC1101_CHRDEV_H ++#define CC1101_CHRDEV_H ++ ++#include ++ ++int cc1101_chrdev_setup(struct spi_driver *cc1101_driver); ++void cc1101_chrdev_teardown(struct spi_driver *cc1101_driver); ++int cc1101_chrdev_add_device(cc1101_t *cc1101); ++void cc1101_chrdev_remove_device(cc1101_t *cc1101); ++ + #endif +\ No newline at end of file +diff --git a/cc1101_config.c b/cc1101_config.c +index b8eb49b..e4c2701 100644 +--- a/cc1101_config.c ++++ b/cc1101_config.c +@@ -1,573 +1,573 @@ +-// SPDX-License-Identifier: GPL-2.0-only +-/* +-* Copyright (c) 2021 +-*/ +-#include "cc1101_config.h" +- +-#include "cc1101_spi.h" +- +-extern uint max_packet_size; +- +-//0x@VH@, @<<@// @RN@@<<@ @Rd@ +-unsigned char DEFAULT_CONFIG[] = { +- 0x29, // IOCFG2 GDO2 Output Pin Configuration +- 0x2E, // IOCFG1 GDO1 Output Pin Configuration +- 0x3F, // IOCFG0 GDO0 Output Pin Configuration +- 0x47, // FIFOTHR RX FIFO and TX FIFO Thresholds +- 0xD3, // SYNC1 Sync Word, High Byte +- 0x91, // SYNC0 Sync Word, Low Byte +- 0xFF, // PKTLEN Packet Length +- 0x04, // PKTCTRL1 Packet Automation Control +- 0x45, // PKTCTRL0 Packet Automation Control +- 0x00, // ADDR Device Address +- 0x00, // CHANNR Channel Number +- 0x0F, // FSCTRL1 Frequency Synthesizer Control +- 0x00, // FSCTRL0 Frequency Synthesizer Control +- 0x1E, // FREQ2 Frequency Control Word, High Byte +- 0xC4, // FREQ1 Frequency Control Word, Middle Byte +- 0xEC, // FREQ0 Frequency Control Word, Low Byte +- 0x8C, // MDMCFG4 Modem Configuration +- 0x22, // MDMCFG3 Modem Configuration +- 0x02, // MDMCFG2 Modem Configuration +- 0x22, // MDMCFG1 Modem Configuration +- 0xF8, // MDMCFG0 Modem Configuration +- 0x47, // DEVIATN Modem Deviation Setting +- 0x07, // MCSM2 Main Radio Control State Machine Configuration +- 0x30, // MCSM1 Main Radio Control State Machine Configuration +- 0x04, // MCSM0 Main Radio Control State Machine Configuration +- 0x36, // FOCCFG Frequency Offset Compensation Configuration +- 0x6C, // BSCFG Bit Synchronization Configuration +- 0x03, // AGCCTRL2 AGC Control +- 0x40, // AGCCTRL1 AGC Control +- 0x91, // AGCCTRL0 AGC Control +- 0x87, // WOREVT1 High Byte Event0 Timeout +- 0x6B, // WOREVT0 Low Byte Event0 Timeout +- 0xF8, // WORCTRL Wake On Radio Control +- 0x56, // FREND1 Front End RX Configuration +- 0x10, // FREND0 Front End TX Configuration +- 0xEA, // FSCAL3 Frequency Synthesizer Calibration +- 0x2A, // FSCAL2 Frequency Synthesizer Calibration +- 0x00, // FSCAL1 Frequency Synthesizer Calibration +- 0x1F, // FSCAL0 Frequency Synthesizer Calibration +- 0x41, // RCCTRL1 RC Oscillator Configuration +- 0x00, // RCCTRL0 RC Oscillator Configuration +- 0x59, // FSTEST Frequency Synthesizer Calibration Control +- 0x7F, // PTEST Production Test +- 0x3F, // AGCTEST AGC Test +- 0x81, // TEST2 Various Test Settings +- 0x35, // TEST1 Various Test Settings +- 0x0B, // TEST0 Various Test Settings +-}; +- +-/* +-* Checks if a baud rate is valid for a given modulation +-* +-* Arguments: +-* baud_rate - baud rate value (e, m) +-* modulation - modulation mode +-* +-* Returns: +-* 1 - Valid +-* 0 - Invalid +-* +-* Min/Max Values (Page 8 - Table 3) +-* +-* Min: +-* e m +-* 0.6 - 0x04 0x84 -> 0.601292 +-* 26 - 0x0a 0x07 -> 26.0849 +-* +-* Max: +-* e m +-* 250 - 0x0d 0x3b -> 249.938965 +-* 300 - 0x0d 0x7a -> 299.926758 +-* 500 - 0x0e 0x3b -> 499.87793 +-*/ +- +-#define BAUD_RATE_0_6 0x0484 +-#define BAUD_RATE_26 0x0a07 +-#define BAUD_RATE_250 0x0d3b +-#define BAUD_RATE_300 0x0d7a +-#define BAUD_RATE_500 0x0e3b +- +-int validate_baud_rate(unsigned short baud_rate, unsigned char modulation) { +- unsigned short min = 0; +- unsigned short max = 0; +- +- switch(modulation) { +- case MOD_2FSK: +- min = BAUD_RATE_0_6; +- max = BAUD_RATE_500; +- break; +- case MOD_GFSK: +- case MOD_OOK: +- min = BAUD_RATE_0_6; +- max = BAUD_RATE_250; +- break; +- case MOD_4FSK: +- min = BAUD_RATE_0_6; +- max = BAUD_RATE_300; +- break; +- case MOD_MSK: +- min = BAUD_RATE_26; +- max = BAUD_RATE_500; +- break; +- } +- +- if(baud_rate < min || baud_rate > max){ +- return 0; +- } +- else { +- return 1; +- } +-} +- +-/* +-* Validates a common config struct +-* +-* Arguments: +-* cc1101: cc1101 device (only used for error printing) +-* config: a Common config struct +-* +-* Returns: +-* 1 - Valid Config +-* 0 - Invalid config +-*/ +-int cc1101_config_validate_common(cc1101_t *cc1101, const struct cc1101_common_config *config) { +- +- unsigned short baud_rate = config->baud_rate_exponent << 8 | config->baud_rate_mantissa; +- +- /* Frequency -> Multiplier Formula: +- * +- * multiplier = (freq * 2**16) / XTAL_FREQUENCY +- * +- * e.g +- * 756184 = (300 * 2**16) / 26 +- * +- * Valid ranges - 300-348 MHz, 387-464 MHz, 779-928 MHz +- * +- * 299.999756 -> 756184 +- * 347.999939 -> 877174 +- * 386.999939 -> 975478 +- * 463.999786 -> 1169564 +- * 778.999878 -> 1963559 +- * 928.000000 -> 2339131 +- * +- */ +- +- if (!( +- (config->frequency >= 756184 && config->frequency <= 877174) || +- (config->frequency >= 975478 && config->frequency <= 1169564) || +- (config->frequency >= 1963559 && config->frequency <= 2339131) +- )){ +- +- CC1101_ERROR(cc1101, "Invalid Frequency - %X", config->frequency); +- return 0; +- } +- +- if(validate_baud_rate(baud_rate, config->modulation) == 0){ +- CC1101_ERROR(cc1101, "Invalid Baud Rate - E:%02x M:%02x", config->baud_rate_exponent, config->baud_rate_mantissa); +- return 0; +- } +- +- // Sync word is allowed to be any 16 bit value, or the same 16-bit value twice +- if(config->sync_word > 0xFFFF) { +- if((config->sync_word & 0xFFFF) != (config->sync_word >> 16)) { +- CC1101_ERROR(cc1101, "Invalid Sync Word - %08x", config->sync_word); +- return 0; +- } +- } +- +- if(config->deviation_exponent > 0x07 || config->deviation_mantissa > 0x07){ +- CC1101_ERROR(cc1101, "Invalid Deviation - E: %02x M: %02x", config->deviation_exponent, config->deviation_mantissa); +- return 0; +- } +- +- return 1; +-} +- +-#ifndef RXONLY +-/* +-* Validates a TX config struct +-* +-* Arguments: +-* cc1101: cc1101 device (only used for error printing) +-* tx_config: a TX config struct +-* +-* Returns: +-* 1 - Valid Config +-* 0 - Invalid config +-*/ +-int cc1101_config_validate_tx(cc1101_t *cc1101, const struct cc1101_tx_config *tx_config) { +- +- // Validate the common configuration +- if (!cc1101_config_validate_common(cc1101, (struct cc1101_common_config *)tx_config)) { +- return 0; +- } +- +- // No additional validation for the TX config. Any byte is valid for tx_power +- +- return 1; +-} +- +-/* +-* Converts a TX config to a set of CC1101 configuration registers that can be written to the device +-* +-* Arguments: +-* config: pointer to a byte array which will contain the configuration register values +-* tx_config: a TX config struct +-* +-*/ +-void cc1101_config_tx_to_registers(unsigned char *config, const struct cc1101_tx_config *tx_config) { +- // Copy the default config +- memcpy(config, &DEFAULT_CONFIG, sizeof(cc1101_device_config_t)); +- +- // Standard Configuration +- config[IOCFG0] = 0x2E; // Disable GDO0 clock output +- config[PKTCTRL1] = 0x00; // Disable Preamble Quality Threshold, CRC Autoflush, Status, Address Check +- config[PKTCTRL0] = 0x00; // Disable Whitening, CRC. Set fixed packet length mode +- config[MDMCFG1] = 0x23; // 2 Preamble bytes. FEC Disabled. Default channel spacing (199.951172 kHZ) +- config[MCSM0] = 0x14; // Autocal when going from IDLE to RX or TX. PO_TIMEOUT 2.3-2.4us. Disable radio pin control, XOSC_FORCE +- config[TEST0] = 0x09; // Disable VCO Selection calibration +- config[FSCAL3] = 0xEA; // Smart RF Studio value +- config[FSCAL2] = 0x2A; // Smart RF Studio value +- config[FSCAL1] = 0x00; // Smart RF Studio value +- config[FSCAL0] = 0x1F; // Smart RF Studio value +- +- // User Configuration +- +- // Shift frequency multiplier across registers +- config[FREQ2] = tx_config->common.frequency >> 16; +- config[FREQ1] = tx_config->common.frequency >> 8; +- config[FREQ0] = tx_config->common.frequency & 0xFF; +- +- // Set baud rate exponent. RX bandwidth exponent and mantissa set to 0 (not used in TX) +- config[MDMCFG4] = tx_config->common.baud_rate_exponent; +- config[MDMCFG3] = tx_config->common.baud_rate_mantissa; +- config[MDMCFG2] = cc1101_get_mdmcfg2(&tx_config->common, NULL); +- +- config[SYNC1] = (tx_config->common.sync_word & 0xFF00) >> 8; +- config[SYNC0] = tx_config->common.sync_word & 0xFF; +- +- config[DEVIATN] = tx_config->common.deviation_exponent << 4 | tx_config->common.deviation_mantissa; +- +- if(tx_config->common.modulation == MOD_OOK) { +- config[FREND0] = 0x11; // PATABLE position 1 is used for OOK on, position 0 for off (default 0) +- } +- else { +- config[FREND0] = 0x10; // PATABLE position 0 for all other modulations (i.e disable power ramping) +- } +-} +- +-/* +-* Apply a stored TX config to the hardware +-* +-* Arguments: +-* cc1101: cc1101 device +-* +-*/ +-void cc1101_config_apply_tx(cc1101_t *cc1101) { +- cc1101_device_config_t device_config; +- cc1101_patable_t patable = {0}; +- +- // Convert the configuration to a set of register values +- cc1101_config_tx_to_registers(device_config, &cc1101->tx_config); +- +- // Set the PATABLE value +- if(cc1101->tx_config.common.modulation == MOD_OOK) { +- // OOK uses PATABLE[0] for off power and PATABLE[1] for on power +- patable[1] = cc1101->tx_config.tx_power; +- } +- else { +- patable[0] = cc1101->tx_config.tx_power; +- } +- +- // Write the registers and PATABLE to the device +- cc1101_spi_write_config_registers(cc1101, device_config, sizeof(cc1101_device_config_t)); +- cc1101_spi_write_patable(cc1101, patable, sizeof(patable)); +-} +-#endif +- +-/* +-* Validates an RX config struct +-* +-* Arguments: +-* cc1101: cc1101 device (only used for error printing) +-* rx_config: an RX config struct +-* +-* Returns: +-* 1 - Valid Config +-* 0 - Invalid config +-*/ +-int cc1101_config_validate_rx(cc1101_t *cc1101, const struct cc1101_rx_config *rx_config) { +- +- // Validate the common configuration +- if (!cc1101_config_validate_common(cc1101, (struct cc1101_common_config *)rx_config)) { +- return 0; +- } +- +- switch(rx_config->max_lna_gain){ +- case 0: +- case 3: +- case 6: +- case 7: +- case 9: +- case 12: +- case 15: +- case 17: +- break; +- default: +- CC1101_ERROR(cc1101, "Invalid Max LNA Gain %d dB", rx_config->max_lna_gain); +- return 0; +- } +- +- switch(rx_config->max_dvga_gain){ +- case 0: +- case 6: +- case 12: +- case 18: +- break; +- default: +- CC1101_ERROR(cc1101, "Invalid Max DVGA Gain %d dB", rx_config->max_dvga_gain); +- return 0; +- } +- +- switch(rx_config->magn_target){ +- case 24: +- case 27: +- case 30: +- case 33: +- case 36: +- case 38: +- case 40: +- case 42: +- break; +- default: +- CC1101_ERROR(cc1101, "Invalid Channel Filter Target Amplitude %d dB", rx_config->magn_target); +- return 0; +- } +- +- if(rx_config->carrier_sense_mode == CS_DISABLED){ +- // Do nothing +- } +- else if(rx_config->carrier_sense_mode == CS_ABSOLUTE){ +- // Absolute carrier sense threshold must be between -7 dB and 7 dB +- if(rx_config->carrier_sense < -7 || rx_config->carrier_sense > 7){ +- CC1101_ERROR(cc1101, "Invalid Absolute Carrier Sense Threshold %d dB", rx_config->carrier_sense); +- return 0; +- } +- } +- else if (rx_config->carrier_sense_mode == CS_RELATIVE){ +- // Relative carrier sense threshold must be either 6, 10 or 14 dB +- switch(rx_config->carrier_sense) { +- case 6: +- case 10: +- case 14: +- break; +- default: +- CC1101_ERROR(cc1101, "Invalid Relative Carrier Sense Threshold %d dB", rx_config->carrier_sense); +- return 0; +- } +- } +- else { +- CC1101_ERROR(cc1101, "Invalid Carrier Sense Mode %d", rx_config->carrier_sense_mode); +- return 0; +- } +- +- +- // Validate the packet length value provided from userspace +- if(rx_config->packet_length == 0 || rx_config->packet_length > max_packet_size) { +- CC1101_ERROR(cc1101, "Invalid Receive Packet Length %d", rx_config->packet_length); +- return 0; +- } +- +- // Validate Bandwidth +- if(rx_config->bandwidth_exponent > 3 || rx_config->bandwidth_mantissa > 3){ +- CC1101_ERROR(cc1101, "Invalid Deviation - E: %02x M: %02x", rx_config->bandwidth_exponent, rx_config->bandwidth_mantissa); +- return 0; +- } +- +- return 1; +-} +- +-/* +-* Converts an RX config to a set of CC1101 configuration registers that can be written to the device +-* +-* Arguments: +-* config: pointer to a byte array which will contain the configuration register values +-* rx_config: an RX config struct +-* +-*/ +-void cc1101_config_rx_to_registers(unsigned char *config, const struct cc1101_rx_config *rx_config) { +- // Copy the default config +- memcpy(config, &DEFAULT_CONFIG, sizeof(cc1101_device_config_t)); +- +- // Standard Configuration +- config[IOCFG2] = 0x00; // Raise GDO2 on RX FIFO threshold +- config[IOCFG0] = 0x2E; // Disable GDO0 clock output +- config[PKTCTRL1] = 0x00; // Disable Preamble Quality Threshold, CRC Autoflush, Status, Address Check +- config[PKTCTRL0] = 0x02; // Disable Whitening, CRC. Set infinite packet length mode +- config[MDMCFG1] = 0x23; // 2 Preamble bytes. FEC Disabled. Default channel spacing (199.951172 kHZ) +- config[MCSM0] = 0x14; // Autocal when going from IDLE to RX or TX. PO_TIMEOUT 2.3-2.4us. Disable radio pin control, XOSC_FORCE +- config[TEST0] = 0x09; // Disable VCO Selection calibration +- config[FSCAL3] = 0xEA; // Smart RF Studio value +- config[FSCAL2] = 0x2A; // Smart RF Studio value +- config[FSCAL1] = 0x00; // Smart RF Studio value +- config[FSCAL0] = 0x1F; // Smart RF Studio value +- +- // User Configuration +- config[FREQ2] = rx_config->common.frequency >> 16; +- config[FREQ1] = rx_config->common.frequency >> 8; +- config[FREQ0] = rx_config->common.frequency & 0xFF; +- +- config[MDMCFG4] = rx_config->bandwidth_exponent << 6 | rx_config->bandwidth_mantissa << 4 | rx_config->common.baud_rate_exponent; +- config[MDMCFG3] = rx_config->common.baud_rate_mantissa; +- config[MDMCFG2] = cc1101_get_mdmcfg2(&rx_config->common, rx_config); +- +- config[SYNC1] = (rx_config->common.sync_word & 0xFF00) >> 8; +- config[SYNC0] = rx_config->common.sync_word & 0xFF; +- +- config[DEVIATN] = rx_config->common.deviation_exponent << 4 | rx_config->common.deviation_mantissa; +- +- // Set the MAGN_TARGET from the config +- switch(rx_config->magn_target){ +- case 27: +- config[AGCCTRL2] = 1; +- break; +- case 30: +- config[AGCCTRL2] = 2; +- break; +- case 33: +- config[AGCCTRL2] = 3; +- break; +- case 36: +- config[AGCCTRL2] = 4; +- break; +- case 38: +- config[AGCCTRL2] = 5; +- break; +- case 40: +- config[AGCCTRL2] = 6; +- break; +- case 42: +- config[AGCCTRL2] = 7; +- break; +- default: +- config[AGCCTRL2] = 0; +- } +- +- // Set the MAX_DVGA_GAIN from the config +- switch(rx_config->max_dvga_gain){ +- case 6: +- config[AGCCTRL2] |= (1 << 6); +- break; +- case 12: +- config[AGCCTRL2] |= (2 << 6); +- break; +- case 18: +- config[AGCCTRL2] |= (3 << 6); +- break; +- default: +- break; +- } +- +- // Set the MAX_LNA_GAIN from the config +- switch(rx_config->max_lna_gain){ +- case 3: +- config[AGCCTRL2] |= (1 << 3); +- break; +- case 6: +- config[AGCCTRL2] |= (2 << 3); +- break; +- case 7: +- config[AGCCTRL2] |= (3 << 3); +- break; +- case 9: +- config[AGCCTRL2] |= (4 << 3); +- break; +- case 12: +- config[AGCCTRL2] |= (5 << 3); +- break; +- case 15: +- config[AGCCTRL2] |= (6 << 3); +- break; +- case 17: +- config[AGCCTRL2] |= (7 << 3); +- break; +- default: +- break; +- } +- +- // Set the CARRIER_SENSE_REL_THR and CARRIER_SENSE_ABS_THR based on the config value +- // Set AGC_LNA_PRIORITY to the default value +- if(rx_config->carrier_sense_mode == CS_ABSOLUTE){ +- config[AGCCTRL1] = 0x40 | ((char) rx_config->carrier_sense & 0x0F); +- } +- else if(rx_config->carrier_sense_mode == CS_RELATIVE){ +- switch(rx_config->carrier_sense) { +- case 6: +- config[AGCCTRL1] = 0x58; // Default AGC_LNA_PRIORITY. CARRIER_SENSE_REL_THR 6dB increase in RSSI. CS absolute threshold disabled +- break; +- case 10: +- config[AGCCTRL1] = 0x68; // Default AGC_LNA_PRIORITY. CARRIER_SENSE_REL_THR 10dB increase in RSSI. CS absolute threshold disabled +- break; +- default: +- config[AGCCTRL1] = 0x78; // Default AGC_LNA_PRIORITY. CARRIER_SENSE_REL_THR 14dB increase in RSSI. CS absolute threshold disabled +- break; +- } +- } +-} +- +-/* +-* Apply a stored RX config to the hardware +-* +-* Arguments: +-* cc1101: cc1101 device +-* +-*/ +-void cc1101_config_apply_rx(cc1101_t *cc1101) { +- cc1101_device_config_t device_config; +- +- // Convert the configuration to a set of register values +- cc1101_config_rx_to_registers(device_config, &cc1101->rx_config); +- +- // Write the registers to the device +- cc1101_spi_write_config_registers(cc1101, device_config, sizeof(cc1101_device_config_t)); +-} +- +-/* +-* Get the value for the MDMCFG2 register from common config. This value is needed by the RX interrupt handler +-* +-* Arguments: +-* config: common config for TX or RX +-* rx_config: an RX configuration. Can be NULL for TX +-*/ +-unsigned char cc1101_get_mdmcfg2(const struct cc1101_common_config *config, const struct cc1101_rx_config *rx_config) { +- +- unsigned char value = (config->modulation << 4) & 0x70; // Enable DC Filter. Modulation from config. +- +- if(rx_config == NULL || rx_config->carrier_sense_mode == CS_DISABLED) { +- if(config->sync_word > 0 && config->sync_word <= 0xFFFF) { +- value = value | 0x02; //Manchester encoding disabled. 16 sync bits, carrier sense disabled +- } +- else if(config->sync_word > 0xFFFF) { +- value = value | 0x03; //Manchester encoding disabled. 32 sync bits, carrier sense disabled +- } +- } +- else { +- if(config->sync_word == 0){ +- value = value | 0x04; //Manchester encoding disabled. no sync word, carrier sense enabled +- } +- else if(config->sync_word > 0 && config->sync_word <= 0xFFFF) { +- value = value | 0x06; //Manchester encoding disabled. 16 sync bits, carrier sense enabled +- } +- else { +- value = value | 0x07; //Manchester encoding disabled. 32 sync bits, carrier sense enabled +- } +- } +- +- return value; +-} +- +- +- ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++* Copyright (c) 2021 ++*/ ++#include "cc1101_config.h" ++ ++#include "cc1101_spi.h" ++ ++extern uint max_packet_size; ++ ++//0x@VH@, @<<@// @RN@@<<@ @Rd@ ++unsigned char DEFAULT_CONFIG[] = { ++ 0x29, // IOCFG2 GDO2 Output Pin Configuration ++ 0x2E, // IOCFG1 GDO1 Output Pin Configuration ++ 0x3F, // IOCFG0 GDO0 Output Pin Configuration ++ 0x47, // FIFOTHR RX FIFO and TX FIFO Thresholds ++ 0xD3, // SYNC1 Sync Word, High Byte ++ 0x91, // SYNC0 Sync Word, Low Byte ++ 0xFF, // PKTLEN Packet Length ++ 0x04, // PKTCTRL1 Packet Automation Control ++ 0x45, // PKTCTRL0 Packet Automation Control ++ 0x00, // ADDR Device Address ++ 0x00, // CHANNR Channel Number ++ 0x0F, // FSCTRL1 Frequency Synthesizer Control ++ 0x00, // FSCTRL0 Frequency Synthesizer Control ++ 0x1E, // FREQ2 Frequency Control Word, High Byte ++ 0xC4, // FREQ1 Frequency Control Word, Middle Byte ++ 0xEC, // FREQ0 Frequency Control Word, Low Byte ++ 0x8C, // MDMCFG4 Modem Configuration ++ 0x22, // MDMCFG3 Modem Configuration ++ 0x02, // MDMCFG2 Modem Configuration ++ 0x22, // MDMCFG1 Modem Configuration ++ 0xF8, // MDMCFG0 Modem Configuration ++ 0x47, // DEVIATN Modem Deviation Setting ++ 0x07, // MCSM2 Main Radio Control State Machine Configuration ++ 0x30, // MCSM1 Main Radio Control State Machine Configuration ++ 0x04, // MCSM0 Main Radio Control State Machine Configuration ++ 0x36, // FOCCFG Frequency Offset Compensation Configuration ++ 0x6C, // BSCFG Bit Synchronization Configuration ++ 0x03, // AGCCTRL2 AGC Control ++ 0x40, // AGCCTRL1 AGC Control ++ 0x91, // AGCCTRL0 AGC Control ++ 0x87, // WOREVT1 High Byte Event0 Timeout ++ 0x6B, // WOREVT0 Low Byte Event0 Timeout ++ 0xF8, // WORCTRL Wake On Radio Control ++ 0x56, // FREND1 Front End RX Configuration ++ 0x10, // FREND0 Front End TX Configuration ++ 0xEA, // FSCAL3 Frequency Synthesizer Calibration ++ 0x2A, // FSCAL2 Frequency Synthesizer Calibration ++ 0x00, // FSCAL1 Frequency Synthesizer Calibration ++ 0x1F, // FSCAL0 Frequency Synthesizer Calibration ++ 0x41, // RCCTRL1 RC Oscillator Configuration ++ 0x00, // RCCTRL0 RC Oscillator Configuration ++ 0x59, // FSTEST Frequency Synthesizer Calibration Control ++ 0x7F, // PTEST Production Test ++ 0x3F, // AGCTEST AGC Test ++ 0x81, // TEST2 Various Test Settings ++ 0x35, // TEST1 Various Test Settings ++ 0x0B, // TEST0 Various Test Settings ++}; ++ ++/* ++* Checks if a baud rate is valid for a given modulation ++* ++* Arguments: ++* baud_rate - baud rate value (e, m) ++* modulation - modulation mode ++* ++* Returns: ++* 1 - Valid ++* 0 - Invalid ++* ++* Min/Max Values (Page 8 - Table 3) ++* ++* Min: ++* e m ++* 0.6 - 0x04 0x84 -> 0.601292 ++* 26 - 0x0a 0x07 -> 26.0849 ++* ++* Max: ++* e m ++* 250 - 0x0d 0x3b -> 249.938965 ++* 300 - 0x0d 0x7a -> 299.926758 ++* 500 - 0x0e 0x3b -> 499.87793 ++*/ ++ ++#define BAUD_RATE_0_6 0x0484 ++#define BAUD_RATE_26 0x0a07 ++#define BAUD_RATE_250 0x0d3b ++#define BAUD_RATE_300 0x0d7a ++#define BAUD_RATE_500 0x0e3b ++ ++int validate_baud_rate(unsigned short baud_rate, unsigned char modulation) { ++ unsigned short min = 0; ++ unsigned short max = 0; ++ ++ switch(modulation) { ++ case MOD_2FSK: ++ min = BAUD_RATE_0_6; ++ max = BAUD_RATE_500; ++ break; ++ case MOD_GFSK: ++ case MOD_OOK: ++ min = BAUD_RATE_0_6; ++ max = BAUD_RATE_250; ++ break; ++ case MOD_4FSK: ++ min = BAUD_RATE_0_6; ++ max = BAUD_RATE_300; ++ break; ++ case MOD_MSK: ++ min = BAUD_RATE_26; ++ max = BAUD_RATE_500; ++ break; ++ } ++ ++ if(baud_rate < min || baud_rate > max){ ++ return 0; ++ } ++ else { ++ return 1; ++ } ++} ++ ++/* ++* Validates a common config struct ++* ++* Arguments: ++* cc1101: cc1101 device (only used for error printing) ++* config: a Common config struct ++* ++* Returns: ++* 1 - Valid Config ++* 0 - Invalid config ++*/ ++int cc1101_config_validate_common(cc1101_t *cc1101, const struct cc1101_common_config *config) { ++ ++ unsigned short baud_rate = config->baud_rate_exponent << 8 | config->baud_rate_mantissa; ++ ++ /* Frequency -> Multiplier Formula: ++ * ++ * multiplier = (freq * 2**16) / XTAL_FREQUENCY ++ * ++ * e.g ++ * 756184 = (300 * 2**16) / 26 ++ * ++ * Valid ranges - 300-348 MHz, 387-464 MHz, 779-928 MHz ++ * ++ * 299.999756 -> 756184 ++ * 347.999939 -> 877174 ++ * 386.999939 -> 975478 ++ * 463.999786 -> 1169564 ++ * 778.999878 -> 1963559 ++ * 928.000000 -> 2339131 ++ * ++ */ ++ ++ if (!( ++ (config->frequency >= 756184 && config->frequency <= 877174) || ++ (config->frequency >= 975478 && config->frequency <= 1169564) || ++ (config->frequency >= 1963559 && config->frequency <= 2339131) ++ )){ ++ ++ CC1101_ERROR(cc1101, "Invalid Frequency - %X", config->frequency); ++ return 0; ++ } ++ ++ if(validate_baud_rate(baud_rate, config->modulation) == 0){ ++ CC1101_ERROR(cc1101, "Invalid Baud Rate - E:%02x M:%02x", config->baud_rate_exponent, config->baud_rate_mantissa); ++ return 0; ++ } ++ ++ // Sync word is allowed to be any 16 bit value, or the same 16-bit value twice ++ if(config->sync_word > 0xFFFF) { ++ if((config->sync_word & 0xFFFF) != (config->sync_word >> 16)) { ++ CC1101_ERROR(cc1101, "Invalid Sync Word - %08x", config->sync_word); ++ return 0; ++ } ++ } ++ ++ if(config->deviation_exponent > 0x07 || config->deviation_mantissa > 0x07){ ++ CC1101_ERROR(cc1101, "Invalid Deviation - E: %02x M: %02x", config->deviation_exponent, config->deviation_mantissa); ++ return 0; ++ } ++ ++ return 1; ++} ++ ++#ifndef RXONLY ++/* ++* Validates a TX config struct ++* ++* Arguments: ++* cc1101: cc1101 device (only used for error printing) ++* tx_config: a TX config struct ++* ++* Returns: ++* 1 - Valid Config ++* 0 - Invalid config ++*/ ++int cc1101_config_validate_tx(cc1101_t *cc1101, const struct cc1101_tx_config *tx_config) { ++ ++ // Validate the common configuration ++ if (!cc1101_config_validate_common(cc1101, (struct cc1101_common_config *)tx_config)) { ++ return 0; ++ } ++ ++ // No additional validation for the TX config. Any byte is valid for tx_power ++ ++ return 1; ++} ++ ++/* ++* Converts a TX config to a set of CC1101 configuration registers that can be written to the device ++* ++* Arguments: ++* config: pointer to a byte array which will contain the configuration register values ++* tx_config: a TX config struct ++* ++*/ ++void cc1101_config_tx_to_registers(unsigned char *config, const struct cc1101_tx_config *tx_config) { ++ // Copy the default config ++ memcpy(config, &DEFAULT_CONFIG, sizeof(cc1101_device_config_t)); ++ ++ // Standard Configuration ++ config[IOCFG0] = 0x2E; // Disable GDO0 clock output ++ config[PKTCTRL1] = 0x00; // Disable Preamble Quality Threshold, CRC Autoflush, Status, Address Check ++ config[PKTCTRL0] = 0x00; // Disable Whitening, CRC. Set fixed packet length mode ++ config[MDMCFG1] = 0x23; // 2 Preamble bytes. FEC Disabled. Default channel spacing (199.951172 kHZ) ++ config[MCSM0] = 0x14; // Autocal when going from IDLE to RX or TX. PO_TIMEOUT 2.3-2.4us. Disable radio pin control, XOSC_FORCE ++ config[TEST0] = 0x09; // Disable VCO Selection calibration ++ config[FSCAL3] = 0xEA; // Smart RF Studio value ++ config[FSCAL2] = 0x2A; // Smart RF Studio value ++ config[FSCAL1] = 0x00; // Smart RF Studio value ++ config[FSCAL0] = 0x1F; // Smart RF Studio value ++ ++ // User Configuration ++ ++ // Shift frequency multiplier across registers ++ config[FREQ2] = tx_config->common.frequency >> 16; ++ config[FREQ1] = tx_config->common.frequency >> 8; ++ config[FREQ0] = tx_config->common.frequency & 0xFF; ++ ++ // Set baud rate exponent. RX bandwidth exponent and mantissa set to 0 (not used in TX) ++ config[MDMCFG4] = tx_config->common.baud_rate_exponent; ++ config[MDMCFG3] = tx_config->common.baud_rate_mantissa; ++ config[MDMCFG2] = cc1101_get_mdmcfg2(&tx_config->common, NULL); ++ ++ config[SYNC1] = (tx_config->common.sync_word & 0xFF00) >> 8; ++ config[SYNC0] = tx_config->common.sync_word & 0xFF; ++ ++ config[DEVIATN] = tx_config->common.deviation_exponent << 4 | tx_config->common.deviation_mantissa; ++ ++ if(tx_config->common.modulation == MOD_OOK) { ++ config[FREND0] = 0x11; // PATABLE position 1 is used for OOK on, position 0 for off (default 0) ++ } ++ else { ++ config[FREND0] = 0x10; // PATABLE position 0 for all other modulations (i.e disable power ramping) ++ } ++} ++ ++/* ++* Apply a stored TX config to the hardware ++* ++* Arguments: ++* cc1101: cc1101 device ++* ++*/ ++void cc1101_config_apply_tx(cc1101_t *cc1101) { ++ cc1101_device_config_t device_config; ++ cc1101_patable_t patable = {0}; ++ ++ // Convert the configuration to a set of register values ++ cc1101_config_tx_to_registers(device_config, &cc1101->tx_config); ++ ++ // Set the PATABLE value ++ if(cc1101->tx_config.common.modulation == MOD_OOK) { ++ // OOK uses PATABLE[0] for off power and PATABLE[1] for on power ++ patable[1] = cc1101->tx_config.tx_power; ++ } ++ else { ++ patable[0] = cc1101->tx_config.tx_power; ++ } ++ ++ // Write the registers and PATABLE to the device ++ cc1101_spi_write_config_registers(cc1101, device_config, sizeof(cc1101_device_config_t)); ++ cc1101_spi_write_patable(cc1101, patable, sizeof(patable)); ++} ++#endif ++ ++/* ++* Validates an RX config struct ++* ++* Arguments: ++* cc1101: cc1101 device (only used for error printing) ++* rx_config: an RX config struct ++* ++* Returns: ++* 1 - Valid Config ++* 0 - Invalid config ++*/ ++int cc1101_config_validate_rx(cc1101_t *cc1101, const struct cc1101_rx_config *rx_config) { ++ ++ // Validate the common configuration ++ if (!cc1101_config_validate_common(cc1101, (struct cc1101_common_config *)rx_config)) { ++ return 0; ++ } ++ ++ switch(rx_config->max_lna_gain){ ++ case 0: ++ case 3: ++ case 6: ++ case 7: ++ case 9: ++ case 12: ++ case 15: ++ case 17: ++ break; ++ default: ++ CC1101_ERROR(cc1101, "Invalid Max LNA Gain %d dB", rx_config->max_lna_gain); ++ return 0; ++ } ++ ++ switch(rx_config->max_dvga_gain){ ++ case 0: ++ case 6: ++ case 12: ++ case 18: ++ break; ++ default: ++ CC1101_ERROR(cc1101, "Invalid Max DVGA Gain %d dB", rx_config->max_dvga_gain); ++ return 0; ++ } ++ ++ switch(rx_config->magn_target){ ++ case 24: ++ case 27: ++ case 30: ++ case 33: ++ case 36: ++ case 38: ++ case 40: ++ case 42: ++ break; ++ default: ++ CC1101_ERROR(cc1101, "Invalid Channel Filter Target Amplitude %d dB", rx_config->magn_target); ++ return 0; ++ } ++ ++ if(rx_config->carrier_sense_mode == CS_DISABLED){ ++ // Do nothing ++ } ++ else if(rx_config->carrier_sense_mode == CS_ABSOLUTE){ ++ // Absolute carrier sense threshold must be between -7 dB and 7 dB ++ if(rx_config->carrier_sense < -7 || rx_config->carrier_sense > 7){ ++ CC1101_ERROR(cc1101, "Invalid Absolute Carrier Sense Threshold %d dB", rx_config->carrier_sense); ++ return 0; ++ } ++ } ++ else if (rx_config->carrier_sense_mode == CS_RELATIVE){ ++ // Relative carrier sense threshold must be either 6, 10 or 14 dB ++ switch(rx_config->carrier_sense) { ++ case 6: ++ case 10: ++ case 14: ++ break; ++ default: ++ CC1101_ERROR(cc1101, "Invalid Relative Carrier Sense Threshold %d dB", rx_config->carrier_sense); ++ return 0; ++ } ++ } ++ else { ++ CC1101_ERROR(cc1101, "Invalid Carrier Sense Mode %d", rx_config->carrier_sense_mode); ++ return 0; ++ } ++ ++ ++ // Validate the packet length value provided from userspace ++ if(rx_config->packet_length == 0 || rx_config->packet_length > max_packet_size) { ++ CC1101_ERROR(cc1101, "Invalid Receive Packet Length %d", rx_config->packet_length); ++ return 0; ++ } ++ ++ // Validate Bandwidth ++ if(rx_config->bandwidth_exponent > 3 || rx_config->bandwidth_mantissa > 3){ ++ CC1101_ERROR(cc1101, "Invalid Deviation - E: %02x M: %02x", rx_config->bandwidth_exponent, rx_config->bandwidth_mantissa); ++ return 0; ++ } ++ ++ return 1; ++} ++ ++/* ++* Converts an RX config to a set of CC1101 configuration registers that can be written to the device ++* ++* Arguments: ++* config: pointer to a byte array which will contain the configuration register values ++* rx_config: an RX config struct ++* ++*/ ++void cc1101_config_rx_to_registers(unsigned char *config, const struct cc1101_rx_config *rx_config) { ++ // Copy the default config ++ memcpy(config, &DEFAULT_CONFIG, sizeof(cc1101_device_config_t)); ++ ++ // Standard Configuration ++ config[IOCFG2] = 0x00; // Raise GDO2 on RX FIFO threshold ++ config[IOCFG0] = 0x2E; // Disable GDO0 clock output ++ config[PKTCTRL1] = 0x00; // Disable Preamble Quality Threshold, CRC Autoflush, Status, Address Check ++ config[PKTCTRL0] = 0x02; // Disable Whitening, CRC. Set infinite packet length mode ++ config[MDMCFG1] = 0x23; // 2 Preamble bytes. FEC Disabled. Default channel spacing (199.951172 kHZ) ++ config[MCSM0] = 0x14; // Autocal when going from IDLE to RX or TX. PO_TIMEOUT 2.3-2.4us. Disable radio pin control, XOSC_FORCE ++ config[TEST0] = 0x09; // Disable VCO Selection calibration ++ config[FSCAL3] = 0xEA; // Smart RF Studio value ++ config[FSCAL2] = 0x2A; // Smart RF Studio value ++ config[FSCAL1] = 0x00; // Smart RF Studio value ++ config[FSCAL0] = 0x1F; // Smart RF Studio value ++ ++ // User Configuration ++ config[FREQ2] = rx_config->common.frequency >> 16; ++ config[FREQ1] = rx_config->common.frequency >> 8; ++ config[FREQ0] = rx_config->common.frequency & 0xFF; ++ ++ config[MDMCFG4] = rx_config->bandwidth_exponent << 6 | rx_config->bandwidth_mantissa << 4 | rx_config->common.baud_rate_exponent; ++ config[MDMCFG3] = rx_config->common.baud_rate_mantissa; ++ config[MDMCFG2] = cc1101_get_mdmcfg2(&rx_config->common, rx_config); ++ ++ config[SYNC1] = (rx_config->common.sync_word & 0xFF00) >> 8; ++ config[SYNC0] = rx_config->common.sync_word & 0xFF; ++ ++ config[DEVIATN] = rx_config->common.deviation_exponent << 4 | rx_config->common.deviation_mantissa; ++ ++ // Set the MAGN_TARGET from the config ++ switch(rx_config->magn_target){ ++ case 27: ++ config[AGCCTRL2] = 1; ++ break; ++ case 30: ++ config[AGCCTRL2] = 2; ++ break; ++ case 33: ++ config[AGCCTRL2] = 3; ++ break; ++ case 36: ++ config[AGCCTRL2] = 4; ++ break; ++ case 38: ++ config[AGCCTRL2] = 5; ++ break; ++ case 40: ++ config[AGCCTRL2] = 6; ++ break; ++ case 42: ++ config[AGCCTRL2] = 7; ++ break; ++ default: ++ config[AGCCTRL2] = 0; ++ } ++ ++ // Set the MAX_DVGA_GAIN from the config ++ switch(rx_config->max_dvga_gain){ ++ case 6: ++ config[AGCCTRL2] |= (1 << 6); ++ break; ++ case 12: ++ config[AGCCTRL2] |= (2 << 6); ++ break; ++ case 18: ++ config[AGCCTRL2] |= (3 << 6); ++ break; ++ default: ++ break; ++ } ++ ++ // Set the MAX_LNA_GAIN from the config ++ switch(rx_config->max_lna_gain){ ++ case 3: ++ config[AGCCTRL2] |= (1 << 3); ++ break; ++ case 6: ++ config[AGCCTRL2] |= (2 << 3); ++ break; ++ case 7: ++ config[AGCCTRL2] |= (3 << 3); ++ break; ++ case 9: ++ config[AGCCTRL2] |= (4 << 3); ++ break; ++ case 12: ++ config[AGCCTRL2] |= (5 << 3); ++ break; ++ case 15: ++ config[AGCCTRL2] |= (6 << 3); ++ break; ++ case 17: ++ config[AGCCTRL2] |= (7 << 3); ++ break; ++ default: ++ break; ++ } ++ ++ // Set the CARRIER_SENSE_REL_THR and CARRIER_SENSE_ABS_THR based on the config value ++ // Set AGC_LNA_PRIORITY to the default value ++ if(rx_config->carrier_sense_mode == CS_ABSOLUTE){ ++ config[AGCCTRL1] = 0x40 | ((char) rx_config->carrier_sense & 0x0F); ++ } ++ else if(rx_config->carrier_sense_mode == CS_RELATIVE){ ++ switch(rx_config->carrier_sense) { ++ case 6: ++ config[AGCCTRL1] = 0x58; // Default AGC_LNA_PRIORITY. CARRIER_SENSE_REL_THR 6dB increase in RSSI. CS absolute threshold disabled ++ break; ++ case 10: ++ config[AGCCTRL1] = 0x68; // Default AGC_LNA_PRIORITY. CARRIER_SENSE_REL_THR 10dB increase in RSSI. CS absolute threshold disabled ++ break; ++ default: ++ config[AGCCTRL1] = 0x78; // Default AGC_LNA_PRIORITY. CARRIER_SENSE_REL_THR 14dB increase in RSSI. CS absolute threshold disabled ++ break; ++ } ++ } ++} ++ ++/* ++* Apply a stored RX config to the hardware ++* ++* Arguments: ++* cc1101: cc1101 device ++* ++*/ ++void cc1101_config_apply_rx(cc1101_t *cc1101) { ++ cc1101_device_config_t device_config; ++ ++ // Convert the configuration to a set of register values ++ cc1101_config_rx_to_registers(device_config, &cc1101->rx_config); ++ ++ // Write the registers to the device ++ cc1101_spi_write_config_registers(cc1101, device_config, sizeof(cc1101_device_config_t)); ++} ++ ++/* ++* Get the value for the MDMCFG2 register from common config. This value is needed by the RX interrupt handler ++* ++* Arguments: ++* config: common config for TX or RX ++* rx_config: an RX configuration. Can be NULL for TX ++*/ ++unsigned char cc1101_get_mdmcfg2(const struct cc1101_common_config *config, const struct cc1101_rx_config *rx_config) { ++ ++ unsigned char value = (config->modulation << 4) & 0x70; // Enable DC Filter. Modulation from config. ++ ++ if(rx_config == NULL || rx_config->carrier_sense_mode == CS_DISABLED) { ++ if(config->sync_word > 0 && config->sync_word <= 0xFFFF) { ++ value = value | 0x02; //Manchester encoding disabled. 16 sync bits, carrier sense disabled ++ } ++ else if(config->sync_word > 0xFFFF) { ++ value = value | 0x03; //Manchester encoding disabled. 32 sync bits, carrier sense disabled ++ } ++ } ++ else { ++ if(config->sync_word == 0){ ++ value = value | 0x04; //Manchester encoding disabled. no sync word, carrier sense enabled ++ } ++ else if(config->sync_word > 0 && config->sync_word <= 0xFFFF) { ++ value = value | 0x06; //Manchester encoding disabled. 16 sync bits, carrier sense enabled ++ } ++ else { ++ value = value | 0x07; //Manchester encoding disabled. 32 sync bits, carrier sense enabled ++ } ++ } ++ ++ return value; ++} ++ ++ ++ +diff --git a/cc1101_config.h b/cc1101_config.h +index 6a3b558..8a076ba 100644 +--- a/cc1101_config.h ++++ b/cc1101_config.h +@@ -1,22 +1,22 @@ +-// SPDX-License-Identifier: GPL-2.0-only +-/* +-* Copyright (c) 2021 +-*/ +-#ifndef CC1101_UTILS_H +-#define CC1101_UTILS_H +- +-#include "cc1101_internal.h" +- +-unsigned char cc1101_get_mdmcfg2(const struct cc1101_common_config *common_config, const struct cc1101_rx_config *rx_config); +- +-int cc1101_config_validate_rx(cc1101_t *cc1101, const struct cc1101_rx_config *rx_config); +-void cc1101_config_apply_rx(cc1101_t *cc1101); +-void cc1101_config_rx_to_registers(unsigned char *config, const struct cc1101_rx_config *rx_config); +- +-#ifndef RXONLY +-int cc1101_config_validate_tx(cc1101_t *cc1101, const struct cc1101_tx_config *tx_config); +-void cc1101_config_apply_tx(cc1101_t *cc1101); +-void cc1101_config_tx_to_registers(unsigned char *config, const struct cc1101_tx_config *tx_config); +-#endif +- ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++* Copyright (c) 2021 ++*/ ++#ifndef CC1101_UTILS_H ++#define CC1101_UTILS_H ++ ++#include "cc1101_internal.h" ++ ++unsigned char cc1101_get_mdmcfg2(const struct cc1101_common_config *common_config, const struct cc1101_rx_config *rx_config); ++ ++int cc1101_config_validate_rx(cc1101_t *cc1101, const struct cc1101_rx_config *rx_config); ++void cc1101_config_apply_rx(cc1101_t *cc1101); ++void cc1101_config_rx_to_registers(unsigned char *config, const struct cc1101_rx_config *rx_config); ++ ++#ifndef RXONLY ++int cc1101_config_validate_tx(cc1101_t *cc1101, const struct cc1101_tx_config *tx_config); ++void cc1101_config_apply_tx(cc1101_t *cc1101); ++void cc1101_config_tx_to_registers(unsigned char *config, const struct cc1101_tx_config *tx_config); ++#endif ++ + #endif +\ No newline at end of file +diff --git a/cc1101_internal.h b/cc1101_internal.h +index ebaa64c..4d0a30f 100644 +--- a/cc1101_internal.h ++++ b/cc1101_internal.h +@@ -1,151 +1,151 @@ +-// SPDX-License-Identifier: GPL-2.0-only +-/* +-* Copyright (c) 2021 +-*/ +-#ifndef CC1101_INTERNAL_H +-#define CC1101_INTERNAL_H +- +-#include +-#include +- +-#include "cc1101.h" +- +-#define DRIVER_VERSION 4 +- +-// Configuration Registers - CC1101 Datasheet Table 43 +-// Generated in SmartRF Studio with config string #define @RN@ @<<@ 0x@AH@ @<<@ // @Rd@ +-#define IOCFG2 0x00 // GDO2 Output Pin Configuration +-#define IOCFG1 0x01 // GDO1 Output Pin Configuration +-#define IOCFG0 0x02 // GDO0 Output Pin Configuration +-#define FIFOTHR 0x03 // RX FIFO and TX FIFO Thresholds +-#define SYNC1 0x04 // Sync Word, High Byte +-#define SYNC0 0x05 // Sync Word, Low Byte +-#define PKTLEN 0x06 // Packet Length +-#define PKTCTRL1 0x07 // Packet Automation Control +-#define PKTCTRL0 0x08 // Packet Automation Control +-#define ADDR 0x09 // Device Address +-#define CHANNR 0x0A // Channel Number +-#define FSCTRL1 0x0B // Frequency Synthesizer Control +-#define FSCTRL0 0x0C // Frequency Synthesizer Control +-#define FREQ2 0x0D // Frequency Control Word, High Byte +-#define FREQ1 0x0E // Frequency Control Word, Middle Byte +-#define FREQ0 0x0F // Frequency Control Word, Low Byte +-#define MDMCFG4 0x10 // Modem Configuration +-#define MDMCFG3 0x11 // Modem Configuration +-#define MDMCFG2 0x12 // Modem Configuration +-#define MDMCFG1 0x13 // Modem Configuration +-#define MDMCFG0 0x14 // Modem Configuration +-#define DEVIATN 0x15 // Modem Deviation Setting +-#define MCSM2 0x16 // Main Radio Control State Machine Configuration +-#define MCSM1 0x17 // Main Radio Control State Machine Configuration +-#define MCSM0 0x18 // Main Radio Control State Machine Configuration +-#define FOCCFG 0x19 // Frequency Offset Compensation Configuration +-#define BSCFG 0x1A // Bit Synchronization Configuration +-#define AGCCTRL2 0x1B // AGC Control +-#define AGCCTRL1 0x1C // AGC Control +-#define AGCCTRL0 0x1D // AGC Control +-#define WOREVT1 0x1E // High Byte Event0 Timeout +-#define WOREVT0 0x1F // Low Byte Event0 Timeout +-#define WORCTRL 0x20 // Wake On Radio Control +-#define FREND1 0x21 // Front End RX Configuration +-#define FREND0 0x22 // Front End TX Configuration +-#define FSCAL3 0x23 // Frequency Synthesizer Calibration +-#define FSCAL2 0x24 // Frequency Synthesizer Calibration +-#define FSCAL1 0x25 // Frequency Synthesizer Calibration +-#define FSCAL0 0x26 // Frequency Synthesizer Calibration +-#define RCCTRL1 0x27 // RC Oscillator Configuration +-#define RCCTRL0 0x28 // RC Oscillator Configuration +-#define FSTEST 0x29 // Frequency Synthesizer Calibration Control +-#define PTEST 0x2A // Production Test +-#define AGCTEST 0x2B // AGC Test +-#define TEST2 0x2C // Various Test Settings +-#define TEST1 0x2D // Various Test Settings +-#define TEST0 0x2E // Various Test Settings +- +-// Command strobes - CC1101 Datasheet Table 42 +-#define SRES 0x30 +-#define SFSTXON 0x31 +-#define SXOFF 0x32 +-#define SCAL 0x33 +-#define SRX 0x34 +-#define STX 0x35 +-#define SIDLE 0x36 +-#define SAFC 0x37 +-#define SWOR 0x38 +-#define SPWD 0x39 +-#define SFRX 0x3A +-#define SFTX 0x3B +-#define SWORRST 0x3C +-#define SNOP 0x3D +- +-// Status Registers - CC1101 Datasheet Table 44 +-#define PARTNUM 0x30 +-#define VERSION 0x31 +-#define FREQEST 0x32 +-#define LQI 0x33 +-#define RSSI 0x34 +-#define MARCSTATE 0x35 +-#define WORTIME1 0x36 +-#define WORTIME0 0x37 +-#define PKTSTATUS 0x38 +-#define VCO_VC_DAC 0x39 +-#define TXBYTES 0x3A +-#define RXBYTES 0x3B +-#define RCCTRL1_STATUS 0x3C +-#define RCCTRL0_STATUS 0x3D +- +-#define PATABLE 0x3E +-#define FIFO 0x3F +- +-#define FIFO_LEN 64 +- +-// State transition times - CC1101 Datasheet Page 54/55 +-#define TIME_IDLE_TO_RX_NOCAL 76 // Rounded up from 75.1 +-#define TIME_IDLE_TO_RX_CAL 799 +-#define TIME_IDLE_TO_TX_NOCAL 76 // Rounded up from 75.2 +-#define TIME_IDLE_TO_TX_CAL 799 +-#define TIME_TX_TO_RX 32 // Rounded up from 31.1 - compensates for variable baud rate +-#define TIME_RX_TO_TX 32 +-#define TIME_TX_TO_IDLE_NOCAL 1 // Rounded up from 0.25/baud - compensates for variable baud rate +-#define TIME_TX_TO_IDLE_CAL 726 // Rounded up from 725 to compensate for variable baud rate +-#define TIME_RX_TO_IDLE_NOCAL 1 // Rounded up from ~0.1 +-#define TIME_RX_TO_IDLE_CAL 724 +-#define TIME_MANUAL_CAL 735 +- +-// Error macros +-#ifndef CONFIG_DYNAMIC_DEBUG +-extern uint debug; +-#define CC1101_DEBUG(cc1101, format, ...) if (debug) dev_info(&cc1101->spi->dev, format "\n", ##__VA_ARGS__) +-#else +-#define CC1101_DEBUG(cc1101, format, ...) dev_dbg(&cc1101->spi->dev, format "\n", ##__VA_ARGS__) +-#endif +- +-#define CC1101_INFO(cc1101, format, ...) dev_info(&cc1101->spi->dev, format "\n", ##__VA_ARGS__) +-#define CC1101_ERROR(cc1101, format, ...) dev_err(&cc1101->spi->dev, format "\n", ##__VA_ARGS__) +- +-// Driver state +-typedef enum { +- MODE_IDLE, +- MODE_TX, +- MODE_RX +-} cc1101_mode_t; +- +- +-// Struct to hold per-device state +-typedef struct { +- struct spi_device *spi; +- dev_t devt; +- int irq; +- struct mutex chrdev_lock; +- struct mutex device_lock; +- cc1101_mode_t mode; +- struct cc1101_tx_config tx_config; +- struct cc1101_rx_config rx_config; +- unsigned char *current_packet; +- u32 bytes_remaining; +- DECLARE_KFIFO_PTR(rx_fifo, unsigned char); +- struct timer_list rx_timeout; +- struct work_struct rx_timeout_work; +-} cc1101_t; +- ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++* Copyright (c) 2021 ++*/ ++#ifndef CC1101_INTERNAL_H ++#define CC1101_INTERNAL_H ++ ++#include ++#include ++ ++#include "cc1101.h" ++ ++#define DRIVER_VERSION 4 ++ ++// Configuration Registers - CC1101 Datasheet Table 43 ++// Generated in SmartRF Studio with config string #define @RN@ @<<@ 0x@AH@ @<<@ // @Rd@ ++#define IOCFG2 0x00 // GDO2 Output Pin Configuration ++#define IOCFG1 0x01 // GDO1 Output Pin Configuration ++#define IOCFG0 0x02 // GDO0 Output Pin Configuration ++#define FIFOTHR 0x03 // RX FIFO and TX FIFO Thresholds ++#define SYNC1 0x04 // Sync Word, High Byte ++#define SYNC0 0x05 // Sync Word, Low Byte ++#define PKTLEN 0x06 // Packet Length ++#define PKTCTRL1 0x07 // Packet Automation Control ++#define PKTCTRL0 0x08 // Packet Automation Control ++#define ADDR 0x09 // Device Address ++#define CHANNR 0x0A // Channel Number ++#define FSCTRL1 0x0B // Frequency Synthesizer Control ++#define FSCTRL0 0x0C // Frequency Synthesizer Control ++#define FREQ2 0x0D // Frequency Control Word, High Byte ++#define FREQ1 0x0E // Frequency Control Word, Middle Byte ++#define FREQ0 0x0F // Frequency Control Word, Low Byte ++#define MDMCFG4 0x10 // Modem Configuration ++#define MDMCFG3 0x11 // Modem Configuration ++#define MDMCFG2 0x12 // Modem Configuration ++#define MDMCFG1 0x13 // Modem Configuration ++#define MDMCFG0 0x14 // Modem Configuration ++#define DEVIATN 0x15 // Modem Deviation Setting ++#define MCSM2 0x16 // Main Radio Control State Machine Configuration ++#define MCSM1 0x17 // Main Radio Control State Machine Configuration ++#define MCSM0 0x18 // Main Radio Control State Machine Configuration ++#define FOCCFG 0x19 // Frequency Offset Compensation Configuration ++#define BSCFG 0x1A // Bit Synchronization Configuration ++#define AGCCTRL2 0x1B // AGC Control ++#define AGCCTRL1 0x1C // AGC Control ++#define AGCCTRL0 0x1D // AGC Control ++#define WOREVT1 0x1E // High Byte Event0 Timeout ++#define WOREVT0 0x1F // Low Byte Event0 Timeout ++#define WORCTRL 0x20 // Wake On Radio Control ++#define FREND1 0x21 // Front End RX Configuration ++#define FREND0 0x22 // Front End TX Configuration ++#define FSCAL3 0x23 // Frequency Synthesizer Calibration ++#define FSCAL2 0x24 // Frequency Synthesizer Calibration ++#define FSCAL1 0x25 // Frequency Synthesizer Calibration ++#define FSCAL0 0x26 // Frequency Synthesizer Calibration ++#define RCCTRL1 0x27 // RC Oscillator Configuration ++#define RCCTRL0 0x28 // RC Oscillator Configuration ++#define FSTEST 0x29 // Frequency Synthesizer Calibration Control ++#define PTEST 0x2A // Production Test ++#define AGCTEST 0x2B // AGC Test ++#define TEST2 0x2C // Various Test Settings ++#define TEST1 0x2D // Various Test Settings ++#define TEST0 0x2E // Various Test Settings ++ ++// Command strobes - CC1101 Datasheet Table 42 ++#define SRES 0x30 ++#define SFSTXON 0x31 ++#define SXOFF 0x32 ++#define SCAL 0x33 ++#define SRX 0x34 ++#define STX 0x35 ++#define SIDLE 0x36 ++#define SAFC 0x37 ++#define SWOR 0x38 ++#define SPWD 0x39 ++#define SFRX 0x3A ++#define SFTX 0x3B ++#define SWORRST 0x3C ++#define SNOP 0x3D ++ ++// Status Registers - CC1101 Datasheet Table 44 ++#define PARTNUM 0x30 ++#define VERSION 0x31 ++#define FREQEST 0x32 ++#define LQI 0x33 ++#define RSSI 0x34 ++#define MARCSTATE 0x35 ++#define WORTIME1 0x36 ++#define WORTIME0 0x37 ++#define PKTSTATUS 0x38 ++#define VCO_VC_DAC 0x39 ++#define TXBYTES 0x3A ++#define RXBYTES 0x3B ++#define RCCTRL1_STATUS 0x3C ++#define RCCTRL0_STATUS 0x3D ++ ++#define PATABLE 0x3E ++#define FIFO 0x3F ++ ++#define FIFO_LEN 64 ++ ++// State transition times - CC1101 Datasheet Page 54/55 ++#define TIME_IDLE_TO_RX_NOCAL 76 // Rounded up from 75.1 ++#define TIME_IDLE_TO_RX_CAL 799 ++#define TIME_IDLE_TO_TX_NOCAL 76 // Rounded up from 75.2 ++#define TIME_IDLE_TO_TX_CAL 799 ++#define TIME_TX_TO_RX 32 // Rounded up from 31.1 - compensates for variable baud rate ++#define TIME_RX_TO_TX 32 ++#define TIME_TX_TO_IDLE_NOCAL 1 // Rounded up from 0.25/baud - compensates for variable baud rate ++#define TIME_TX_TO_IDLE_CAL 726 // Rounded up from 725 to compensate for variable baud rate ++#define TIME_RX_TO_IDLE_NOCAL 1 // Rounded up from ~0.1 ++#define TIME_RX_TO_IDLE_CAL 724 ++#define TIME_MANUAL_CAL 735 ++ ++// Error macros ++#ifndef CONFIG_DYNAMIC_DEBUG ++extern uint debug; ++#define CC1101_DEBUG(cc1101, format, ...) if (debug) dev_info(&cc1101->spi->dev, format "\n", ##__VA_ARGS__) ++#else ++#define CC1101_DEBUG(cc1101, format, ...) dev_dbg(&cc1101->spi->dev, format "\n", ##__VA_ARGS__) ++#endif ++ ++#define CC1101_INFO(cc1101, format, ...) dev_info(&cc1101->spi->dev, format "\n", ##__VA_ARGS__) ++#define CC1101_ERROR(cc1101, format, ...) dev_err(&cc1101->spi->dev, format "\n", ##__VA_ARGS__) ++ ++// Driver state ++typedef enum { ++ MODE_IDLE, ++ MODE_TX, ++ MODE_RX ++} cc1101_mode_t; ++ ++ ++// Struct to hold per-device state ++typedef struct { ++ struct spi_device *spi; ++ dev_t devt; ++ int irq; ++ struct mutex chrdev_lock; ++ struct mutex device_lock; ++ cc1101_mode_t mode; ++ struct cc1101_tx_config tx_config; ++ struct cc1101_rx_config rx_config; ++ unsigned char *current_packet; ++ u32 bytes_remaining; ++ DECLARE_KFIFO_PTR(rx_fifo, unsigned char); ++ struct timer_list rx_timeout; ++ struct work_struct rx_timeout_work; ++} cc1101_t; ++ + #endif +\ No newline at end of file +diff --git a/cc1101_main.c b/cc1101_main.c +index a684adf..fde434c 100755 +--- a/cc1101_main.c ++++ b/cc1101_main.c +@@ -1,190 +1,190 @@ +-// SPDX-License-Identifier: GPL-2.0-only +-/* +-* Copyright (c) 2021 +-*/ +- +-#include +-#include +-#include +-#include +-#include +- +-#include "cc1101_internal.h" +-#include "cc1101_chrdev.h" +-#include "cc1101_spi.h" +-#include "cc1101_radio.h" +- +-// Module parameter for the largest packet that can be transmitted/received +-uint max_packet_size = 1024; +-module_param(max_packet_size, uint, 0660); +- +-// Module parameter for the number of received packets to be held in the kernel FIFO waiting to be read +-uint rx_fifo_size = 10; +-module_param(rx_fifo_size, uint, 0660); +- +-#ifndef CONFIG_DYNAMIC_DEBUG +-uint debug = 0; +-module_param(debug, uint, 0660); +-#endif +- +-#ifdef RXONLY +- #define EXTRA_STATUS "[RX Only] -" +-#else +- #define EXTRA_STATUS "-" +-#endif +- +-/* +-* Function called on module insertion for each CC1101 entry in device tree +-*/ +-static int cc1101_spi_probe(struct spi_device *spi) +-{ +- cc1101_t *cc1101; +- struct gpio_desc *gpio; +- spi_transaction_t partnum, version; +- +- // Memory allocations - use devm_* functions so they are automatically freed +- // Allocate a new CC1101 struct +- cc1101 = devm_kzalloc(&spi->dev, sizeof(*cc1101), GFP_KERNEL); +- if (cc1101 == NULL) +- { +- dev_err(&spi->dev, "Failed to allocate memory"); +- return -ENOMEM; +- } +- +- // Allocate a buffer to hold partial packets being received +- cc1101->current_packet = devm_kzalloc(&spi->dev, max_packet_size, GFP_KERNEL); +- if (cc1101->current_packet == NULL) +- { +- dev_err(&spi->dev, "Failed to allocate packet buffer memory"); +- return -ENOMEM; +- } +- +- // Associate the SPI device to the CC1101 +- cc1101->spi = spi; +- +- // Initialise the device locks +- mutex_init(&cc1101->device_lock); +- mutex_init(&cc1101->chrdev_lock); +- +- // Setup the RX timeout timer +- timer_setup(&cc1101->rx_timeout, cc1101_rx_timeout, 0); +- +- // Read the CC1101 part and version numbers from the device registers +- partnum = cc1101_spi_read_status_register(cc1101, PARTNUM); +- version = cc1101_spi_read_status_register(cc1101, VERSION); +- +- // Check the device is a CC1101 +- if (partnum.data != 0x00 || (version.data != 0x04 && version.data != 0x14)) +- { +- dev_info(&spi->dev, "Device not found (Partnum: 0x%02x, Version: 0x%02x)", partnum.data, version.data); +- return -ENODEV; +- } +- +- // Reset the device, which will place it in idle and load the default config +- cc1101_reset(cc1101); +- +- // Get the GPIO associated with the device in device tree +- gpio = devm_gpiod_get_index(&spi->dev, "int", 0, GPIOD_IN); +- if (IS_ERR(gpio)) +- { +- dev_err(&spi->dev, "Failed to get GPIO"); +- return -ENODEV; +- } +- +- // Get an IRQ for the GPIO +- cc1101->irq = gpiod_to_irq(gpio); +- +- // Attach the interrupt handler to the GPIO +- if(devm_request_threaded_irq(&spi->dev, cc1101->irq, NULL, cc1101_rx_interrupt, IRQF_TRIGGER_RISING | IRQF_ONESHOT, dev_name(&spi->dev), cc1101) != 0){ +- dev_err(&spi->dev, "Failed to setup interrupt handler"); +- return -ENODEV; +- } +- +- // Add a /dev/cc1101.x.x character device for the device +- if(cc1101_chrdev_add_device(cc1101) != 0){ +- dev_err(&spi->dev, "Failed to create character device"); +- return -ENODEV; +- } +- +- // Associate the device struct to the parent SPI device +- spi_set_drvdata(spi, cc1101); +- +- CC1101_INFO(cc1101, "Ready " EXTRA_STATUS " (Partnum: 0x%02x, Version: 0x%02x)", partnum.data, version.data); +- +- return 0; +-} +- +-/* +-* Function called on module removal for each CC1101 entry in device tree +-*/ +-static int cc1101_spi_remove(struct spi_device *spi) +-{ +- cc1101_t* cc1101; +- cc1101 = spi_get_drvdata(spi); +- +- // Remove the RX timeout timer +- del_timer(&cc1101->rx_timeout); +- +- // Reset the hardware, placing it in idle mode +- cc1101_reset(cc1101); +- +- // Remove /dev/cc1101.x.x +- cc1101_chrdev_remove_device(cc1101); +- CC1101_INFO(cc1101, "Removed"); +- return 0; +-} +- +-/* +-* Register the module to handle cc1101 entries in device tree +-*/ +-static const struct of_device_id cc1101_dt_ids[] = { +- {.compatible = "ti,cc1101"}, +- {}, +-}; +-MODULE_DEVICE_TABLE(of, cc1101_dt_ids); +- +-static const struct spi_device_id cc1101_id[] = { +- { +- .name = "cc1101", +- }, +- {} +-}; +-MODULE_DEVICE_TABLE(spi, cc1101_id); +- +-static struct spi_driver cc1101_driver = { +- .driver = { +- .name = "cc1101", +- .owner = THIS_MODULE, +- .of_match_table = cc1101_dt_ids, +- }, +- .probe = cc1101_spi_probe, +- .remove = cc1101_spi_remove, +- .id_table = cc1101_id +-}; +- +-/* +-* Functions executed on module insertion/removal +-* Setup/remove the CC1101 character device class +-*/ +-static int __init cc1101_init(void) +-{ +- #ifndef CONFIG_DYNAMIC_DEBUG +- if(debug > 1) { +- return -EINVAL; +- } +- #endif +- +- return cc1101_chrdev_setup(&cc1101_driver); +-} +-module_init(cc1101_init); +- +-static void __exit cc1101_exit(void) +-{ +- cc1101_chrdev_teardown(&cc1101_driver); +-} +-module_exit(cc1101_exit); +- +-MODULE_LICENSE("GPL"); +-MODULE_AUTHOR(""); +-MODULE_DESCRIPTION("TI CC1101 Device Driver"); +-MODULE_VERSION("1.4.0"); ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++* Copyright (c) 2021 ++*/ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "cc1101_internal.h" ++#include "cc1101_chrdev.h" ++#include "cc1101_spi.h" ++#include "cc1101_radio.h" ++ ++// Module parameter for the largest packet that can be transmitted/received ++uint max_packet_size = 1024; ++module_param(max_packet_size, uint, 0660); ++ ++// Module parameter for the number of received packets to be held in the kernel FIFO waiting to be read ++uint rx_fifo_size = 10; ++module_param(rx_fifo_size, uint, 0660); ++ ++#ifndef CONFIG_DYNAMIC_DEBUG ++uint debug = 0; ++module_param(debug, uint, 0660); ++#endif ++ ++#ifdef RXONLY ++ #define EXTRA_STATUS "[RX Only] -" ++#else ++ #define EXTRA_STATUS "-" ++#endif ++ ++/* ++* Function called on module insertion for each CC1101 entry in device tree ++*/ ++static int cc1101_spi_probe(struct spi_device *spi) ++{ ++ cc1101_t *cc1101; ++ struct gpio_desc *gpio; ++ spi_transaction_t partnum, version; ++ ++ // Memory allocations - use devm_* functions so they are automatically freed ++ // Allocate a new CC1101 struct ++ cc1101 = devm_kzalloc(&spi->dev, sizeof(*cc1101), GFP_KERNEL); ++ if (cc1101 == NULL) ++ { ++ dev_err(&spi->dev, "Failed to allocate memory"); ++ return -ENOMEM; ++ } ++ ++ // Allocate a buffer to hold partial packets being received ++ cc1101->current_packet = devm_kzalloc(&spi->dev, max_packet_size, GFP_KERNEL); ++ if (cc1101->current_packet == NULL) ++ { ++ dev_err(&spi->dev, "Failed to allocate packet buffer memory"); ++ return -ENOMEM; ++ } ++ ++ // Associate the SPI device to the CC1101 ++ cc1101->spi = spi; ++ ++ // Initialise the device locks ++ mutex_init(&cc1101->device_lock); ++ mutex_init(&cc1101->chrdev_lock); ++ ++ // Setup the RX timeout timer ++ timer_setup(&cc1101->rx_timeout, cc1101_rx_timeout, 0); ++ ++ // Read the CC1101 part and version numbers from the device registers ++ partnum = cc1101_spi_read_status_register(cc1101, PARTNUM); ++ version = cc1101_spi_read_status_register(cc1101, VERSION); ++ ++ // Check the device is a CC1101 ++ if (partnum.data != 0x00 || (version.data != 0x04 && version.data != 0x14)) ++ { ++ dev_info(&spi->dev, "Device not found (Partnum: 0x%02x, Version: 0x%02x)", partnum.data, version.data); ++ return -ENODEV; ++ } ++ ++ // Reset the device, which will place it in idle and load the default config ++ cc1101_reset(cc1101); ++ ++ // Get the GPIO associated with the device in device tree ++ gpio = devm_gpiod_get_index(&spi->dev, "int", 0, GPIOD_IN); ++ if (IS_ERR(gpio)) ++ { ++ dev_err(&spi->dev, "Failed to get GPIO"); ++ return -ENODEV; ++ } ++ ++ // Get an IRQ for the GPIO ++ cc1101->irq = gpiod_to_irq(gpio); ++ ++ // Attach the interrupt handler to the GPIO ++ if(devm_request_threaded_irq(&spi->dev, cc1101->irq, NULL, cc1101_rx_interrupt, IRQF_TRIGGER_RISING | IRQF_ONESHOT, dev_name(&spi->dev), cc1101) != 0){ ++ dev_err(&spi->dev, "Failed to setup interrupt handler"); ++ return -ENODEV; ++ } ++ ++ // Add a /dev/cc1101.x.x character device for the device ++ if(cc1101_chrdev_add_device(cc1101) != 0){ ++ dev_err(&spi->dev, "Failed to create character device"); ++ return -ENODEV; ++ } ++ ++ // Associate the device struct to the parent SPI device ++ spi_set_drvdata(spi, cc1101); ++ ++ CC1101_INFO(cc1101, "Ready " EXTRA_STATUS " (Partnum: 0x%02x, Version: 0x%02x)", partnum.data, version.data); ++ ++ return 0; ++} ++ ++/* ++* Function called on module removal for each CC1101 entry in device tree ++*/ ++static int cc1101_spi_remove(struct spi_device *spi) ++{ ++ cc1101_t* cc1101; ++ cc1101 = spi_get_drvdata(spi); ++ ++ // Remove the RX timeout timer ++ del_timer(&cc1101->rx_timeout); ++ ++ // Reset the hardware, placing it in idle mode ++ cc1101_reset(cc1101); ++ ++ // Remove /dev/cc1101.x.x ++ cc1101_chrdev_remove_device(cc1101); ++ CC1101_INFO(cc1101, "Removed"); ++ return 0; ++} ++ ++/* ++* Register the module to handle cc1101 entries in device tree ++*/ ++static const struct of_device_id cc1101_dt_ids[] = { ++ {.compatible = "ti,cc1101"}, ++ {}, ++}; ++MODULE_DEVICE_TABLE(of, cc1101_dt_ids); ++ ++static const struct spi_device_id cc1101_id[] = { ++ { ++ .name = "cc1101", ++ }, ++ {} ++}; ++MODULE_DEVICE_TABLE(spi, cc1101_id); ++ ++static struct spi_driver cc1101_driver = { ++ .driver = { ++ .name = "cc1101", ++ .owner = THIS_MODULE, ++ .of_match_table = cc1101_dt_ids, ++ }, ++ .probe = cc1101_spi_probe, ++ .remove = cc1101_spi_remove, ++ .id_table = cc1101_id ++}; ++ ++/* ++* Functions executed on module insertion/removal ++* Setup/remove the CC1101 character device class ++*/ ++static int __init cc1101_init(void) ++{ ++ #ifndef CONFIG_DYNAMIC_DEBUG ++ if(debug > 1) { ++ return -EINVAL; ++ } ++ #endif ++ ++ return cc1101_chrdev_setup(&cc1101_driver); ++} ++module_init(cc1101_init); ++ ++static void __exit cc1101_exit(void) ++{ ++ cc1101_chrdev_teardown(&cc1101_driver); ++} ++module_exit(cc1101_exit); ++ ++MODULE_LICENSE("GPL"); ++MODULE_AUTHOR(""); ++MODULE_DESCRIPTION("TI CC1101 Device Driver"); ++MODULE_VERSION("1.4.0"); +diff --git a/cc1101_radio.c b/cc1101_radio.c +index b0c1050..f2fd56f 100755 +--- a/cc1101_radio.c ++++ b/cc1101_radio.c +@@ -1,515 +1,515 @@ +-// SPDX-License-Identifier: GPL-2.0-only +-/* +-* Copyright (c) 2021 +-*/ +-#include "cc1101_internal.h" +-#include "cc1101_radio.h" +-#include "cc1101_config.h" +-#include "cc1101_spi.h" +- +-#include +-#include +-#include +- +-/* +-* Change the hardware and driver state. Delays based on the state transition times in the datasheet +-* +-* Arguments: +-* cc1101: device struct +-* to: mode to change the device state to +-*/ +-static void change_state(cc1101_t* cc1101, cc1101_mode_t to){ +- +- unsigned long delay; +- unsigned char command; +- +- switch(to){ +- case MODE_IDLE: +- command = SIDLE; +- switch(cc1101->mode) { +- case MODE_TX: +- CC1101_DEBUG(cc1101, "TX -> IDLE"); +- delay = TIME_TX_TO_IDLE_CAL; +- break; +- case MODE_RX: +- CC1101_DEBUG(cc1101, "RX -> IDLE"); +- delay = TIME_RX_TO_IDLE_CAL; +- break; +- case MODE_IDLE: +- CC1101_DEBUG(cc1101, "IDLE -> IDLE"); +- return; +- default: +- CC1101_DEBUG(cc1101, "%d -> IDLE", to); +- return; +- } +- break; +- case MODE_TX: +- command = STX; +- switch(cc1101->mode) { +- case MODE_IDLE: +- CC1101_DEBUG(cc1101, "IDLE -> TX"); +- delay = TIME_IDLE_TO_TX_CAL; +- break; +- case MODE_RX: +- CC1101_DEBUG(cc1101, "RX -> TX"); +- delay = TIME_RX_TO_TX; +- break; +- case MODE_TX: +- CC1101_DEBUG(cc1101, "TX -> TX"); +- return; +- default: +- CC1101_DEBUG(cc1101, "%d -> TX", to); +- return; +- } +- break; +- case MODE_RX: +- command = SRX; +- switch(cc1101->mode) { +- case MODE_IDLE: +- CC1101_DEBUG(cc1101, "IDLE -> RX"); +- delay = TIME_IDLE_TO_RX_CAL; +- break; +- case MODE_TX: +- CC1101_DEBUG(cc1101, "TX -> RX"); +- delay = TIME_TX_TO_RX; +- break; +- case MODE_RX: +- CC1101_DEBUG(cc1101, "RX -> RX"); +- return; +- default: +- CC1101_DEBUG(cc1101, "%d -> RX", to); +- return; +- } +- break; +- default: +- return; +- } +- // Send the command via SPI +- cc1101->mode = to; +- cc1101_spi_send_command(cc1101, command); +- udelay(delay); +-} +- +-/* +-* Flush the device's RXFIFO, returning to the idle state +-* +-* Arguments: +-* cc1101: device struct +-*/ +-void cc1101_flush_rx_fifo(cc1101_t *cc1101){ +- cc1101_spi_send_command(cc1101, SFRX); +- cc1101->mode = MODE_IDLE; +-} +- +-/* +-* Flush the device's TXFIFO, returning to the idle state +-* +-* Arguments: +-* cc1101: device struct +-*/ +-void cc1101_flush_tx_fifo(cc1101_t *cc1101){ +- cc1101_spi_send_command(cc1101, SFTX); +- cc1101->mode = MODE_IDLE; +-} +- +-/* +-* Set the device to idle +-* +-* Arguments: +-* cc1101: device struct +-*/ +-void cc1101_idle(cc1101_t* cc1101) +-{ +- CC1101_DEBUG(cc1101, "Idle Mode"); +- change_state(cc1101, MODE_IDLE); +-} +- +-/* +-* Set the device to RX +-* +-* Arguments: +-* cc1101: device struct +-*/ +-void cc1101_rx(cc1101_t* cc1101) +-{ +- CC1101_DEBUG(cc1101, "Receive Mode"); +- change_state(cc1101, MODE_RX); +-} +- +-# ifndef RXONLY +-/* +-* Function to transmit an arbitrary length packet (> 64 bytes) +-* Arguments: +-* cc1101: device struct +-* buf: bytes to transmit +-* len: length of buf +-* +-* +-*/ +-static void tx_multi(cc1101_t* cc1101, const char* buf, size_t len){ +- +- size_t bytes_remaining, fragment_size; +- unsigned char tx_bytes; +- int fixed_packet_mode = 0; +- +- // Before transmission, the full packet length is left to transmit +- bytes_remaining = len; +- +- // Write the value that the packet counter will be at when transmission finishes into PKTLEN +- // This is recommended by the datasheet, but will not be used until the radio +- // is placed in fixed length packet mode +- cc1101_spi_write_config_register(cc1101, PKTLEN, len % 256); +- +- // Set to continual transmit mode +- cc1101_spi_write_config_register(cc1101, PKTCTRL0, 0x02); +- +- // Write the first 32 byte fragment of the packet to the device +- cc1101_spi_write_txfifo(cc1101, &buf[0], 32); +- +- // Decrement the number of bytes remaining to transmit +- bytes_remaining -= 32; +- +- // Instruct the device to begin transmission +- change_state(cc1101, MODE_TX); +- +- CC1101_DEBUG(cc1101, "Transmitting 32 bytes, %zu remaining, %zu Total", bytes_remaining, len); +- +- // While there are still bytes to transmit +- while(bytes_remaining != 0){ +- +- // Read the current number bytes in the FIFO to be transmitted +- tx_bytes = cc1101_spi_read_status_register(cc1101, TXBYTES).data; +- +- // Check for underflow, exit +- // Caller will change state to IDLE and flush the TXFIFO, so handle the error by returning +- if(tx_bytes > FIFO_LEN) { +- CC1101_DEBUG(cc1101, "TXFIFO Underflow, Aborting Transmission"); +- return; +- } +- +- // If there are less than 32 bytes left of the current fragment to transmit, there is room in the +- // device's FIFO to fit another fragment +- if(tx_bytes < 32){ +- +- // Switch to fixed packet mode if we're safely within the last 256 bytes and haven't already +- if(!fixed_packet_mode && bytes_remaining < 128){ +- cc1101_spi_write_config_register(cc1101, PKTCTRL0, 0x00); +- fixed_packet_mode = 1; +- } +- +- // If there are less than 32 bytes left to transmit, set the number of bytes to transmit to the remaining length +- // Otherwise, transmit a full 32 bytes +- fragment_size = bytes_remaining < 32 ? bytes_remaining : 32; +- +- // Write the fragment to the device's TX FIFO and decrement the number of bytes left in the packet +- cc1101_spi_write_txfifo(cc1101, &buf[len-bytes_remaining], fragment_size); +- bytes_remaining-=fragment_size; +- CC1101_DEBUG(cc1101, "Transmitting %zu bytes, %zu remaining, %zu Total", fragment_size, bytes_remaining, len); +- } +- } +- +- // Wait until transmission has finished +- do { +- bytes_remaining = cc1101_spi_read_status_register(cc1101, TXBYTES).data; +- +- // Check for underflow, this shouldn't occur in fixed length mode. +- // Caller will change state to IDLE and flush the TXFIFO, so handle the error by exiting the loop +- if(bytes_remaining > FIFO_LEN){ +- CC1101_DEBUG(cc1101, "TXFIFO Underflow"); +- break; +- } +- +- } while(bytes_remaining > 0); +-} +- +-/* +-* Function to transmit a packet that will fit completely within the CC1101's TX FIFO (<= 64 bytes) +-* +-* Arguments: +-* cc1101: device struct +-* buf: bytes to transmit +-* len: length of buf +-* +-*/ +-static void tx_single(cc1101_t* cc1101, const char* buf, size_t len) +-{ +- int bytes_remaining; +- +- // Write the packet to device's TX FIFO +- cc1101_spi_write_txfifo(cc1101, buf, len); +- +- // Write the length of the packet to the device's Packet Length register +- cc1101_spi_write_config_register(cc1101, PKTLEN, len); +- +- // Set the device's transmission mode to fixed length +- cc1101_spi_write_config_register(cc1101, PKTCTRL0, 0x00); +- +- // Instruct the device to begin transmission +- change_state(cc1101, MODE_TX); +- +- CC1101_DEBUG(cc1101, "Transmitting %zu bytes, 0 remaining, %zu Total", len, len); +- +- // Wait until transmission has finished. Handle overflow condition +- do { +- bytes_remaining = cc1101_spi_read_status_register(cc1101, TXBYTES).data; +- +- // Check for underflow, this shouldn't occur in fixed length mode. +- // Handle the error by exiting the loop, as the device will be sent to idle afterwards anyway +- if(bytes_remaining > FIFO_LEN){ +- CC1101_DEBUG(cc1101, "TXFIFO Underflow"); +- break; +- } +- +- } while(bytes_remaining > 0) ; +-} +- +-/* +-* Function to transmit a packet +-* +-* Arguments: +-* cc1101: device struct +-* buf: bytes to transmit +-* len: length of buf +-* +-*/ +-void cc1101_tx(cc1101_t* cc1101, const char* buf, size_t len){ +- +- CC1101_DEBUG(cc1101, "Transmit Mode"); +- // Put the device into idle mode +- change_state(cc1101, MODE_IDLE); +- +- // TX method based on whether the packet will fully fit in the device's FIFO +- if(len > FIFO_LEN){ +- tx_multi(cc1101, buf, len); +- } +- else{ +- tx_single(cc1101, buf, len); +- } +- +- // Set the device to idle +- cc1101_idle(cc1101); +- +- // Flush the TXFIFO +- cc1101_flush_tx_fifo(cc1101); +- +- // Return to RX mode if configured +- if(cc1101->rx_config.packet_length > 0){ +- // Restore RX config +- cc1101_config_apply_rx(cc1101); +- cc1101_rx(cc1101); +- } +-} +-#endif +- +-/* +-* Function to reset the device and the driver's internal state +-* +-* Arguments: +-* cc1101: device struct +-*/ +-void cc1101_reset(cc1101_t* cc1101) +-{ +- CC1101_DEBUG(cc1101, "Reset"); +- +- // Reset the device +- cc1101->mode = MODE_IDLE; +- cc1101_spi_send_command(cc1101, SRES); +- +- // Reset the current packet counter +- cc1101->bytes_remaining = 0; +- +- // Clear the RX FIFO +- kfifo_reset(&cc1101->rx_fifo); +-} +- +-// Longest to expect between interrupts. At 0.6 kBaud (75 bytes/sec), time to fill to FIFOTHR (32 bytes) should be ~425ms, so 1000ms here seems reasonable +-// If an interrupt is missed, the RXFIFO will overflow and the device will not send any further interrupts, causing the device to lock indefinitely. +-// This value is used to set a timer that ensures the interrupt handler will trigger and unlock the device if this occurs. +-#define RX_TIMEOUT_MS 1000 +- +-/* +-* Interrupt handler function called when the device raises GDO2 +-* The default receive configuration instructs it to raise GDO2 when the RX FIFO has received x bytes +-* +-* Arguments: +-* irq: IRQ number +-* handle: device struct +-*/ +-irqreturn_t cc1101_rx_interrupt(int irq, void *handle) +-{ +- cc1101_t* cc1101 = handle; +- +- size_t i; +- int fifo_available; +- unsigned char rx_bytes; +- +- CC1101_DEBUG(cc1101, "Interrupt"); +- +- // Interrupt is only used when the driver is in receive mode +- if(cc1101->mode == MODE_RX){ +- +- // Stop the RX timeout timer (if it's running) while processing the interrupt +- del_timer(&cc1101->rx_timeout); +- +- // Read the number of bytes in the device's RX FIFO +- rx_bytes = cc1101_spi_read_status_register(cc1101, RXBYTES).data; +- +- // If an overflow has occured part of the packet will have been missed, so reset and wait for the next packet +- if(rx_bytes > FIFO_LEN) { +- CC1101_ERROR(cc1101, "RXFIFO Overflow. If this error persists, decrease baud rate"); +- +- // Flush the RXFIFO +- cc1101_flush_rx_fifo(cc1101); +- +- // Reset SYNC_MODE to the value from the config +- cc1101_spi_write_config_register(cc1101, MDMCFG2, cc1101_get_mdmcfg2(&cc1101->rx_config.common, &cc1101->rx_config)); +- +- // Put the device back into receive mode ready to receive the next packet +- change_state(cc1101, MODE_RX); +- +- // Unlock mutex and reset packet count +- cc1101->bytes_remaining = 0; +- mutex_unlock(&cc1101->device_lock); +- +- return IRQ_HANDLED; +- } +- +- // If 0 bytes remaining, this is a new packet +- if(cc1101->bytes_remaining == 0){ +- +- // Try to lock the device +- if(mutex_trylock(&cc1101->device_lock) != 1){ +- // If this fails, it is because a process has /dev/cc1101.x.x open +- CC1101_DEBUG(cc1101, "Interrupt Handler Failed To Acquire Lock"); +- +- // Drain the device's RX FIFO, otherwise it will overflow, causing RX to stop +- // This can be drained into the temp buffer as the next interrupt will start a new packet and overwrite it anyway +- cc1101_spi_read_rxfifo(cc1101, &cc1101->current_packet[0], rx_bytes - 1); +- +- // Return and wait for the next interrupt +- return IRQ_HANDLED; +- } +- // If the lock is held, a packet can be received +- CC1101_DEBUG(cc1101, "Receiving Packet"); +- +- // Update the number of bytes to receive from the RX configuration +- cc1101->bytes_remaining = cc1101->rx_config.packet_length; +- +- // Set SYNC_MODE to "No Preamble/Sync" - this will cause RX to continue even if carrier-sense drops below the defined threshold +- // This prevents a situation where more bytes are expected for the current packet but another interrupt doesn't occur +- cc1101_spi_write_config_register(cc1101, MDMCFG2, cc1101_get_mdmcfg2(&cc1101->rx_config.common, &cc1101->rx_config) & 0xF8); +- +- // Start a timer for how long we should wait for another interrupt to arrive +- mod_timer(&cc1101->rx_timeout, jiffies + msecs_to_jiffies(RX_TIMEOUT_MS)); +- } +- +- // Something went wrong and there aren't any bytes in the RX FIFO even though GDO2 went high +- if(rx_bytes == 0){ +- // Reset the receive counter, so the next interrupt will be the start of a new packet +- CC1101_ERROR(cc1101, "Receive Error, Waiting for Next Packet"); +- cc1101->bytes_remaining = 0; +- +- // Reset SYNC_MODE to the value from the config +- cc1101_spi_write_config_register(cc1101, MDMCFG2, cc1101_get_mdmcfg2(&cc1101->rx_config.common, &cc1101->rx_config)); +- +- // Release the device lock +- mutex_unlock(&cc1101->device_lock); +- } +- // Received some bytes, but there are still some remaining in the packet to be received +- else if(rx_bytes < cc1101->bytes_remaining){ +- +- CC1101_DEBUG(cc1101, "Received %d Bytes, Read %d Bytes, %d Bytes Remaining", rx_bytes, rx_bytes - 1, cc1101->bytes_remaining - (rx_bytes - 1)); +- +- // Read the received number of bytes from the device's RX FIFO into the temporary buffer +- cc1101_spi_read_rxfifo(cc1101, &cc1101->current_packet[cc1101->rx_config.packet_length - cc1101->bytes_remaining], rx_bytes - 1); +- +- // Decrement the number of bytes left to receive in the packet +- cc1101->bytes_remaining = cc1101->bytes_remaining - (rx_bytes - 1); +- +- // Restart the timer for how long we should wait for another interrupt to arrive +- mod_timer(&cc1101->rx_timeout, jiffies + msecs_to_jiffies(RX_TIMEOUT_MS)); +- +- //Return without releasing the lock. The device is in the middle of a receive and can't be reconfigured +- } +- // Received a number of bytes greater than or equal to the number left in the packet +- else { +- del_timer(&cc1101->rx_timeout); +- +- CC1101_DEBUG(cc1101, "Received %d Bytes, Read %d Bytes, %d Bytes Remaining", rx_bytes, cc1101->bytes_remaining, 0); +- +- // RX has finished and the required bytes are in the device's RX FIFO, so put the device in idle mode +- change_state(cc1101, MODE_IDLE); +- +- // Read the remaining bytes from the device's RX FIFO +- cc1101_spi_read_rxfifo(cc1101, &cc1101->current_packet[cc1101->rx_config.packet_length - cc1101->bytes_remaining], cc1101->bytes_remaining); +- +- // Get the amount of space left in the received packet buffer +- fifo_available = kfifo_avail(&cc1101->rx_fifo); +- +- // Remove oldest packet from the received packet buffer if there is less than is required to hold the newly received packet +- if(fifo_available < cc1101->rx_config.packet_length){ +- CC1101_DEBUG(cc1101, "RX FIFO Full - Removing Packet"); +- for(i = 0; i < cc1101->rx_config.packet_length; i++){ +- kfifo_skip(&cc1101->rx_fifo); +- } +- } +- +- // Add the new packet to the received packet buffer +- kfifo_in(&cc1101->rx_fifo, cc1101->current_packet, cc1101->rx_config.packet_length); +- +- CC1101_DEBUG(cc1101, "Packet Received"); +- +- // Reset the number of bytes remaining +- cc1101->bytes_remaining = 0; +- +- // Flush the device's RX FIFO +- cc1101_flush_rx_fifo(cc1101); +- +- // Reset SYNC_MODE to the value from the config +- cc1101_spi_write_config_register(cc1101, MDMCFG2, cc1101_get_mdmcfg2(&cc1101->rx_config.common, &cc1101->rx_config)); +- +- // Put the device back into receive mode ready to receive the next packet +- change_state(cc1101, MODE_RX); +- +- // Release the lock so the device can be reconfigured if necessary +- mutex_unlock(&cc1101->device_lock); +- } +- } +- return IRQ_HANDLED; +-} +- +-/* +-* Work function to recover from a missed interrupt in RX +-* +-* Arguments: +-* work: rx_timeout_work struct of the device that has missed an interrupt +-*/ +-void cc1101_rx_timeout_work(struct work_struct *work){ +- // Get the device from the work_struct +- cc1101_t *cc1101; +- cc1101 = container_of(work, cc1101_t, rx_timeout_work); +- +- // Call the interrupt handler, which will detect the RXFIFO overflow state from the device and recover +- cc1101_rx_interrupt(cc1101->irq, cc1101); +-} +- +-/* +-* Receive timer callback. Called when an interrupt is expected during RX, but never arrives +-* +-* This can occur at high baud rates if the interrupt handler has not finished execution when the next interrupt arrives +-* +-* RXFIFO will have overflowed by the time this is called. +-* +-* Arguments: +-* cc1101: device struct +-*/ +-void cc1101_rx_timeout(struct timer_list *t){ +- // Get the device the timeout has occured on +- cc1101_t *cc1101 = from_timer(cc1101, t, rx_timeout); +- CC1101_ERROR(cc1101, "RX Interrupt Missed"); +- +- // Schedule the handler to be called in the process context to recover +- INIT_WORK(&cc1101->rx_timeout_work, cc1101_rx_timeout_work); +- schedule_work(&cc1101->rx_timeout_work); +-} +- ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++* Copyright (c) 2021 ++*/ ++#include "cc1101_internal.h" ++#include "cc1101_radio.h" ++#include "cc1101_config.h" ++#include "cc1101_spi.h" ++ ++#include ++#include ++#include ++ ++/* ++* Change the hardware and driver state. Delays based on the state transition times in the datasheet ++* ++* Arguments: ++* cc1101: device struct ++* to: mode to change the device state to ++*/ ++static void change_state(cc1101_t* cc1101, cc1101_mode_t to){ ++ ++ unsigned long delay; ++ unsigned char command; ++ ++ switch(to){ ++ case MODE_IDLE: ++ command = SIDLE; ++ switch(cc1101->mode) { ++ case MODE_TX: ++ CC1101_DEBUG(cc1101, "TX -> IDLE"); ++ delay = TIME_TX_TO_IDLE_CAL; ++ break; ++ case MODE_RX: ++ CC1101_DEBUG(cc1101, "RX -> IDLE"); ++ delay = TIME_RX_TO_IDLE_CAL; ++ break; ++ case MODE_IDLE: ++ CC1101_DEBUG(cc1101, "IDLE -> IDLE"); ++ return; ++ default: ++ CC1101_DEBUG(cc1101, "%d -> IDLE", to); ++ return; ++ } ++ break; ++ case MODE_TX: ++ command = STX; ++ switch(cc1101->mode) { ++ case MODE_IDLE: ++ CC1101_DEBUG(cc1101, "IDLE -> TX"); ++ delay = TIME_IDLE_TO_TX_CAL; ++ break; ++ case MODE_RX: ++ CC1101_DEBUG(cc1101, "RX -> TX"); ++ delay = TIME_RX_TO_TX; ++ break; ++ case MODE_TX: ++ CC1101_DEBUG(cc1101, "TX -> TX"); ++ return; ++ default: ++ CC1101_DEBUG(cc1101, "%d -> TX", to); ++ return; ++ } ++ break; ++ case MODE_RX: ++ command = SRX; ++ switch(cc1101->mode) { ++ case MODE_IDLE: ++ CC1101_DEBUG(cc1101, "IDLE -> RX"); ++ delay = TIME_IDLE_TO_RX_CAL; ++ break; ++ case MODE_TX: ++ CC1101_DEBUG(cc1101, "TX -> RX"); ++ delay = TIME_TX_TO_RX; ++ break; ++ case MODE_RX: ++ CC1101_DEBUG(cc1101, "RX -> RX"); ++ return; ++ default: ++ CC1101_DEBUG(cc1101, "%d -> RX", to); ++ return; ++ } ++ break; ++ default: ++ return; ++ } ++ // Send the command via SPI ++ cc1101->mode = to; ++ cc1101_spi_send_command(cc1101, command); ++ udelay(delay); ++} ++ ++/* ++* Flush the device's RXFIFO, returning to the idle state ++* ++* Arguments: ++* cc1101: device struct ++*/ ++void cc1101_flush_rx_fifo(cc1101_t *cc1101){ ++ cc1101_spi_send_command(cc1101, SFRX); ++ cc1101->mode = MODE_IDLE; ++} ++ ++/* ++* Flush the device's TXFIFO, returning to the idle state ++* ++* Arguments: ++* cc1101: device struct ++*/ ++void cc1101_flush_tx_fifo(cc1101_t *cc1101){ ++ cc1101_spi_send_command(cc1101, SFTX); ++ cc1101->mode = MODE_IDLE; ++} ++ ++/* ++* Set the device to idle ++* ++* Arguments: ++* cc1101: device struct ++*/ ++void cc1101_idle(cc1101_t* cc1101) ++{ ++ CC1101_DEBUG(cc1101, "Idle Mode"); ++ change_state(cc1101, MODE_IDLE); ++} ++ ++/* ++* Set the device to RX ++* ++* Arguments: ++* cc1101: device struct ++*/ ++void cc1101_rx(cc1101_t* cc1101) ++{ ++ CC1101_DEBUG(cc1101, "Receive Mode"); ++ change_state(cc1101, MODE_RX); ++} ++ ++# ifndef RXONLY ++/* ++* Function to transmit an arbitrary length packet (> 64 bytes) ++* Arguments: ++* cc1101: device struct ++* buf: bytes to transmit ++* len: length of buf ++* ++* ++*/ ++static void tx_multi(cc1101_t* cc1101, const char* buf, size_t len){ ++ ++ size_t bytes_remaining, fragment_size; ++ unsigned char tx_bytes; ++ int fixed_packet_mode = 0; ++ ++ // Before transmission, the full packet length is left to transmit ++ bytes_remaining = len; ++ ++ // Write the value that the packet counter will be at when transmission finishes into PKTLEN ++ // This is recommended by the datasheet, but will not be used until the radio ++ // is placed in fixed length packet mode ++ cc1101_spi_write_config_register(cc1101, PKTLEN, len % 256); ++ ++ // Set to continual transmit mode ++ cc1101_spi_write_config_register(cc1101, PKTCTRL0, 0x02); ++ ++ // Write the first 32 byte fragment of the packet to the device ++ cc1101_spi_write_txfifo(cc1101, &buf[0], 32); ++ ++ // Decrement the number of bytes remaining to transmit ++ bytes_remaining -= 32; ++ ++ // Instruct the device to begin transmission ++ change_state(cc1101, MODE_TX); ++ ++ CC1101_DEBUG(cc1101, "Transmitting 32 bytes, %zu remaining, %zu Total", bytes_remaining, len); ++ ++ // While there are still bytes to transmit ++ while(bytes_remaining != 0){ ++ ++ // Read the current number bytes in the FIFO to be transmitted ++ tx_bytes = cc1101_spi_read_status_register(cc1101, TXBYTES).data; ++ ++ // Check for underflow, exit ++ // Caller will change state to IDLE and flush the TXFIFO, so handle the error by returning ++ if(tx_bytes > FIFO_LEN) { ++ CC1101_DEBUG(cc1101, "TXFIFO Underflow, Aborting Transmission"); ++ return; ++ } ++ ++ // If there are less than 32 bytes left of the current fragment to transmit, there is room in the ++ // device's FIFO to fit another fragment ++ if(tx_bytes < 32){ ++ ++ // Switch to fixed packet mode if we're safely within the last 256 bytes and haven't already ++ if(!fixed_packet_mode && bytes_remaining < 128){ ++ cc1101_spi_write_config_register(cc1101, PKTCTRL0, 0x00); ++ fixed_packet_mode = 1; ++ } ++ ++ // If there are less than 32 bytes left to transmit, set the number of bytes to transmit to the remaining length ++ // Otherwise, transmit a full 32 bytes ++ fragment_size = bytes_remaining < 32 ? bytes_remaining : 32; ++ ++ // Write the fragment to the device's TX FIFO and decrement the number of bytes left in the packet ++ cc1101_spi_write_txfifo(cc1101, &buf[len-bytes_remaining], fragment_size); ++ bytes_remaining-=fragment_size; ++ CC1101_DEBUG(cc1101, "Transmitting %zu bytes, %zu remaining, %zu Total", fragment_size, bytes_remaining, len); ++ } ++ } ++ ++ // Wait until transmission has finished ++ do { ++ bytes_remaining = cc1101_spi_read_status_register(cc1101, TXBYTES).data; ++ ++ // Check for underflow, this shouldn't occur in fixed length mode. ++ // Caller will change state to IDLE and flush the TXFIFO, so handle the error by exiting the loop ++ if(bytes_remaining > FIFO_LEN){ ++ CC1101_DEBUG(cc1101, "TXFIFO Underflow"); ++ break; ++ } ++ ++ } while(bytes_remaining > 0); ++} ++ ++/* ++* Function to transmit a packet that will fit completely within the CC1101's TX FIFO (<= 64 bytes) ++* ++* Arguments: ++* cc1101: device struct ++* buf: bytes to transmit ++* len: length of buf ++* ++*/ ++static void tx_single(cc1101_t* cc1101, const char* buf, size_t len) ++{ ++ int bytes_remaining; ++ ++ // Write the packet to device's TX FIFO ++ cc1101_spi_write_txfifo(cc1101, buf, len); ++ ++ // Write the length of the packet to the device's Packet Length register ++ cc1101_spi_write_config_register(cc1101, PKTLEN, len); ++ ++ // Set the device's transmission mode to fixed length ++ cc1101_spi_write_config_register(cc1101, PKTCTRL0, 0x00); ++ ++ // Instruct the device to begin transmission ++ change_state(cc1101, MODE_TX); ++ ++ CC1101_DEBUG(cc1101, "Transmitting %zu bytes, 0 remaining, %zu Total", len, len); ++ ++ // Wait until transmission has finished. Handle overflow condition ++ do { ++ bytes_remaining = cc1101_spi_read_status_register(cc1101, TXBYTES).data; ++ ++ // Check for underflow, this shouldn't occur in fixed length mode. ++ // Handle the error by exiting the loop, as the device will be sent to idle afterwards anyway ++ if(bytes_remaining > FIFO_LEN){ ++ CC1101_DEBUG(cc1101, "TXFIFO Underflow"); ++ break; ++ } ++ ++ } while(bytes_remaining > 0) ; ++} ++ ++/* ++* Function to transmit a packet ++* ++* Arguments: ++* cc1101: device struct ++* buf: bytes to transmit ++* len: length of buf ++* ++*/ ++void cc1101_tx(cc1101_t* cc1101, const char* buf, size_t len){ ++ ++ CC1101_DEBUG(cc1101, "Transmit Mode"); ++ // Put the device into idle mode ++ change_state(cc1101, MODE_IDLE); ++ ++ // TX method based on whether the packet will fully fit in the device's FIFO ++ if(len > FIFO_LEN){ ++ tx_multi(cc1101, buf, len); ++ } ++ else{ ++ tx_single(cc1101, buf, len); ++ } ++ ++ // Set the device to idle ++ cc1101_idle(cc1101); ++ ++ // Flush the TXFIFO ++ cc1101_flush_tx_fifo(cc1101); ++ ++ // Return to RX mode if configured ++ if(cc1101->rx_config.packet_length > 0){ ++ // Restore RX config ++ cc1101_config_apply_rx(cc1101); ++ cc1101_rx(cc1101); ++ } ++} ++#endif ++ ++/* ++* Function to reset the device and the driver's internal state ++* ++* Arguments: ++* cc1101: device struct ++*/ ++void cc1101_reset(cc1101_t* cc1101) ++{ ++ CC1101_DEBUG(cc1101, "Reset"); ++ ++ // Reset the device ++ cc1101->mode = MODE_IDLE; ++ cc1101_spi_send_command(cc1101, SRES); ++ ++ // Reset the current packet counter ++ cc1101->bytes_remaining = 0; ++ ++ // Clear the RX FIFO ++ kfifo_reset(&cc1101->rx_fifo); ++} ++ ++// Longest to expect between interrupts. At 0.6 kBaud (75 bytes/sec), time to fill to FIFOTHR (32 bytes) should be ~425ms, so 1000ms here seems reasonable ++// If an interrupt is missed, the RXFIFO will overflow and the device will not send any further interrupts, causing the device to lock indefinitely. ++// This value is used to set a timer that ensures the interrupt handler will trigger and unlock the device if this occurs. ++#define RX_TIMEOUT_MS 1000 ++ ++/* ++* Interrupt handler function called when the device raises GDO2 ++* The default receive configuration instructs it to raise GDO2 when the RX FIFO has received x bytes ++* ++* Arguments: ++* irq: IRQ number ++* handle: device struct ++*/ ++irqreturn_t cc1101_rx_interrupt(int irq, void *handle) ++{ ++ cc1101_t* cc1101 = handle; ++ ++ size_t i; ++ int fifo_available; ++ unsigned char rx_bytes; ++ ++ CC1101_DEBUG(cc1101, "Interrupt"); ++ ++ // Interrupt is only used when the driver is in receive mode ++ if(cc1101->mode == MODE_RX){ ++ ++ // Stop the RX timeout timer (if it's running) while processing the interrupt ++ del_timer(&cc1101->rx_timeout); ++ ++ // Read the number of bytes in the device's RX FIFO ++ rx_bytes = cc1101_spi_read_status_register(cc1101, RXBYTES).data; ++ ++ // If an overflow has occured part of the packet will have been missed, so reset and wait for the next packet ++ if(rx_bytes > FIFO_LEN) { ++ CC1101_ERROR(cc1101, "RXFIFO Overflow. If this error persists, decrease baud rate"); ++ ++ // Flush the RXFIFO ++ cc1101_flush_rx_fifo(cc1101); ++ ++ // Reset SYNC_MODE to the value from the config ++ cc1101_spi_write_config_register(cc1101, MDMCFG2, cc1101_get_mdmcfg2(&cc1101->rx_config.common, &cc1101->rx_config)); ++ ++ // Put the device back into receive mode ready to receive the next packet ++ change_state(cc1101, MODE_RX); ++ ++ // Unlock mutex and reset packet count ++ cc1101->bytes_remaining = 0; ++ mutex_unlock(&cc1101->device_lock); ++ ++ return IRQ_HANDLED; ++ } ++ ++ // If 0 bytes remaining, this is a new packet ++ if(cc1101->bytes_remaining == 0){ ++ ++ // Try to lock the device ++ if(mutex_trylock(&cc1101->device_lock) != 1){ ++ // If this fails, it is because a process has /dev/cc1101.x.x open ++ CC1101_DEBUG(cc1101, "Interrupt Handler Failed To Acquire Lock"); ++ ++ // Drain the device's RX FIFO, otherwise it will overflow, causing RX to stop ++ // This can be drained into the temp buffer as the next interrupt will start a new packet and overwrite it anyway ++ cc1101_spi_read_rxfifo(cc1101, &cc1101->current_packet[0], rx_bytes - 1); ++ ++ // Return and wait for the next interrupt ++ return IRQ_HANDLED; ++ } ++ // If the lock is held, a packet can be received ++ CC1101_DEBUG(cc1101, "Receiving Packet"); ++ ++ // Update the number of bytes to receive from the RX configuration ++ cc1101->bytes_remaining = cc1101->rx_config.packet_length; ++ ++ // Set SYNC_MODE to "No Preamble/Sync" - this will cause RX to continue even if carrier-sense drops below the defined threshold ++ // This prevents a situation where more bytes are expected for the current packet but another interrupt doesn't occur ++ cc1101_spi_write_config_register(cc1101, MDMCFG2, cc1101_get_mdmcfg2(&cc1101->rx_config.common, &cc1101->rx_config) & 0xF8); ++ ++ // Start a timer for how long we should wait for another interrupt to arrive ++ mod_timer(&cc1101->rx_timeout, jiffies + msecs_to_jiffies(RX_TIMEOUT_MS)); ++ } ++ ++ // Something went wrong and there aren't any bytes in the RX FIFO even though GDO2 went high ++ if(rx_bytes == 0){ ++ // Reset the receive counter, so the next interrupt will be the start of a new packet ++ CC1101_ERROR(cc1101, "Receive Error, Waiting for Next Packet"); ++ cc1101->bytes_remaining = 0; ++ ++ // Reset SYNC_MODE to the value from the config ++ cc1101_spi_write_config_register(cc1101, MDMCFG2, cc1101_get_mdmcfg2(&cc1101->rx_config.common, &cc1101->rx_config)); ++ ++ // Release the device lock ++ mutex_unlock(&cc1101->device_lock); ++ } ++ // Received some bytes, but there are still some remaining in the packet to be received ++ else if(rx_bytes < cc1101->bytes_remaining){ ++ ++ CC1101_DEBUG(cc1101, "Received %d Bytes, Read %d Bytes, %d Bytes Remaining", rx_bytes, rx_bytes - 1, cc1101->bytes_remaining - (rx_bytes - 1)); ++ ++ // Read the received number of bytes from the device's RX FIFO into the temporary buffer ++ cc1101_spi_read_rxfifo(cc1101, &cc1101->current_packet[cc1101->rx_config.packet_length - cc1101->bytes_remaining], rx_bytes - 1); ++ ++ // Decrement the number of bytes left to receive in the packet ++ cc1101->bytes_remaining = cc1101->bytes_remaining - (rx_bytes - 1); ++ ++ // Restart the timer for how long we should wait for another interrupt to arrive ++ mod_timer(&cc1101->rx_timeout, jiffies + msecs_to_jiffies(RX_TIMEOUT_MS)); ++ ++ //Return without releasing the lock. The device is in the middle of a receive and can't be reconfigured ++ } ++ // Received a number of bytes greater than or equal to the number left in the packet ++ else { ++ del_timer(&cc1101->rx_timeout); ++ ++ CC1101_DEBUG(cc1101, "Received %d Bytes, Read %d Bytes, %d Bytes Remaining", rx_bytes, cc1101->bytes_remaining, 0); ++ ++ // RX has finished and the required bytes are in the device's RX FIFO, so put the device in idle mode ++ change_state(cc1101, MODE_IDLE); ++ ++ // Read the remaining bytes from the device's RX FIFO ++ cc1101_spi_read_rxfifo(cc1101, &cc1101->current_packet[cc1101->rx_config.packet_length - cc1101->bytes_remaining], cc1101->bytes_remaining); ++ ++ // Get the amount of space left in the received packet buffer ++ fifo_available = kfifo_avail(&cc1101->rx_fifo); ++ ++ // Remove oldest packet from the received packet buffer if there is less than is required to hold the newly received packet ++ if(fifo_available < cc1101->rx_config.packet_length){ ++ CC1101_DEBUG(cc1101, "RX FIFO Full - Removing Packet"); ++ for(i = 0; i < cc1101->rx_config.packet_length; i++){ ++ kfifo_skip(&cc1101->rx_fifo); ++ } ++ } ++ ++ // Add the new packet to the received packet buffer ++ kfifo_in(&cc1101->rx_fifo, cc1101->current_packet, cc1101->rx_config.packet_length); ++ ++ CC1101_DEBUG(cc1101, "Packet Received"); ++ ++ // Reset the number of bytes remaining ++ cc1101->bytes_remaining = 0; ++ ++ // Flush the device's RX FIFO ++ cc1101_flush_rx_fifo(cc1101); ++ ++ // Reset SYNC_MODE to the value from the config ++ cc1101_spi_write_config_register(cc1101, MDMCFG2, cc1101_get_mdmcfg2(&cc1101->rx_config.common, &cc1101->rx_config)); ++ ++ // Put the device back into receive mode ready to receive the next packet ++ change_state(cc1101, MODE_RX); ++ ++ // Release the lock so the device can be reconfigured if necessary ++ mutex_unlock(&cc1101->device_lock); ++ } ++ } ++ return IRQ_HANDLED; ++} ++ ++/* ++* Work function to recover from a missed interrupt in RX ++* ++* Arguments: ++* work: rx_timeout_work struct of the device that has missed an interrupt ++*/ ++void cc1101_rx_timeout_work(struct work_struct *work){ ++ // Get the device from the work_struct ++ cc1101_t *cc1101; ++ cc1101 = container_of(work, cc1101_t, rx_timeout_work); ++ ++ // Call the interrupt handler, which will detect the RXFIFO overflow state from the device and recover ++ cc1101_rx_interrupt(cc1101->irq, cc1101); ++} ++ ++/* ++* Receive timer callback. Called when an interrupt is expected during RX, but never arrives ++* ++* This can occur at high baud rates if the interrupt handler has not finished execution when the next interrupt arrives ++* ++* RXFIFO will have overflowed by the time this is called. ++* ++* Arguments: ++* cc1101: device struct ++*/ ++void cc1101_rx_timeout(struct timer_list *t){ ++ // Get the device the timeout has occured on ++ cc1101_t *cc1101 = from_timer(cc1101, t, rx_timeout); ++ CC1101_ERROR(cc1101, "RX Interrupt Missed"); ++ ++ // Schedule the handler to be called in the process context to recover ++ INIT_WORK(&cc1101->rx_timeout_work, cc1101_rx_timeout_work); ++ schedule_work(&cc1101->rx_timeout_work); ++} ++ +diff --git a/cc1101_radio.h b/cc1101_radio.h +index 9f09043..696b96b 100755 +--- a/cc1101_radio.h ++++ b/cc1101_radio.h +@@ -1,16 +1,16 @@ +-#ifndef CC1101_RADIO_H +-#define CC1101_RADIO_H +- +-#include +-#include +- +-irqreturn_t cc1101_rx_interrupt(int irq, void *handle); +-void cc1101_rx_timeout(struct timer_list *timer); +-void cc1101_idle(cc1101_t* cc1101); +-void cc1101_rx(cc1101_t* cc1101); +-#ifndef RXONLY +-void cc1101_tx(cc1101_t* cc1101, const char* buf, size_t len); +-#endif +-void cc1101_reset(cc1101_t* cc1101); +- ++#ifndef CC1101_RADIO_H ++#define CC1101_RADIO_H ++ ++#include ++#include ++ ++irqreturn_t cc1101_rx_interrupt(int irq, void *handle); ++void cc1101_rx_timeout(struct timer_list *timer); ++void cc1101_idle(cc1101_t* cc1101); ++void cc1101_rx(cc1101_t* cc1101); ++#ifndef RXONLY ++void cc1101_tx(cc1101_t* cc1101, const char* buf, size_t len); ++#endif ++void cc1101_reset(cc1101_t* cc1101); ++ + #endif +\ No newline at end of file +diff --git a/cc1101_spi.c b/cc1101_spi.c +index 3f9bf77..c950711 100755 +--- a/cc1101_spi.c ++++ b/cc1101_spi.c +@@ -1,338 +1,338 @@ +-// SPDX-License-Identifier: GPL-2.0-only +-/* +-* Copyright (c) 2021 +-*/ +-#include "cc1101_spi.h" +- +-#include "cc1101_internal.h" +- +-#define MAX_REPEATS 10 +- +-#define SPI_BURST 0x40 +-#define SPI_READ 0x80 +- +-/* +-* Read a single byte from one of the device's configuration registers via SPI +-* +-* Arguments: +-* cc1101: device struct +-* reg: address of CC1101 register to read +-* +-* Returns: +-* Transaction struct containing register contents and device status +-*/ +-spi_transaction_t cc1101_spi_read_config_register(cc1101_t* cc1101, unsigned char reg) { +- struct spi_transfer t = {0}; +- struct spi_message m; +- spi_transaction_t transaction; +- +- WARN_ON(reg > 0x2E); +- +- transaction.header = reg | SPI_READ; +- transaction.data = 0; +- +- t.tx_buf = &transaction; +- t.rx_buf = &transaction; +- t.len = 2; +- t.cs_change = 0; +- +- spi_message_init(&m); +- spi_message_add_tail(&t, &m); +- spi_sync(cc1101->spi, &m); +- +- return transaction; +-} +- +-/* +-* Writes a single byte to one of the device's registers via SPI +-* +-* Arguments: +-* cc1101: device struct +-* reg: address of CC1101 register to write +-* value: byte to write to the register +-* +-* Returns: +-* Device status +-*/ +-unsigned char cc1101_spi_write_config_register(cc1101_t* cc1101, unsigned char reg, unsigned char value) { +- struct spi_transfer t = {0}; +- struct spi_message m; +- spi_transaction_t transaction; +- +- WARN_ON(reg > 0x2E); +- +- transaction.header = reg; +- transaction.data = value; +- +- t.tx_buf = &transaction, +- t.rx_buf = &transaction, +- t.len = 2, +- t.cs_change = 0, +- +- spi_message_init(&m); +- spi_message_add_tail(&t, &m); +- spi_sync(cc1101->spi, &m); +- +- return transaction.header; +-} +- +-/* +-* Read a sequence of bytes from the device's registers via SPI +-* +-* Arguments: +-* cc1101: device struct +-* reg: address of CC1101 registers to start reading from. +-* out: pointer to a buffer that will contain the register values read from the device +-* len: size of out, number of bytes to read from the device +-* +-* Returns: +-* Device status +-*/ +-static unsigned char read_registers(cc1101_t* cc1101, unsigned char reg, unsigned char* out, unsigned char len) { +- struct spi_transfer t = {0}; +- struct spi_message m; +- unsigned char transaction[FIFO_LEN + 1]; +- +- transaction[0] = reg | SPI_BURST | SPI_READ; +- +- t.tx_buf = &transaction; +- t.rx_buf = &transaction; +- t.len = len + 1; +- t.cs_change = 0; +- +- spi_message_init(&m); +- spi_message_add_tail(&t, &m); +- spi_sync(cc1101->spi, &m); +- +- memcpy(out, &transaction[1], len); +- +- return transaction[0]; +-} +- +-/* +-* Read a sequence of bytes from the device's config registers via SPI +-* +-* Arguments: +-* cc1101: device struct +-* registers: pointer to a buffer that will contain the values read from the device +-* len: size of out, number of bytes to read from the device +-* +-* Returns: +-* Device status +-* +-*/ +-unsigned char cc1101_spi_read_config_registers(cc1101_t* cc1101, unsigned char* registers, unsigned char len) { +- BUG_ON(len > CONFIG_REGISTERS_LEN); +- return read_registers(cc1101, 0x00, registers, len); +-} +- +-/* +-* Read a sequence of bytes from the device's PATABLE via SPI +-* +-* Arguments: +-* cc1101: device struct +-* patable: pointer to a buffer that will contain the values read from the device +-* len: size of out, number of bytes to read from the device +-* +-* Returns: +-* Device status +-*/ +-unsigned char cc1101_spi_read_patable(cc1101_t* cc1101, unsigned char* patable, unsigned char len) { +- BUG_ON(len > PATABLE_LEN); +- return read_registers(cc1101, PATABLE, patable, len); +-} +- +-/* +-* Read a sequence of bytes from the device's RXFIFO via SPI +-* +-* Arguments: +-* cc1101: device struct +-* rx_bytes: pointer to a buffer that will contain the values read from the device +-* len: size of out, number of bytes to read from the device +-* +-* Returns: +-* Device status +-*/ +-unsigned char cc1101_spi_read_rxfifo(cc1101_t* cc1101, unsigned char* rx_bytes, unsigned char len) { +- BUG_ON(len > FIFO_LEN); +- return read_registers(cc1101, FIFO, rx_bytes, len); +-} +- +-/* +-* Write a sequence of bytes to the device's registers via SPI +-* +-* Arguments: +-* cc1101: device struct +-* reg: address of CC1101 registers to start writing to. Must have the burst bit set +-* in: pointer to a buffer containing a byte sequence to write to the device registers +-* len: number of bytes in buffer that will be written to the device +-* +-* Returns: +-* Device status +-*/ +-static unsigned char write_registers(cc1101_t* cc1101, unsigned char reg, const unsigned char* in, unsigned char len) { +- struct spi_transfer t = {0}; +- struct spi_message m; +- unsigned char transaction[FIFO_LEN + 1]; +- +- transaction[0] = reg | SPI_BURST; +- memcpy(&transaction[1], in, len); +- +- t.tx_buf = &transaction; +- t.rx_buf = &transaction; +- t.len = len + 1; +- t.cs_change = 0; +- +- spi_message_init(&m); +- spi_message_add_tail(&t, &m); +- spi_sync(cc1101->spi, &m); +- +- return transaction[0]; +-} +- +-/* +-* Write a sequence of bytes to the device's configuration registers via SPI +-* +-* Arguments: +-* cc1101: device struct +-* registers: pointer to a buffer containing a byte sequence to write to the device registers +-* len: number of bytes in buffer that will be written to the device +-* +-* Returns: +-* Device status +-*/ +-unsigned char cc1101_spi_write_config_registers(cc1101_t* cc1101, const unsigned char* registers, unsigned char len) { +- BUG_ON(len > CONFIG_REGISTERS_LEN); +- return write_registers(cc1101, 0x00, registers, len); +-} +- +-/* +-* Write a sequence of bytes to the device's PATABLE registers via SPI +-* +-* Arguments: +-* cc1101: device struct +-* patable: pointer to a buffer containing a byte sequence to write to the PATABLE +-* len: number of bytes in buffer that will be written to the device +-* +-* Returns: +-* Device status +-*/ +-unsigned char cc1101_spi_write_patable(cc1101_t* cc1101, const unsigned char* patable, unsigned char len) { +- BUG_ON(len > PATABLE_LEN); +- return write_registers(cc1101, PATABLE, patable, len); +-} +- +-/* +-* Write a sequence of bytes to the device's TXFIFO via SPI +-* +-* Arguments: +-* cc1101: device struct +-* tx_bytes: pointer to a buffer containing a byte sequence to write to the TXFIFO +-* len: number of bytes in buffer that will be written to the device +-* +-* Returns: +-* Device status +-*/ +-unsigned char cc1101_spi_write_txfifo(cc1101_t* cc1101, const unsigned char* tx_bytes, unsigned char len) { +- BUG_ON(len > FIFO_LEN); +- return write_registers(cc1101, FIFO, tx_bytes, len); +-} +- +-/* +-* Sends a command to the device via SPI +-* +-* Arguments: +-* cc1101: device struct +-* command: command to send to the device +-* +-* Returns: +-* Device status +-*/ +-unsigned char cc1101_spi_send_command(cc1101_t* cc1101, unsigned char command) { +- struct spi_transfer t = {0}; +- struct spi_message m; +- unsigned char transaction; +- +- WARN_ON(command < 0x30 || command > 0x3D); +- +- transaction = command; +- +- t.tx_buf = &transaction; +- t.rx_buf = &transaction; +- t.len = 1; +- t.cs_change = 0; +- +- spi_message_init(&m); +- spi_message_add_tail(&t, &m); +- spi_sync(cc1101->spi, &m); +- +- return transaction; +-} +- +-/* +-* Read a single byte from one of the device's status registers via SPI +-* +-* Arguments: +-* cc1101: device struct +-* reg: address of CC1101 status register to read +-* +-* Returns: +-* Transaction struct containing register contents and device status +-*/ +-spi_transaction_t cc1101_spi_read_status_register_once(cc1101_t* cc1101, unsigned char reg) { +- struct spi_transfer t = {0}; +- struct spi_message m; +- spi_transaction_t transaction; +- +- WARN_ON(reg < 0x30 || reg > 0x3D); +- +- // Status registers require the burst bit to be set +- transaction.header = reg | SPI_BURST | SPI_READ; +- transaction.data = 0; +- +- t.tx_buf = &transaction; +- t.rx_buf = &transaction; +- t.len = 2; +- t.cs_change = 0; +- +- spi_message_init(&m); +- spi_message_add_tail(&t, &m); +- spi_sync(cc1101->spi, &m); +- +- return transaction; +-} +- +-/* +-* Read a single byte from one of the device's status registers via SPI +-* +-* The double-read implemented here is recommended by the CC1101 Errata to prevent corrupt values +-* from being read from the device. +-* +-* Arguments: +-* cc1101: device struct +-* reg: address of CC1101 status register to read +-* +-* Returns: +-* Transaction struct containing register contents and device status +-*/ +-spi_transaction_t cc1101_spi_read_status_register(cc1101_t* cc1101, unsigned char reg) { +- spi_transaction_t result, result_check; +- +- // Read status register +- result = cc1101_spi_read_status_register_once(cc1101, reg); +- while (1) { +- // Read again +- result_check = cc1101_spi_read_status_register_once(cc1101, reg); +- +- // If the values match, the value was valid +- if (result.data == result_check.data) { +- break; +- } +- // If not, continue until two reads agree +- else { +- result = result_check; +- } +- } +- +- return result; ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++* Copyright (c) 2021 ++*/ ++#include "cc1101_spi.h" ++ ++#include "cc1101_internal.h" ++ ++#define MAX_REPEATS 10 ++ ++#define SPI_BURST 0x40 ++#define SPI_READ 0x80 ++ ++/* ++* Read a single byte from one of the device's configuration registers via SPI ++* ++* Arguments: ++* cc1101: device struct ++* reg: address of CC1101 register to read ++* ++* Returns: ++* Transaction struct containing register contents and device status ++*/ ++spi_transaction_t cc1101_spi_read_config_register(cc1101_t* cc1101, unsigned char reg) { ++ struct spi_transfer t = {0}; ++ struct spi_message m; ++ spi_transaction_t transaction; ++ ++ WARN_ON(reg > 0x2E); ++ ++ transaction.header = reg | SPI_READ; ++ transaction.data = 0; ++ ++ t.tx_buf = &transaction; ++ t.rx_buf = &transaction; ++ t.len = 2; ++ t.cs_change = 0; ++ ++ spi_message_init(&m); ++ spi_message_add_tail(&t, &m); ++ spi_sync(cc1101->spi, &m); ++ ++ return transaction; ++} ++ ++/* ++* Writes a single byte to one of the device's registers via SPI ++* ++* Arguments: ++* cc1101: device struct ++* reg: address of CC1101 register to write ++* value: byte to write to the register ++* ++* Returns: ++* Device status ++*/ ++unsigned char cc1101_spi_write_config_register(cc1101_t* cc1101, unsigned char reg, unsigned char value) { ++ struct spi_transfer t = {0}; ++ struct spi_message m; ++ spi_transaction_t transaction; ++ ++ WARN_ON(reg > 0x2E); ++ ++ transaction.header = reg; ++ transaction.data = value; ++ ++ t.tx_buf = &transaction, ++ t.rx_buf = &transaction, ++ t.len = 2, ++ t.cs_change = 0, ++ ++ spi_message_init(&m); ++ spi_message_add_tail(&t, &m); ++ spi_sync(cc1101->spi, &m); ++ ++ return transaction.header; ++} ++ ++/* ++* Read a sequence of bytes from the device's registers via SPI ++* ++* Arguments: ++* cc1101: device struct ++* reg: address of CC1101 registers to start reading from. ++* out: pointer to a buffer that will contain the register values read from the device ++* len: size of out, number of bytes to read from the device ++* ++* Returns: ++* Device status ++*/ ++static unsigned char read_registers(cc1101_t* cc1101, unsigned char reg, unsigned char* out, unsigned char len) { ++ struct spi_transfer t = {0}; ++ struct spi_message m; ++ unsigned char transaction[FIFO_LEN + 1]; ++ ++ transaction[0] = reg | SPI_BURST | SPI_READ; ++ ++ t.tx_buf = &transaction; ++ t.rx_buf = &transaction; ++ t.len = len + 1; ++ t.cs_change = 0; ++ ++ spi_message_init(&m); ++ spi_message_add_tail(&t, &m); ++ spi_sync(cc1101->spi, &m); ++ ++ memcpy(out, &transaction[1], len); ++ ++ return transaction[0]; ++} ++ ++/* ++* Read a sequence of bytes from the device's config registers via SPI ++* ++* Arguments: ++* cc1101: device struct ++* registers: pointer to a buffer that will contain the values read from the device ++* len: size of out, number of bytes to read from the device ++* ++* Returns: ++* Device status ++* ++*/ ++unsigned char cc1101_spi_read_config_registers(cc1101_t* cc1101, unsigned char* registers, unsigned char len) { ++ BUG_ON(len > CONFIG_REGISTERS_LEN); ++ return read_registers(cc1101, 0x00, registers, len); ++} ++ ++/* ++* Read a sequence of bytes from the device's PATABLE via SPI ++* ++* Arguments: ++* cc1101: device struct ++* patable: pointer to a buffer that will contain the values read from the device ++* len: size of out, number of bytes to read from the device ++* ++* Returns: ++* Device status ++*/ ++unsigned char cc1101_spi_read_patable(cc1101_t* cc1101, unsigned char* patable, unsigned char len) { ++ BUG_ON(len > PATABLE_LEN); ++ return read_registers(cc1101, PATABLE, patable, len); ++} ++ ++/* ++* Read a sequence of bytes from the device's RXFIFO via SPI ++* ++* Arguments: ++* cc1101: device struct ++* rx_bytes: pointer to a buffer that will contain the values read from the device ++* len: size of out, number of bytes to read from the device ++* ++* Returns: ++* Device status ++*/ ++unsigned char cc1101_spi_read_rxfifo(cc1101_t* cc1101, unsigned char* rx_bytes, unsigned char len) { ++ BUG_ON(len > FIFO_LEN); ++ return read_registers(cc1101, FIFO, rx_bytes, len); ++} ++ ++/* ++* Write a sequence of bytes to the device's registers via SPI ++* ++* Arguments: ++* cc1101: device struct ++* reg: address of CC1101 registers to start writing to. Must have the burst bit set ++* in: pointer to a buffer containing a byte sequence to write to the device registers ++* len: number of bytes in buffer that will be written to the device ++* ++* Returns: ++* Device status ++*/ ++static unsigned char write_registers(cc1101_t* cc1101, unsigned char reg, const unsigned char* in, unsigned char len) { ++ struct spi_transfer t = {0}; ++ struct spi_message m; ++ unsigned char transaction[FIFO_LEN + 1]; ++ ++ transaction[0] = reg | SPI_BURST; ++ memcpy(&transaction[1], in, len); ++ ++ t.tx_buf = &transaction; ++ t.rx_buf = &transaction; ++ t.len = len + 1; ++ t.cs_change = 0; ++ ++ spi_message_init(&m); ++ spi_message_add_tail(&t, &m); ++ spi_sync(cc1101->spi, &m); ++ ++ return transaction[0]; ++} ++ ++/* ++* Write a sequence of bytes to the device's configuration registers via SPI ++* ++* Arguments: ++* cc1101: device struct ++* registers: pointer to a buffer containing a byte sequence to write to the device registers ++* len: number of bytes in buffer that will be written to the device ++* ++* Returns: ++* Device status ++*/ ++unsigned char cc1101_spi_write_config_registers(cc1101_t* cc1101, const unsigned char* registers, unsigned char len) { ++ BUG_ON(len > CONFIG_REGISTERS_LEN); ++ return write_registers(cc1101, 0x00, registers, len); ++} ++ ++/* ++* Write a sequence of bytes to the device's PATABLE registers via SPI ++* ++* Arguments: ++* cc1101: device struct ++* patable: pointer to a buffer containing a byte sequence to write to the PATABLE ++* len: number of bytes in buffer that will be written to the device ++* ++* Returns: ++* Device status ++*/ ++unsigned char cc1101_spi_write_patable(cc1101_t* cc1101, const unsigned char* patable, unsigned char len) { ++ BUG_ON(len > PATABLE_LEN); ++ return write_registers(cc1101, PATABLE, patable, len); ++} ++ ++/* ++* Write a sequence of bytes to the device's TXFIFO via SPI ++* ++* Arguments: ++* cc1101: device struct ++* tx_bytes: pointer to a buffer containing a byte sequence to write to the TXFIFO ++* len: number of bytes in buffer that will be written to the device ++* ++* Returns: ++* Device status ++*/ ++unsigned char cc1101_spi_write_txfifo(cc1101_t* cc1101, const unsigned char* tx_bytes, unsigned char len) { ++ BUG_ON(len > FIFO_LEN); ++ return write_registers(cc1101, FIFO, tx_bytes, len); ++} ++ ++/* ++* Sends a command to the device via SPI ++* ++* Arguments: ++* cc1101: device struct ++* command: command to send to the device ++* ++* Returns: ++* Device status ++*/ ++unsigned char cc1101_spi_send_command(cc1101_t* cc1101, unsigned char command) { ++ struct spi_transfer t = {0}; ++ struct spi_message m; ++ unsigned char transaction; ++ ++ WARN_ON(command < 0x30 || command > 0x3D); ++ ++ transaction = command; ++ ++ t.tx_buf = &transaction; ++ t.rx_buf = &transaction; ++ t.len = 1; ++ t.cs_change = 0; ++ ++ spi_message_init(&m); ++ spi_message_add_tail(&t, &m); ++ spi_sync(cc1101->spi, &m); ++ ++ return transaction; ++} ++ ++/* ++* Read a single byte from one of the device's status registers via SPI ++* ++* Arguments: ++* cc1101: device struct ++* reg: address of CC1101 status register to read ++* ++* Returns: ++* Transaction struct containing register contents and device status ++*/ ++spi_transaction_t cc1101_spi_read_status_register_once(cc1101_t* cc1101, unsigned char reg) { ++ struct spi_transfer t = {0}; ++ struct spi_message m; ++ spi_transaction_t transaction; ++ ++ WARN_ON(reg < 0x30 || reg > 0x3D); ++ ++ // Status registers require the burst bit to be set ++ transaction.header = reg | SPI_BURST | SPI_READ; ++ transaction.data = 0; ++ ++ t.tx_buf = &transaction; ++ t.rx_buf = &transaction; ++ t.len = 2; ++ t.cs_change = 0; ++ ++ spi_message_init(&m); ++ spi_message_add_tail(&t, &m); ++ spi_sync(cc1101->spi, &m); ++ ++ return transaction; ++} ++ ++/* ++* Read a single byte from one of the device's status registers via SPI ++* ++* The double-read implemented here is recommended by the CC1101 Errata to prevent corrupt values ++* from being read from the device. ++* ++* Arguments: ++* cc1101: device struct ++* reg: address of CC1101 status register to read ++* ++* Returns: ++* Transaction struct containing register contents and device status ++*/ ++spi_transaction_t cc1101_spi_read_status_register(cc1101_t* cc1101, unsigned char reg) { ++ spi_transaction_t result, result_check; ++ ++ // Read status register ++ result = cc1101_spi_read_status_register_once(cc1101, reg); ++ while (1) { ++ // Read again ++ result_check = cc1101_spi_read_status_register_once(cc1101, reg); ++ ++ // If the values match, the value was valid ++ if (result.data == result_check.data) { ++ break; ++ } ++ // If not, continue until two reads agree ++ else { ++ result = result_check; ++ } ++ } ++ ++ return result; + } +\ No newline at end of file +diff --git a/cc1101_spi.h b/cc1101_spi.h +index 0e4ecc3..388b819 100755 +--- a/cc1101_spi.h ++++ b/cc1101_spi.h +@@ -1,32 +1,32 @@ +-// SPDX-License-Identifier: GPL-2.0-only +-/* +-* Copyright (c) 2021 +-*/ +-#ifndef CC1101_SPI_H +-#define CC1101_SPI_H +- +-#include +- +-#include "cc1101_internal.h" +- +-typedef struct { +- unsigned char header; +- unsigned char data; +-} spi_transaction_t; +- +-spi_transaction_t cc1101_spi_read_config_register(cc1101_t* cc1101, unsigned char reg); +-unsigned char cc1101_spi_write_config_register(cc1101_t* cc1101, unsigned char reg, unsigned char value); +- +-unsigned char cc1101_spi_read_config_registers(cc1101_t* cc1101, unsigned char* registers, unsigned char len); +-unsigned char cc1101_spi_read_patable(cc1101_t* cc1101, unsigned char* patable, unsigned char len); +-unsigned char cc1101_spi_read_rxfifo(cc1101_t* cc1101, unsigned char* rx_bytes, unsigned char len); +- +-unsigned char cc1101_spi_write_config_registers(cc1101_t* cc1101, const unsigned char* registers, unsigned char len); +-unsigned char cc1101_spi_write_patable(cc1101_t* cc1101, const unsigned char* patable, unsigned char len); +-unsigned char cc1101_spi_write_txfifo(cc1101_t* cc1101, const unsigned char* tx_bytes, unsigned char len); +- +-spi_transaction_t cc1101_spi_read_status_register_once(cc1101_t* cc1101, unsigned char reg); +-spi_transaction_t cc1101_spi_read_status_register(cc1101_t* cc1101, unsigned char reg); +-unsigned char cc1101_spi_send_command(cc1101_t* cc1101, unsigned char command); +- ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++* Copyright (c) 2021 ++*/ ++#ifndef CC1101_SPI_H ++#define CC1101_SPI_H ++ ++#include ++ ++#include "cc1101_internal.h" ++ ++typedef struct { ++ unsigned char header; ++ unsigned char data; ++} spi_transaction_t; ++ ++spi_transaction_t cc1101_spi_read_config_register(cc1101_t* cc1101, unsigned char reg); ++unsigned char cc1101_spi_write_config_register(cc1101_t* cc1101, unsigned char reg, unsigned char value); ++ ++unsigned char cc1101_spi_read_config_registers(cc1101_t* cc1101, unsigned char* registers, unsigned char len); ++unsigned char cc1101_spi_read_patable(cc1101_t* cc1101, unsigned char* patable, unsigned char len); ++unsigned char cc1101_spi_read_rxfifo(cc1101_t* cc1101, unsigned char* rx_bytes, unsigned char len); ++ ++unsigned char cc1101_spi_write_config_registers(cc1101_t* cc1101, const unsigned char* registers, unsigned char len); ++unsigned char cc1101_spi_write_patable(cc1101_t* cc1101, const unsigned char* patable, unsigned char len); ++unsigned char cc1101_spi_write_txfifo(cc1101_t* cc1101, const unsigned char* tx_bytes, unsigned char len); ++ ++spi_transaction_t cc1101_spi_read_status_register_once(cc1101_t* cc1101, unsigned char reg); ++spi_transaction_t cc1101_spi_read_status_register(cc1101_t* cc1101, unsigned char reg); ++unsigned char cc1101_spi_send_command(cc1101_t* cc1101, unsigned char command); ++ + #endif +\ No newline at end of file +-- +2.49.0 + diff --git a/0002-Fix-build-errors-for-6.10.patch b/0002-Fix-build-errors-for-6.10.patch new file mode 100644 index 0000000..82e73f6 --- /dev/null +++ b/0002-Fix-build-errors-for-6.10.patch @@ -0,0 +1,56 @@ +From 0de2cb135aed20f3e706c3502d4cc9597debbda7 Mon Sep 17 00:00:00 2001 +From: itycodes +Date: Thu, 12 Jun 2025 02:03:17 +0200 +Subject: [PATCH 2/4] Fix build errors for 6.10 + +--- + cc1101_chrdev.c | 4 ++-- + cc1101_main.c | 3 +-- + 2 files changed, 3 insertions(+), 4 deletions(-) + +diff --git a/cc1101_chrdev.c b/cc1101_chrdev.c +index 21779b0..9a0bc69 100644 +--- a/cc1101_chrdev.c ++++ b/cc1101_chrdev.c +@@ -378,7 +378,7 @@ int cc1101_chrdev_add_device(cc1101_t * cc1101) { + cc1101->devt = MKDEV(SPI_MAJOR_NUMBER, device_index); + + // Create a /dev/cc1101.x.x character device +- if(IS_ERR(device_create(dev_class, &cc1101->spi->dev, cc1101->devt, cc1101, "cc1101.%d.%d", cc1101->spi->master->bus_num, cc1101->spi->chip_select))) { ++ if(IS_ERR(device_create(dev_class, &cc1101->spi->dev, cc1101->devt, cc1101, "cc1101.%d.%s", cc1101->spi->controller->bus_num, cc1101->spi->chip_select))) { + ret = -ENODEV; + goto done; + } +@@ -432,7 +432,7 @@ int cc1101_chrdev_setup(struct spi_driver* cc1101_driver) + goto err_register; + } + +- dev_class = class_create(THIS_MODULE, "cc1101"); ++ dev_class = class_create("cc1101"); + if (IS_ERR(dev_class)) { + ret = PTR_ERR(dev_class); + goto err_class_create; +diff --git a/cc1101_main.c b/cc1101_main.c +index fde434c..8bc340f 100755 +--- a/cc1101_main.c ++++ b/cc1101_main.c +@@ -117,7 +117,7 @@ static int cc1101_spi_probe(struct spi_device *spi) + /* + * Function called on module removal for each CC1101 entry in device tree + */ +-static int cc1101_spi_remove(struct spi_device *spi) ++static void cc1101_spi_remove(struct spi_device *spi) + { + cc1101_t* cc1101; + cc1101 = spi_get_drvdata(spi); +@@ -131,7 +131,6 @@ static int cc1101_spi_remove(struct spi_device *spi) + // Remove /dev/cc1101.x.x + cc1101_chrdev_remove_device(cc1101); + CC1101_INFO(cc1101, "Removed"); +- return 0; + } + + /* +-- +2.49.0 + diff --git a/0003-Fix-Makefile-not-respecting-KDIR.patch b/0003-Fix-Makefile-not-respecting-KDIR.patch new file mode 100644 index 0000000..293043c --- /dev/null +++ b/0003-Fix-Makefile-not-respecting-KDIR.patch @@ -0,0 +1,35 @@ +From b7f93f17eb508c07d45ace1b7b7e62be3ed81664 Mon Sep 17 00:00:00 2001 +From: itycodes +Date: Thu, 12 Jun 2025 02:07:07 +0200 +Subject: [PATCH 3/4] Fix Makefile not respecting KDIR + +--- + Makefile | 8 +++++--- + 1 file changed, 5 insertions(+), 3 deletions(-) + +diff --git a/Makefile b/Makefile +index c3154cc..30a4df4 100755 +--- a/Makefile ++++ b/Makefile +@@ -1,11 +1,13 @@ + obj-m := cc1101.o + cc1101-objs+= cc1101_main.o cc1101_chrdev.o cc1101_spi.o cc1101_radio.o cc1101_config.o + ++KDIR ?= /lib/modules/$(shell uname -r)/build ++ + all: +- make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules ++ make -C $(KDIR) M=$(PWD) modules + + rxonly: +- make -C /lib/modules/$(shell uname -r)/build M=$(PWD) ccflags-y="-DRXONLY" modules ++ make -C $(KDIR) M=$(PWD) ccflags-y="-DRXONLY" modules + + clean: +- make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean +\ No newline at end of file ++ make -C $(KDIR) M=$(PWD) clean +\ No newline at end of file +-- +2.49.0 + diff --git a/0004-Update-to-6.12.patch b/0004-Update-to-6.12.patch new file mode 100644 index 0000000..7821963 --- /dev/null +++ b/0004-Update-to-6.12.patch @@ -0,0 +1,27 @@ +From 4cd1b3906c5f42334629866afd5616515ab94bd1 Mon Sep 17 00:00:00 2001 +From: itycodes +Date: Thu, 12 Jun 2025 02:10:13 +0200 +Subject: [PATCH 4/4] Update to 6.12 + +--- + cc1101_internal.h | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/cc1101_internal.h b/cc1101_internal.h +index 4d0a30f..5f4c958 100644 +--- a/cc1101_internal.h ++++ b/cc1101_internal.h +@@ -7,6 +7,10 @@ + + #include + #include ++#include ++#include ++#include ++ + + #include "cc1101.h" + +-- +2.49.0 + diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..bd3c20a --- /dev/null +++ b/flake.nix @@ -0,0 +1,52 @@ +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, flake-utils }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { + inherit system; + config.allowUnfree = true; + }; + lib = pkgs.lib; + + kernel = pkgs.linuxPackages.kernel; + kmod = pkgs.stdenv.mkDerivation rec { + name = "cc1101-driver"; + src = builtins.fetchGit { + url = "https://github.com/28757B2/cc1101-driver"; + rev = "2d0293ee8afba84846da9ebdca2a718d6cc7a387"; + }; + patches = [ + ./0001-Replace-Windows-line-endings-with-Unix-ones.patch + ./0002-Fix-build-errors-for-6.10.patch + ./0003-Fix-Makefile-not-respecting-KDIR.patch + ./0004-Update-to-6.12.patch + ]; + + nativeBuildInputs = [ pkgs.kmod pkgs.ncurses pkgs.gnumake ]; + makeFlags = [ + "KDIR=${kernel.dev}/lib/modules/${kernel.modDirVersion}/build" + ]; + + buildPhase = '' + make ${lib.concatStringsSep " " makeFlags} + ''; + + installPhase = '' + mkdir -p $out/lib/modules/${kernel.modDirVersion}/extra + cp *.ko $out/lib/modules/${kernel.modDirVersion}/extra + ''; + + meta = { + description = "Linux device driver for the Texas Instruments CC1101 radio"; + platforms = pkgs.lib.platforms.linux; + }; + }; + in { + packages.default = kmod; + }); +}