You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cc1101-driver/0001-Replace-Windows-line-e...

5502 lines
193 KiB

From d182784a48ae1b86baaf1956e88139068b117e03 Mon Sep 17 00:00:00 2001
From: itycodes <tranquillitycodes@proton.me>
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.
-
- <one line to give the program's name and a brief idea of what it does.>
- Copyright (C) <year> <name of author>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along
- with this program; if not, write to the Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-
-Also add information on how to contact you by electronic and paper mail.
-
-If the program is interactive, make it output a short notice like this
-when it starts in an interactive mode:
-
- Gnomovision version 69, Copyright (C) year name of author
- Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
- This is free software, and you are welcome to redistribute it
- under certain conditions; type `show c' for details.
-
-The hypothetical commands `show w' and `show c' should show the appropriate
-parts of the General Public License. Of course, the commands you use may
-be called something other than `show w' and `show c'; they could even be
-mouse-clicks or menu items--whatever suits your program.
-
-You should also get your employer (if you work as a programmer) or your
-school, if any, to sign a "copyright disclaimer" for the program, if
-necessary. Here is a sample; alter the names:
-
- Yoyodyne, Inc., hereby disclaims all copyright interest in the program
- `Gnomovision' (which makes passes at compilers) written by James Hacker.
-
- <signature of Ty Coon>, 1 April 1989
- Ty Coon, President of Vice
-
-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.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+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 <linux/cdev.h>
-#include <linux/uaccess.h>
-
-#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 <linux/cdev.h>
+#include <linux/uaccess.h>
+
+#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 <linux/spi/spi.h>
-
-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 <linux/spi/spi.h>
+
+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 <linux/kernel.h>
-#include <linux/kfifo.h>
-
-#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 <linux/kernel.h>
+#include <linux/kfifo.h>
+
+#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 <linux/module.h>
-#include <linux/spi/spi.h>
-#include <linux/of_gpio.h>
-#include <linux/input.h>
-#include <generated/autoconf.h>
-
-#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 <linux/module.h>
+#include <linux/spi/spi.h>
+#include <linux/of_gpio.h>
+#include <linux/input.h>
+#include <generated/autoconf.h>
+
+#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 <linux/delay.h>
-#include <linux/kfifo.h>
-#include <linux/jiffies.h>
-
-/*
-* 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 <linux/delay.h>
+#include <linux/kfifo.h>
+#include <linux/jiffies.h>
+
+/*
+* 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 <linux/interrupt.h>
-#include <linux/timer.h>
-
-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 <linux/interrupt.h>
+#include <linux/timer.h>
+
+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 <linux/spi/spi.h>
-
-#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 <linux/spi/spi.h>
+
+#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