From d182784a48ae1b86baaf1956e88139068b117e03 Mon Sep 17 00:00:00 2001 From: itycodes Date: Thu, 12 Jun 2025 03:02:51 +0200 Subject: [PATCH 1/4] Replace Windows line endings with Unix ones --- LICENSE.txt | 678 +++++++++++++-------------- cc1101.h | 104 ++-- cc1101_chrdev.c | 928 ++++++++++++++++++------------------ cc1101_chrdev.h | 28 +- cc1101_config.c | 1146 ++++++++++++++++++++++----------------------- cc1101_config.h | 42 +- cc1101_internal.h | 300 ++++++------ cc1101_main.c | 380 +++++++-------- cc1101_radio.c | 1030 ++++++++++++++++++++-------------------- cc1101_radio.h | 30 +- cc1101_spi.c | 674 +++++++++++++------------- cc1101_spi.h | 62 +-- 12 files changed, 2701 insertions(+), 2701 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index 89e08fb..d159169 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,339 +1,339 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/cc1101.h b/cc1101.h index 1a8d9d2..c50480d 100755 --- a/cc1101.h +++ b/cc1101.h @@ -1,53 +1,53 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* -* Copyright (c) 2021 -*/ -#ifndef CC1101_H -#define CC1101_H - -#define CONFIG_REGISTERS_LEN 0x2F -#define PATABLE_LEN 8 - -typedef unsigned char cc1101_device_config_t[CONFIG_REGISTERS_LEN]; -typedef unsigned char cc1101_patable_t[PATABLE_LEN]; - -#define MOD_2FSK 0 -#define MOD_GFSK 1 -#define MOD_OOK 3 -#define MOD_4FSK 4 -#define MOD_MSK 7 - -#define CS_DISABLED 0 -#define CS_RELATIVE 1 -#define CS_ABSOLUTE 2 - -struct cc1101_common_config { - __u32 frequency; - __u8 modulation; - __u8 baud_rate_mantissa; - __u8 baud_rate_exponent; - __u8 deviation_mantissa; - __u8 deviation_exponent; - __u32 sync_word; -}; - -// Message sent from userspace via IOCTL containing RX mode configuration parameters -struct cc1101_rx_config { - struct cc1101_common_config common; - __u8 bandwidth_mantissa; - __u8 bandwidth_exponent; - __u8 max_lna_gain; - __u8 max_dvga_gain; - __u8 magn_target; - __u8 carrier_sense_mode; - __s8 carrier_sense; - __u32 packet_length; -}; - -// Message sent from userspace via IOCTL containing TX mode configuration parameters -struct cc1101_tx_config { - struct cc1101_common_config common; - __u8 tx_power; -}; - +// SPDX-License-Identifier: GPL-2.0-only +/* +* Copyright (c) 2021 +*/ +#ifndef CC1101_H +#define CC1101_H + +#define CONFIG_REGISTERS_LEN 0x2F +#define PATABLE_LEN 8 + +typedef unsigned char cc1101_device_config_t[CONFIG_REGISTERS_LEN]; +typedef unsigned char cc1101_patable_t[PATABLE_LEN]; + +#define MOD_2FSK 0 +#define MOD_GFSK 1 +#define MOD_OOK 3 +#define MOD_4FSK 4 +#define MOD_MSK 7 + +#define CS_DISABLED 0 +#define CS_RELATIVE 1 +#define CS_ABSOLUTE 2 + +struct cc1101_common_config { + __u32 frequency; + __u8 modulation; + __u8 baud_rate_mantissa; + __u8 baud_rate_exponent; + __u8 deviation_mantissa; + __u8 deviation_exponent; + __u32 sync_word; +}; + +// Message sent from userspace via IOCTL containing RX mode configuration parameters +struct cc1101_rx_config { + struct cc1101_common_config common; + __u8 bandwidth_mantissa; + __u8 bandwidth_exponent; + __u8 max_lna_gain; + __u8 max_dvga_gain; + __u8 magn_target; + __u8 carrier_sense_mode; + __s8 carrier_sense; + __u32 packet_length; +}; + +// Message sent from userspace via IOCTL containing TX mode configuration parameters +struct cc1101_tx_config { + struct cc1101_common_config common; + __u8 tx_power; +}; + #endif \ No newline at end of file diff --git a/cc1101_chrdev.c b/cc1101_chrdev.c index 489c569..21779b0 100644 --- a/cc1101_chrdev.c +++ b/cc1101_chrdev.c @@ -1,465 +1,465 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* -* Copyright (c) 2021 -*/ -#include -#include - -#include "cc1101_internal.h" -#include "cc1101_chrdev.h" -#include "cc1101_radio.h" -#include "cc1101_spi.h" -#include "cc1101_config.h" - -// IOCTL Definitions -#define CC1101_BASE 'c' -// Get the driver IOCTL version -#define CC1101_GET_VERSION _IOR(CC1101_BASE, 0, uint) -// Reset the device -#define CC1101_RESET _IO(CC1101_BASE, 1) -// Set TX configuration within driver -#define CC1101_SET_TX_CONF _IOW(CC1101_BASE, 2, struct cc1101_tx_config) -// Set RX configuration within driver -#define CC1101_SET_RX_CONF _IOW(CC1101_BASE, 3, struct cc1101_rx_config) -// Get TX configuration from driver -#define CC1101_GET_TX_CONF _IOR(CC1101_BASE, 4, struct cc1101_tx_config) -// Get TX configuration registers from driver -#define CC1101_GET_TX_RAW_CONF _IOR(CC1101_BASE, 5, cc1101_device_config_t) -// Get RX configuration from driver -#define CC1101_GET_RX_CONF _IOR(CC1101_BASE, 6, struct cc1101_rx_config) -// Get RX configuration registers from driver -#define CC1101_GET_RX_RAW_CONF _IOR(CC1101_BASE, 7, cc1101_device_config_t) -// Read configuration registers from hardware -#define CC1101_GET_DEV_RAW_CONF _IOR(CC1101_BASE, 8, cc1101_device_config_t) -// Get the current RSSI -#define CC1101_GET_RSSI _IOR(CC1101_BASE, 9, unsigned char) -// Get the configured maximum packet size -#define CC1101_GET_MAX_PACKET_SIZE _IOR(CC1101_BASE, 10, u32) - -#define SPI_MAJOR_NUMBER 153 -#define N_SPI_MINOR_NUMBERS 12 - -extern uint max_packet_size; -extern uint rx_fifo_size; - -static struct class *dev_class; -cc1101_t* device_list[N_SPI_MINOR_NUMBERS] = {0}; -static DEFINE_MUTEX(device_list_lock); - -/* -* Handler for open events to /dev/cc1101.x.x -* Only one handle is allowed to transmit, receive or configure the device at a time -* Operations will block until /dev/cc1101.x.x is closed -*/ -static int chrdev_open(struct inode *inode, struct file *file) -{ - cc1101_t* cc1101 = NULL; - int device_index; - - // Search the device list for the cc1101 struct relating to the chardev - if(mutex_lock_interruptible(&device_list_lock) != 0) { - return -EBUSY; - } - - for(device_index = 0; device_index < N_SPI_MINOR_NUMBERS; device_index++){ - if (device_list[device_index] != NULL){ - if (inode->i_rdev == device_list[device_index]->devt) { - cc1101 = device_list[device_index]; - } - } - } - - mutex_unlock(&device_list_lock); - - // This should never occur - a chardev shouldn't be created without a CC1101 being present - if(cc1101 == NULL){ - return -ENODEV; - } - - // Once found, lock the device and save the pointer to private data for the subsequent functions - if(mutex_lock_interruptible(&cc1101->chrdev_lock) != 0) { - return -EBUSY; - }; - - file->private_data = cc1101; - return 0; -} - -/* -* Handler for close events to /dev/cc1101.x.x -* Releases the device lock obtained at open -*/ -static int chrdev_release(struct inode *inode, struct file *file) -{ - cc1101_t* cc1101 = file->private_data; - mutex_unlock(&cc1101->chrdev_lock); - file->private_data = NULL; - return 0; -} - -/* -* Handler for iotcl commands to /dev/cc1101.x.x -* IOCTLs can be used to set and get the TX and RX config, reset the device -* and retrieve the contents of the CC1101's registers -*/ -static long chrdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) -{ - cc1101_t* cc1101 = file->private_data; - spi_transaction_t rssi; - int version = DRIVER_VERSION; - int ret = 0; - - // Temporary holding variables for new TX and RX configs - cc1101_device_config_t device_config; -#ifndef RXONLY - struct cc1101_tx_config tx_config; -#endif - struct cc1101_rx_config rx_config; - - if(_IOC_TYPE(cmd) != CC1101_BASE){ - CC1101_ERROR(cc1101, "Invalid IOCTL\n"); - return -EIO; - } - - // Lock the device for reconfiguration - if(mutex_lock_interruptible(&cc1101->device_lock) != 0) { - return -EBUSY; - }; - - switch(cmd){ - - // Get the userspace API version - case CC1101_GET_VERSION: - ret = copy_to_user((unsigned char*) arg, &version, sizeof(version)); - break; - - // Reset the device and driver state - case CC1101_RESET: - CC1101_INFO(cc1101, "Reset"); - cc1101_reset(cc1101); - ret = 0; - break; - - // Set the RX config for the device - case CC1101_SET_RX_CONF: - - // Copy the config provided in userspace to the kernel - if(copy_from_user(&rx_config, (unsigned char*) arg, sizeof(rx_config)) != 0){ - CC1101_ERROR(cc1101, "Error Copying Device RX Config\n"); - ret = -EFAULT; - goto done; - } - - // Validate the provided config - if(!cc1101_config_validate_rx(cc1101, &rx_config)){ - ret = -EINVAL; - goto done; - } - - // Replace the RX FIFO with a new one based on the provided packet size and the maximum number of queued packets - kfifo_free(&cc1101->rx_fifo); - if(kfifo_alloc(&cc1101->rx_fifo, rx_config.packet_length * rx_fifo_size, GFP_KERNEL) != 0) { - CC1101_ERROR(cc1101, "Failed to allocate packet FIFO memory"); - ret = -ENOMEM; - goto done; - } - - // Store the new RX config in the device struct - memcpy(&cc1101->rx_config, &rx_config, sizeof(struct cc1101_rx_config)); - - // Set the device to idle before reconfiguring - cc1101_idle(cc1101); - - // Write the configuration to the device - cc1101_config_apply_rx(cc1101); - - // Enter RX mode on the device - cc1101_rx(cc1101); - - ret = 0; - break; - - // Returns the RX config configured in the driver to userspace - case CC1101_GET_RX_CONF: - ret = copy_to_user((unsigned char*) arg, &cc1101->rx_config, sizeof(struct cc1101_rx_config)); - break; - - // Returns the register values for the RX configuration to userspace - case CC1101_GET_RX_RAW_CONF: - cc1101_config_rx_to_registers(device_config, &cc1101->rx_config); - ret = copy_to_user((unsigned char*) arg, device_config, sizeof(device_config)); - break; - - // Set the TX config to use for the next packet written to /dev/cc1101.x.x - case CC1101_SET_TX_CONF: -#ifndef RXONLY - // Copy the config provided in userspace to the kernel - if(copy_from_user(&tx_config, (unsigned char*) arg, sizeof(tx_config)) != 0){ - CC1101_ERROR(cc1101, "Error Copying Device TX Config\n"); - ret = -EFAULT; - goto done; - } - - // Validate the provided config - if(!cc1101_config_validate_tx(cc1101, &tx_config)){ - ret = -EINVAL; - goto done; - } - - // Store the new TX config in the device struct - memcpy(&cc1101->tx_config, &tx_config, sizeof(struct cc1101_tx_config)); - - ret = 0; -#else - ret = -EPERM; -#endif - break; - - // Returns the TX config configured in the driver to userspace - case CC1101_GET_TX_CONF: -#ifndef RXONLY - ret = copy_to_user((unsigned char*) arg, &cc1101->tx_config, sizeof(struct cc1101_tx_config)); -#else - ret = -EPERM; -#endif - break; - - // Returns the register values for the TX configuration to userspace - case CC1101_GET_TX_RAW_CONF: -#ifndef RXONLY - cc1101_config_tx_to_registers(device_config, &cc1101->tx_config); - ret = copy_to_user((unsigned char*) arg, device_config, sizeof(device_config)); -#else - ret = -EPERM; -#endif - break; - - // Reads the current state of the CC1101's registers and returns them to userspace - case CC1101_GET_DEV_RAW_CONF: - cc1101_spi_read_config_registers(cc1101, device_config, sizeof(device_config)); - ret = copy_to_user((unsigned char*) arg, device_config, sizeof(device_config)); - break; - - case CC1101_GET_RSSI: - rssi = cc1101_spi_read_status_register_once(cc1101, RSSI); - ret = copy_to_user((unsigned char*) arg, &rssi.data, sizeof(rssi.data)); - break; - - case CC1101_GET_MAX_PACKET_SIZE: - ret = copy_to_user((unsigned char*) arg, &max_packet_size, sizeof(max_packet_size)); - break; - - default: - CC1101_ERROR(cc1101, "Unknown Command %d, %zu, %zu\n", cmd, sizeof(struct cc1101_rx_config), sizeof(struct cc1101_tx_config)); - ret = -EIO; - break; - } - -done: - mutex_unlock(&cc1101->device_lock); - return ret; -} - -/* -* Handler for read events to /dev/cc1101.x.x -* A read request will return one packet from the receive buffer, if present -*/ -static ssize_t chrdev_read(struct file *file, char __user *buf, size_t len, loff_t *off) -{ - cc1101_t* cc1101 = file->private_data; - ssize_t ret; - unsigned int out_bytes; - - // Check a RX config has been set and that the out buffer is the correct size - if (cc1101->rx_config.packet_length == 0 || len != cc1101->rx_config.packet_length) { - return -EMSGSIZE; - } - - if(mutex_lock_interruptible(&cc1101->device_lock) != 0) { - return -EBUSY; - }; - - // Check there is at least one packet in the RX FIFO - if (kfifo_len(&cc1101->rx_fifo) < cc1101->rx_config.packet_length) { - ret = -ENOMSG; - goto done; - } - - // Copy the packet out to userspace - if (kfifo_to_user(&cc1101->rx_fifo, buf, len, &out_bytes) != 0) { - ret = -EFAULT; - goto done; - } - - // Check the number of bytes copied out matches the expected number - if (out_bytes == cc1101->rx_config.packet_length) { - ret = out_bytes; - } - else { - ret = -EFAULT; - } - -done: - mutex_unlock(&cc1101->device_lock); - return ret; -} - -/* -* Handler for write events to /dev/cc1101.x.x -* Written bytes are transmitted by the CC1101 according the TX config -*/ -static ssize_t chrdev_write(struct file *file, const char __user *buf, size_t len, loff_t *off) -{ -#ifndef RXONLY - cc1101_t* cc1101 = file->private_data; - ssize_t ret; - unsigned char *tx_bytes; - - - // Check the number of bytes to be transmitted are allowed - if (len > max_packet_size) { - ret = -EMSGSIZE; - goto done; - } - - // Allocate a temporary buffer for the bytes to be transmitted - tx_bytes = kmalloc(len, GFP_KERNEL); - if(tx_bytes == NULL) { - ret = -ENOMEM; - goto done; - } - - // Copy from userspace to temporary buffer in kernel space - if(copy_from_user(tx_bytes, buf, len) != 0) { - ret = -EFAULT; - goto err_copy; - } - - // Lock the device for reconfiguration - if(mutex_lock_interruptible(&cc1101->device_lock) != 0) { - ret = -EBUSY; - goto err_lock; - }; - - // Set the device to idle before configuring - cc1101_idle(cc1101); - - // Apply the TX config - cc1101_config_apply_tx(cc1101); - - // Transmit bytes using the device and return the number of transmitted bytes - cc1101_tx(cc1101, tx_bytes, len); - ret = len; - - mutex_unlock(&cc1101->device_lock); -err_lock: -err_copy: - kfree(tx_bytes); -done: - return ret; -#else - return -EPERM; -#endif -} - -/* -* Add a character device for a cc1101 -*/ -int cc1101_chrdev_add_device(cc1101_t * cc1101) { - int ret; - int device_index; - - mutex_lock(&device_list_lock); - - // Search for a free minor number - for(device_index = 0; device_index < N_SPI_MINOR_NUMBERS; device_index++){ - if(device_list[device_index] == NULL){ - // Allocate the minor number - cc1101->devt = MKDEV(SPI_MAJOR_NUMBER, device_index); - - // Create a /dev/cc1101.x.x character device - if(IS_ERR(device_create(dev_class, &cc1101->spi->dev, cc1101->devt, cc1101, "cc1101.%d.%d", cc1101->spi->master->bus_num, cc1101->spi->chip_select))) { - ret = -ENODEV; - goto done; - } - - // Insert the device in the device list - device_list[device_index] = cc1101; - ret = 0; - goto done; - } - } - ret = -ENODEV; - -done: - mutex_unlock(&device_list_lock); - return ret; -} - -/* -* Remove a cc1101 character device -*/ -void cc1101_chrdev_remove_device(cc1101_t * cc1101) { - mutex_lock(&device_list_lock); - - // Destroy the character device and remove the entry from the device list - device_destroy(dev_class, cc1101->devt); - device_list[MINOR(cc1101->devt)] = NULL; - - mutex_unlock(&device_list_lock); -} - -// Chardev operation functions -static struct file_operations fops = -{ - .owner = THIS_MODULE, - .read = chrdev_read, - .write = chrdev_write, - .unlocked_ioctl = chrdev_ioctl, - .open = chrdev_open, - .release = chrdev_release, -}; - -/* -* Setup the CC1101 character device class -*/ -int cc1101_chrdev_setup(struct spi_driver* cc1101_driver) -{ - int ret; - - ret = register_chrdev(SPI_MAJOR_NUMBER, "spi", &fops); - if (ret < 0) { - goto err_register; - } - - dev_class = class_create(THIS_MODULE, "cc1101"); - if (IS_ERR(dev_class)) { - ret = PTR_ERR(dev_class); - goto err_class_create; - } - - ret = spi_register_driver(cc1101_driver); - if (ret < 0) { - goto err_register_driver; - } - - goto done; - -err_register_driver: - class_destroy(dev_class); -err_class_create: - unregister_chrdev(SPI_MAJOR_NUMBER, cc1101_driver->driver.name); -err_register: -done: - return ret; -} - -/* -* Remove the CC1101 character device class -*/ -void cc1101_chrdev_teardown(struct spi_driver* cc1101_driver) -{ - spi_unregister_driver(cc1101_driver); - class_destroy(dev_class); - unregister_chrdev(SPI_MAJOR_NUMBER, cc1101_driver->driver.name); +// SPDX-License-Identifier: GPL-2.0-only +/* +* Copyright (c) 2021 +*/ +#include +#include + +#include "cc1101_internal.h" +#include "cc1101_chrdev.h" +#include "cc1101_radio.h" +#include "cc1101_spi.h" +#include "cc1101_config.h" + +// IOCTL Definitions +#define CC1101_BASE 'c' +// Get the driver IOCTL version +#define CC1101_GET_VERSION _IOR(CC1101_BASE, 0, uint) +// Reset the device +#define CC1101_RESET _IO(CC1101_BASE, 1) +// Set TX configuration within driver +#define CC1101_SET_TX_CONF _IOW(CC1101_BASE, 2, struct cc1101_tx_config) +// Set RX configuration within driver +#define CC1101_SET_RX_CONF _IOW(CC1101_BASE, 3, struct cc1101_rx_config) +// Get TX configuration from driver +#define CC1101_GET_TX_CONF _IOR(CC1101_BASE, 4, struct cc1101_tx_config) +// Get TX configuration registers from driver +#define CC1101_GET_TX_RAW_CONF _IOR(CC1101_BASE, 5, cc1101_device_config_t) +// Get RX configuration from driver +#define CC1101_GET_RX_CONF _IOR(CC1101_BASE, 6, struct cc1101_rx_config) +// Get RX configuration registers from driver +#define CC1101_GET_RX_RAW_CONF _IOR(CC1101_BASE, 7, cc1101_device_config_t) +// Read configuration registers from hardware +#define CC1101_GET_DEV_RAW_CONF _IOR(CC1101_BASE, 8, cc1101_device_config_t) +// Get the current RSSI +#define CC1101_GET_RSSI _IOR(CC1101_BASE, 9, unsigned char) +// Get the configured maximum packet size +#define CC1101_GET_MAX_PACKET_SIZE _IOR(CC1101_BASE, 10, u32) + +#define SPI_MAJOR_NUMBER 153 +#define N_SPI_MINOR_NUMBERS 12 + +extern uint max_packet_size; +extern uint rx_fifo_size; + +static struct class *dev_class; +cc1101_t* device_list[N_SPI_MINOR_NUMBERS] = {0}; +static DEFINE_MUTEX(device_list_lock); + +/* +* Handler for open events to /dev/cc1101.x.x +* Only one handle is allowed to transmit, receive or configure the device at a time +* Operations will block until /dev/cc1101.x.x is closed +*/ +static int chrdev_open(struct inode *inode, struct file *file) +{ + cc1101_t* cc1101 = NULL; + int device_index; + + // Search the device list for the cc1101 struct relating to the chardev + if(mutex_lock_interruptible(&device_list_lock) != 0) { + return -EBUSY; + } + + for(device_index = 0; device_index < N_SPI_MINOR_NUMBERS; device_index++){ + if (device_list[device_index] != NULL){ + if (inode->i_rdev == device_list[device_index]->devt) { + cc1101 = device_list[device_index]; + } + } + } + + mutex_unlock(&device_list_lock); + + // This should never occur - a chardev shouldn't be created without a CC1101 being present + if(cc1101 == NULL){ + return -ENODEV; + } + + // Once found, lock the device and save the pointer to private data for the subsequent functions + if(mutex_lock_interruptible(&cc1101->chrdev_lock) != 0) { + return -EBUSY; + }; + + file->private_data = cc1101; + return 0; +} + +/* +* Handler for close events to /dev/cc1101.x.x +* Releases the device lock obtained at open +*/ +static int chrdev_release(struct inode *inode, struct file *file) +{ + cc1101_t* cc1101 = file->private_data; + mutex_unlock(&cc1101->chrdev_lock); + file->private_data = NULL; + return 0; +} + +/* +* Handler for iotcl commands to /dev/cc1101.x.x +* IOCTLs can be used to set and get the TX and RX config, reset the device +* and retrieve the contents of the CC1101's registers +*/ +static long chrdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + cc1101_t* cc1101 = file->private_data; + spi_transaction_t rssi; + int version = DRIVER_VERSION; + int ret = 0; + + // Temporary holding variables for new TX and RX configs + cc1101_device_config_t device_config; +#ifndef RXONLY + struct cc1101_tx_config tx_config; +#endif + struct cc1101_rx_config rx_config; + + if(_IOC_TYPE(cmd) != CC1101_BASE){ + CC1101_ERROR(cc1101, "Invalid IOCTL\n"); + return -EIO; + } + + // Lock the device for reconfiguration + if(mutex_lock_interruptible(&cc1101->device_lock) != 0) { + return -EBUSY; + }; + + switch(cmd){ + + // Get the userspace API version + case CC1101_GET_VERSION: + ret = copy_to_user((unsigned char*) arg, &version, sizeof(version)); + break; + + // Reset the device and driver state + case CC1101_RESET: + CC1101_INFO(cc1101, "Reset"); + cc1101_reset(cc1101); + ret = 0; + break; + + // Set the RX config for the device + case CC1101_SET_RX_CONF: + + // Copy the config provided in userspace to the kernel + if(copy_from_user(&rx_config, (unsigned char*) arg, sizeof(rx_config)) != 0){ + CC1101_ERROR(cc1101, "Error Copying Device RX Config\n"); + ret = -EFAULT; + goto done; + } + + // Validate the provided config + if(!cc1101_config_validate_rx(cc1101, &rx_config)){ + ret = -EINVAL; + goto done; + } + + // Replace the RX FIFO with a new one based on the provided packet size and the maximum number of queued packets + kfifo_free(&cc1101->rx_fifo); + if(kfifo_alloc(&cc1101->rx_fifo, rx_config.packet_length * rx_fifo_size, GFP_KERNEL) != 0) { + CC1101_ERROR(cc1101, "Failed to allocate packet FIFO memory"); + ret = -ENOMEM; + goto done; + } + + // Store the new RX config in the device struct + memcpy(&cc1101->rx_config, &rx_config, sizeof(struct cc1101_rx_config)); + + // Set the device to idle before reconfiguring + cc1101_idle(cc1101); + + // Write the configuration to the device + cc1101_config_apply_rx(cc1101); + + // Enter RX mode on the device + cc1101_rx(cc1101); + + ret = 0; + break; + + // Returns the RX config configured in the driver to userspace + case CC1101_GET_RX_CONF: + ret = copy_to_user((unsigned char*) arg, &cc1101->rx_config, sizeof(struct cc1101_rx_config)); + break; + + // Returns the register values for the RX configuration to userspace + case CC1101_GET_RX_RAW_CONF: + cc1101_config_rx_to_registers(device_config, &cc1101->rx_config); + ret = copy_to_user((unsigned char*) arg, device_config, sizeof(device_config)); + break; + + // Set the TX config to use for the next packet written to /dev/cc1101.x.x + case CC1101_SET_TX_CONF: +#ifndef RXONLY + // Copy the config provided in userspace to the kernel + if(copy_from_user(&tx_config, (unsigned char*) arg, sizeof(tx_config)) != 0){ + CC1101_ERROR(cc1101, "Error Copying Device TX Config\n"); + ret = -EFAULT; + goto done; + } + + // Validate the provided config + if(!cc1101_config_validate_tx(cc1101, &tx_config)){ + ret = -EINVAL; + goto done; + } + + // Store the new TX config in the device struct + memcpy(&cc1101->tx_config, &tx_config, sizeof(struct cc1101_tx_config)); + + ret = 0; +#else + ret = -EPERM; +#endif + break; + + // Returns the TX config configured in the driver to userspace + case CC1101_GET_TX_CONF: +#ifndef RXONLY + ret = copy_to_user((unsigned char*) arg, &cc1101->tx_config, sizeof(struct cc1101_tx_config)); +#else + ret = -EPERM; +#endif + break; + + // Returns the register values for the TX configuration to userspace + case CC1101_GET_TX_RAW_CONF: +#ifndef RXONLY + cc1101_config_tx_to_registers(device_config, &cc1101->tx_config); + ret = copy_to_user((unsigned char*) arg, device_config, sizeof(device_config)); +#else + ret = -EPERM; +#endif + break; + + // Reads the current state of the CC1101's registers and returns them to userspace + case CC1101_GET_DEV_RAW_CONF: + cc1101_spi_read_config_registers(cc1101, device_config, sizeof(device_config)); + ret = copy_to_user((unsigned char*) arg, device_config, sizeof(device_config)); + break; + + case CC1101_GET_RSSI: + rssi = cc1101_spi_read_status_register_once(cc1101, RSSI); + ret = copy_to_user((unsigned char*) arg, &rssi.data, sizeof(rssi.data)); + break; + + case CC1101_GET_MAX_PACKET_SIZE: + ret = copy_to_user((unsigned char*) arg, &max_packet_size, sizeof(max_packet_size)); + break; + + default: + CC1101_ERROR(cc1101, "Unknown Command %d, %zu, %zu\n", cmd, sizeof(struct cc1101_rx_config), sizeof(struct cc1101_tx_config)); + ret = -EIO; + break; + } + +done: + mutex_unlock(&cc1101->device_lock); + return ret; +} + +/* +* Handler for read events to /dev/cc1101.x.x +* A read request will return one packet from the receive buffer, if present +*/ +static ssize_t chrdev_read(struct file *file, char __user *buf, size_t len, loff_t *off) +{ + cc1101_t* cc1101 = file->private_data; + ssize_t ret; + unsigned int out_bytes; + + // Check a RX config has been set and that the out buffer is the correct size + if (cc1101->rx_config.packet_length == 0 || len != cc1101->rx_config.packet_length) { + return -EMSGSIZE; + } + + if(mutex_lock_interruptible(&cc1101->device_lock) != 0) { + return -EBUSY; + }; + + // Check there is at least one packet in the RX FIFO + if (kfifo_len(&cc1101->rx_fifo) < cc1101->rx_config.packet_length) { + ret = -ENOMSG; + goto done; + } + + // Copy the packet out to userspace + if (kfifo_to_user(&cc1101->rx_fifo, buf, len, &out_bytes) != 0) { + ret = -EFAULT; + goto done; + } + + // Check the number of bytes copied out matches the expected number + if (out_bytes == cc1101->rx_config.packet_length) { + ret = out_bytes; + } + else { + ret = -EFAULT; + } + +done: + mutex_unlock(&cc1101->device_lock); + return ret; +} + +/* +* Handler for write events to /dev/cc1101.x.x +* Written bytes are transmitted by the CC1101 according the TX config +*/ +static ssize_t chrdev_write(struct file *file, const char __user *buf, size_t len, loff_t *off) +{ +#ifndef RXONLY + cc1101_t* cc1101 = file->private_data; + ssize_t ret; + unsigned char *tx_bytes; + + + // Check the number of bytes to be transmitted are allowed + if (len > max_packet_size) { + ret = -EMSGSIZE; + goto done; + } + + // Allocate a temporary buffer for the bytes to be transmitted + tx_bytes = kmalloc(len, GFP_KERNEL); + if(tx_bytes == NULL) { + ret = -ENOMEM; + goto done; + } + + // Copy from userspace to temporary buffer in kernel space + if(copy_from_user(tx_bytes, buf, len) != 0) { + ret = -EFAULT; + goto err_copy; + } + + // Lock the device for reconfiguration + if(mutex_lock_interruptible(&cc1101->device_lock) != 0) { + ret = -EBUSY; + goto err_lock; + }; + + // Set the device to idle before configuring + cc1101_idle(cc1101); + + // Apply the TX config + cc1101_config_apply_tx(cc1101); + + // Transmit bytes using the device and return the number of transmitted bytes + cc1101_tx(cc1101, tx_bytes, len); + ret = len; + + mutex_unlock(&cc1101->device_lock); +err_lock: +err_copy: + kfree(tx_bytes); +done: + return ret; +#else + return -EPERM; +#endif +} + +/* +* Add a character device for a cc1101 +*/ +int cc1101_chrdev_add_device(cc1101_t * cc1101) { + int ret; + int device_index; + + mutex_lock(&device_list_lock); + + // Search for a free minor number + for(device_index = 0; device_index < N_SPI_MINOR_NUMBERS; device_index++){ + if(device_list[device_index] == NULL){ + // Allocate the minor number + cc1101->devt = MKDEV(SPI_MAJOR_NUMBER, device_index); + + // Create a /dev/cc1101.x.x character device + if(IS_ERR(device_create(dev_class, &cc1101->spi->dev, cc1101->devt, cc1101, "cc1101.%d.%d", cc1101->spi->master->bus_num, cc1101->spi->chip_select))) { + ret = -ENODEV; + goto done; + } + + // Insert the device in the device list + device_list[device_index] = cc1101; + ret = 0; + goto done; + } + } + ret = -ENODEV; + +done: + mutex_unlock(&device_list_lock); + return ret; +} + +/* +* Remove a cc1101 character device +*/ +void cc1101_chrdev_remove_device(cc1101_t * cc1101) { + mutex_lock(&device_list_lock); + + // Destroy the character device and remove the entry from the device list + device_destroy(dev_class, cc1101->devt); + device_list[MINOR(cc1101->devt)] = NULL; + + mutex_unlock(&device_list_lock); +} + +// Chardev operation functions +static struct file_operations fops = +{ + .owner = THIS_MODULE, + .read = chrdev_read, + .write = chrdev_write, + .unlocked_ioctl = chrdev_ioctl, + .open = chrdev_open, + .release = chrdev_release, +}; + +/* +* Setup the CC1101 character device class +*/ +int cc1101_chrdev_setup(struct spi_driver* cc1101_driver) +{ + int ret; + + ret = register_chrdev(SPI_MAJOR_NUMBER, "spi", &fops); + if (ret < 0) { + goto err_register; + } + + dev_class = class_create(THIS_MODULE, "cc1101"); + if (IS_ERR(dev_class)) { + ret = PTR_ERR(dev_class); + goto err_class_create; + } + + ret = spi_register_driver(cc1101_driver); + if (ret < 0) { + goto err_register_driver; + } + + goto done; + +err_register_driver: + class_destroy(dev_class); +err_class_create: + unregister_chrdev(SPI_MAJOR_NUMBER, cc1101_driver->driver.name); +err_register: +done: + return ret; +} + +/* +* Remove the CC1101 character device class +*/ +void cc1101_chrdev_teardown(struct spi_driver* cc1101_driver) +{ + spi_unregister_driver(cc1101_driver); + class_destroy(dev_class); + unregister_chrdev(SPI_MAJOR_NUMBER, cc1101_driver->driver.name); } \ No newline at end of file diff --git a/cc1101_chrdev.h b/cc1101_chrdev.h index be190d2..9360142 100644 --- a/cc1101_chrdev.h +++ b/cc1101_chrdev.h @@ -1,15 +1,15 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* -* Copyright (c) 2021 -*/ -#ifndef CC1101_CHRDEV_H -#define CC1101_CHRDEV_H - -#include - -int cc1101_chrdev_setup(struct spi_driver *cc1101_driver); -void cc1101_chrdev_teardown(struct spi_driver *cc1101_driver); -int cc1101_chrdev_add_device(cc1101_t *cc1101); -void cc1101_chrdev_remove_device(cc1101_t *cc1101); - +// SPDX-License-Identifier: GPL-2.0-only +/* +* Copyright (c) 2021 +*/ +#ifndef CC1101_CHRDEV_H +#define CC1101_CHRDEV_H + +#include + +int cc1101_chrdev_setup(struct spi_driver *cc1101_driver); +void cc1101_chrdev_teardown(struct spi_driver *cc1101_driver); +int cc1101_chrdev_add_device(cc1101_t *cc1101); +void cc1101_chrdev_remove_device(cc1101_t *cc1101); + #endif \ No newline at end of file diff --git a/cc1101_config.c b/cc1101_config.c index b8eb49b..e4c2701 100644 --- a/cc1101_config.c +++ b/cc1101_config.c @@ -1,573 +1,573 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* -* Copyright (c) 2021 -*/ -#include "cc1101_config.h" - -#include "cc1101_spi.h" - -extern uint max_packet_size; - -//0x@VH@, @<<@// @RN@@<<@ @Rd@ -unsigned char DEFAULT_CONFIG[] = { - 0x29, // IOCFG2 GDO2 Output Pin Configuration - 0x2E, // IOCFG1 GDO1 Output Pin Configuration - 0x3F, // IOCFG0 GDO0 Output Pin Configuration - 0x47, // FIFOTHR RX FIFO and TX FIFO Thresholds - 0xD3, // SYNC1 Sync Word, High Byte - 0x91, // SYNC0 Sync Word, Low Byte - 0xFF, // PKTLEN Packet Length - 0x04, // PKTCTRL1 Packet Automation Control - 0x45, // PKTCTRL0 Packet Automation Control - 0x00, // ADDR Device Address - 0x00, // CHANNR Channel Number - 0x0F, // FSCTRL1 Frequency Synthesizer Control - 0x00, // FSCTRL0 Frequency Synthesizer Control - 0x1E, // FREQ2 Frequency Control Word, High Byte - 0xC4, // FREQ1 Frequency Control Word, Middle Byte - 0xEC, // FREQ0 Frequency Control Word, Low Byte - 0x8C, // MDMCFG4 Modem Configuration - 0x22, // MDMCFG3 Modem Configuration - 0x02, // MDMCFG2 Modem Configuration - 0x22, // MDMCFG1 Modem Configuration - 0xF8, // MDMCFG0 Modem Configuration - 0x47, // DEVIATN Modem Deviation Setting - 0x07, // MCSM2 Main Radio Control State Machine Configuration - 0x30, // MCSM1 Main Radio Control State Machine Configuration - 0x04, // MCSM0 Main Radio Control State Machine Configuration - 0x36, // FOCCFG Frequency Offset Compensation Configuration - 0x6C, // BSCFG Bit Synchronization Configuration - 0x03, // AGCCTRL2 AGC Control - 0x40, // AGCCTRL1 AGC Control - 0x91, // AGCCTRL0 AGC Control - 0x87, // WOREVT1 High Byte Event0 Timeout - 0x6B, // WOREVT0 Low Byte Event0 Timeout - 0xF8, // WORCTRL Wake On Radio Control - 0x56, // FREND1 Front End RX Configuration - 0x10, // FREND0 Front End TX Configuration - 0xEA, // FSCAL3 Frequency Synthesizer Calibration - 0x2A, // FSCAL2 Frequency Synthesizer Calibration - 0x00, // FSCAL1 Frequency Synthesizer Calibration - 0x1F, // FSCAL0 Frequency Synthesizer Calibration - 0x41, // RCCTRL1 RC Oscillator Configuration - 0x00, // RCCTRL0 RC Oscillator Configuration - 0x59, // FSTEST Frequency Synthesizer Calibration Control - 0x7F, // PTEST Production Test - 0x3F, // AGCTEST AGC Test - 0x81, // TEST2 Various Test Settings - 0x35, // TEST1 Various Test Settings - 0x0B, // TEST0 Various Test Settings -}; - -/* -* Checks if a baud rate is valid for a given modulation -* -* Arguments: -* baud_rate - baud rate value (e, m) -* modulation - modulation mode -* -* Returns: -* 1 - Valid -* 0 - Invalid -* -* Min/Max Values (Page 8 - Table 3) -* -* Min: -* e m -* 0.6 - 0x04 0x84 -> 0.601292 -* 26 - 0x0a 0x07 -> 26.0849 -* -* Max: -* e m -* 250 - 0x0d 0x3b -> 249.938965 -* 300 - 0x0d 0x7a -> 299.926758 -* 500 - 0x0e 0x3b -> 499.87793 -*/ - -#define BAUD_RATE_0_6 0x0484 -#define BAUD_RATE_26 0x0a07 -#define BAUD_RATE_250 0x0d3b -#define BAUD_RATE_300 0x0d7a -#define BAUD_RATE_500 0x0e3b - -int validate_baud_rate(unsigned short baud_rate, unsigned char modulation) { - unsigned short min = 0; - unsigned short max = 0; - - switch(modulation) { - case MOD_2FSK: - min = BAUD_RATE_0_6; - max = BAUD_RATE_500; - break; - case MOD_GFSK: - case MOD_OOK: - min = BAUD_RATE_0_6; - max = BAUD_RATE_250; - break; - case MOD_4FSK: - min = BAUD_RATE_0_6; - max = BAUD_RATE_300; - break; - case MOD_MSK: - min = BAUD_RATE_26; - max = BAUD_RATE_500; - break; - } - - if(baud_rate < min || baud_rate > max){ - return 0; - } - else { - return 1; - } -} - -/* -* Validates a common config struct -* -* Arguments: -* cc1101: cc1101 device (only used for error printing) -* config: a Common config struct -* -* Returns: -* 1 - Valid Config -* 0 - Invalid config -*/ -int cc1101_config_validate_common(cc1101_t *cc1101, const struct cc1101_common_config *config) { - - unsigned short baud_rate = config->baud_rate_exponent << 8 | config->baud_rate_mantissa; - - /* Frequency -> Multiplier Formula: - * - * multiplier = (freq * 2**16) / XTAL_FREQUENCY - * - * e.g - * 756184 = (300 * 2**16) / 26 - * - * Valid ranges - 300-348 MHz, 387-464 MHz, 779-928 MHz - * - * 299.999756 -> 756184 - * 347.999939 -> 877174 - * 386.999939 -> 975478 - * 463.999786 -> 1169564 - * 778.999878 -> 1963559 - * 928.000000 -> 2339131 - * - */ - - if (!( - (config->frequency >= 756184 && config->frequency <= 877174) || - (config->frequency >= 975478 && config->frequency <= 1169564) || - (config->frequency >= 1963559 && config->frequency <= 2339131) - )){ - - CC1101_ERROR(cc1101, "Invalid Frequency - %X", config->frequency); - return 0; - } - - if(validate_baud_rate(baud_rate, config->modulation) == 0){ - CC1101_ERROR(cc1101, "Invalid Baud Rate - E:%02x M:%02x", config->baud_rate_exponent, config->baud_rate_mantissa); - return 0; - } - - // Sync word is allowed to be any 16 bit value, or the same 16-bit value twice - if(config->sync_word > 0xFFFF) { - if((config->sync_word & 0xFFFF) != (config->sync_word >> 16)) { - CC1101_ERROR(cc1101, "Invalid Sync Word - %08x", config->sync_word); - return 0; - } - } - - if(config->deviation_exponent > 0x07 || config->deviation_mantissa > 0x07){ - CC1101_ERROR(cc1101, "Invalid Deviation - E: %02x M: %02x", config->deviation_exponent, config->deviation_mantissa); - return 0; - } - - return 1; -} - -#ifndef RXONLY -/* -* Validates a TX config struct -* -* Arguments: -* cc1101: cc1101 device (only used for error printing) -* tx_config: a TX config struct -* -* Returns: -* 1 - Valid Config -* 0 - Invalid config -*/ -int cc1101_config_validate_tx(cc1101_t *cc1101, const struct cc1101_tx_config *tx_config) { - - // Validate the common configuration - if (!cc1101_config_validate_common(cc1101, (struct cc1101_common_config *)tx_config)) { - return 0; - } - - // No additional validation for the TX config. Any byte is valid for tx_power - - return 1; -} - -/* -* Converts a TX config to a set of CC1101 configuration registers that can be written to the device -* -* Arguments: -* config: pointer to a byte array which will contain the configuration register values -* tx_config: a TX config struct -* -*/ -void cc1101_config_tx_to_registers(unsigned char *config, const struct cc1101_tx_config *tx_config) { - // Copy the default config - memcpy(config, &DEFAULT_CONFIG, sizeof(cc1101_device_config_t)); - - // Standard Configuration - config[IOCFG0] = 0x2E; // Disable GDO0 clock output - config[PKTCTRL1] = 0x00; // Disable Preamble Quality Threshold, CRC Autoflush, Status, Address Check - config[PKTCTRL0] = 0x00; // Disable Whitening, CRC. Set fixed packet length mode - config[MDMCFG1] = 0x23; // 2 Preamble bytes. FEC Disabled. Default channel spacing (199.951172 kHZ) - config[MCSM0] = 0x14; // Autocal when going from IDLE to RX or TX. PO_TIMEOUT 2.3-2.4us. Disable radio pin control, XOSC_FORCE - config[TEST0] = 0x09; // Disable VCO Selection calibration - config[FSCAL3] = 0xEA; // Smart RF Studio value - config[FSCAL2] = 0x2A; // Smart RF Studio value - config[FSCAL1] = 0x00; // Smart RF Studio value - config[FSCAL0] = 0x1F; // Smart RF Studio value - - // User Configuration - - // Shift frequency multiplier across registers - config[FREQ2] = tx_config->common.frequency >> 16; - config[FREQ1] = tx_config->common.frequency >> 8; - config[FREQ0] = tx_config->common.frequency & 0xFF; - - // Set baud rate exponent. RX bandwidth exponent and mantissa set to 0 (not used in TX) - config[MDMCFG4] = tx_config->common.baud_rate_exponent; - config[MDMCFG3] = tx_config->common.baud_rate_mantissa; - config[MDMCFG2] = cc1101_get_mdmcfg2(&tx_config->common, NULL); - - config[SYNC1] = (tx_config->common.sync_word & 0xFF00) >> 8; - config[SYNC0] = tx_config->common.sync_word & 0xFF; - - config[DEVIATN] = tx_config->common.deviation_exponent << 4 | tx_config->common.deviation_mantissa; - - if(tx_config->common.modulation == MOD_OOK) { - config[FREND0] = 0x11; // PATABLE position 1 is used for OOK on, position 0 for off (default 0) - } - else { - config[FREND0] = 0x10; // PATABLE position 0 for all other modulations (i.e disable power ramping) - } -} - -/* -* Apply a stored TX config to the hardware -* -* Arguments: -* cc1101: cc1101 device -* -*/ -void cc1101_config_apply_tx(cc1101_t *cc1101) { - cc1101_device_config_t device_config; - cc1101_patable_t patable = {0}; - - // Convert the configuration to a set of register values - cc1101_config_tx_to_registers(device_config, &cc1101->tx_config); - - // Set the PATABLE value - if(cc1101->tx_config.common.modulation == MOD_OOK) { - // OOK uses PATABLE[0] for off power and PATABLE[1] for on power - patable[1] = cc1101->tx_config.tx_power; - } - else { - patable[0] = cc1101->tx_config.tx_power; - } - - // Write the registers and PATABLE to the device - cc1101_spi_write_config_registers(cc1101, device_config, sizeof(cc1101_device_config_t)); - cc1101_spi_write_patable(cc1101, patable, sizeof(patable)); -} -#endif - -/* -* Validates an RX config struct -* -* Arguments: -* cc1101: cc1101 device (only used for error printing) -* rx_config: an RX config struct -* -* Returns: -* 1 - Valid Config -* 0 - Invalid config -*/ -int cc1101_config_validate_rx(cc1101_t *cc1101, const struct cc1101_rx_config *rx_config) { - - // Validate the common configuration - if (!cc1101_config_validate_common(cc1101, (struct cc1101_common_config *)rx_config)) { - return 0; - } - - switch(rx_config->max_lna_gain){ - case 0: - case 3: - case 6: - case 7: - case 9: - case 12: - case 15: - case 17: - break; - default: - CC1101_ERROR(cc1101, "Invalid Max LNA Gain %d dB", rx_config->max_lna_gain); - return 0; - } - - switch(rx_config->max_dvga_gain){ - case 0: - case 6: - case 12: - case 18: - break; - default: - CC1101_ERROR(cc1101, "Invalid Max DVGA Gain %d dB", rx_config->max_dvga_gain); - return 0; - } - - switch(rx_config->magn_target){ - case 24: - case 27: - case 30: - case 33: - case 36: - case 38: - case 40: - case 42: - break; - default: - CC1101_ERROR(cc1101, "Invalid Channel Filter Target Amplitude %d dB", rx_config->magn_target); - return 0; - } - - if(rx_config->carrier_sense_mode == CS_DISABLED){ - // Do nothing - } - else if(rx_config->carrier_sense_mode == CS_ABSOLUTE){ - // Absolute carrier sense threshold must be between -7 dB and 7 dB - if(rx_config->carrier_sense < -7 || rx_config->carrier_sense > 7){ - CC1101_ERROR(cc1101, "Invalid Absolute Carrier Sense Threshold %d dB", rx_config->carrier_sense); - return 0; - } - } - else if (rx_config->carrier_sense_mode == CS_RELATIVE){ - // Relative carrier sense threshold must be either 6, 10 or 14 dB - switch(rx_config->carrier_sense) { - case 6: - case 10: - case 14: - break; - default: - CC1101_ERROR(cc1101, "Invalid Relative Carrier Sense Threshold %d dB", rx_config->carrier_sense); - return 0; - } - } - else { - CC1101_ERROR(cc1101, "Invalid Carrier Sense Mode %d", rx_config->carrier_sense_mode); - return 0; - } - - - // Validate the packet length value provided from userspace - if(rx_config->packet_length == 0 || rx_config->packet_length > max_packet_size) { - CC1101_ERROR(cc1101, "Invalid Receive Packet Length %d", rx_config->packet_length); - return 0; - } - - // Validate Bandwidth - if(rx_config->bandwidth_exponent > 3 || rx_config->bandwidth_mantissa > 3){ - CC1101_ERROR(cc1101, "Invalid Deviation - E: %02x M: %02x", rx_config->bandwidth_exponent, rx_config->bandwidth_mantissa); - return 0; - } - - return 1; -} - -/* -* Converts an RX config to a set of CC1101 configuration registers that can be written to the device -* -* Arguments: -* config: pointer to a byte array which will contain the configuration register values -* rx_config: an RX config struct -* -*/ -void cc1101_config_rx_to_registers(unsigned char *config, const struct cc1101_rx_config *rx_config) { - // Copy the default config - memcpy(config, &DEFAULT_CONFIG, sizeof(cc1101_device_config_t)); - - // Standard Configuration - config[IOCFG2] = 0x00; // Raise GDO2 on RX FIFO threshold - config[IOCFG0] = 0x2E; // Disable GDO0 clock output - config[PKTCTRL1] = 0x00; // Disable Preamble Quality Threshold, CRC Autoflush, Status, Address Check - config[PKTCTRL0] = 0x02; // Disable Whitening, CRC. Set infinite packet length mode - config[MDMCFG1] = 0x23; // 2 Preamble bytes. FEC Disabled. Default channel spacing (199.951172 kHZ) - config[MCSM0] = 0x14; // Autocal when going from IDLE to RX or TX. PO_TIMEOUT 2.3-2.4us. Disable radio pin control, XOSC_FORCE - config[TEST0] = 0x09; // Disable VCO Selection calibration - config[FSCAL3] = 0xEA; // Smart RF Studio value - config[FSCAL2] = 0x2A; // Smart RF Studio value - config[FSCAL1] = 0x00; // Smart RF Studio value - config[FSCAL0] = 0x1F; // Smart RF Studio value - - // User Configuration - config[FREQ2] = rx_config->common.frequency >> 16; - config[FREQ1] = rx_config->common.frequency >> 8; - config[FREQ0] = rx_config->common.frequency & 0xFF; - - config[MDMCFG4] = rx_config->bandwidth_exponent << 6 | rx_config->bandwidth_mantissa << 4 | rx_config->common.baud_rate_exponent; - config[MDMCFG3] = rx_config->common.baud_rate_mantissa; - config[MDMCFG2] = cc1101_get_mdmcfg2(&rx_config->common, rx_config); - - config[SYNC1] = (rx_config->common.sync_word & 0xFF00) >> 8; - config[SYNC0] = rx_config->common.sync_word & 0xFF; - - config[DEVIATN] = rx_config->common.deviation_exponent << 4 | rx_config->common.deviation_mantissa; - - // Set the MAGN_TARGET from the config - switch(rx_config->magn_target){ - case 27: - config[AGCCTRL2] = 1; - break; - case 30: - config[AGCCTRL2] = 2; - break; - case 33: - config[AGCCTRL2] = 3; - break; - case 36: - config[AGCCTRL2] = 4; - break; - case 38: - config[AGCCTRL2] = 5; - break; - case 40: - config[AGCCTRL2] = 6; - break; - case 42: - config[AGCCTRL2] = 7; - break; - default: - config[AGCCTRL2] = 0; - } - - // Set the MAX_DVGA_GAIN from the config - switch(rx_config->max_dvga_gain){ - case 6: - config[AGCCTRL2] |= (1 << 6); - break; - case 12: - config[AGCCTRL2] |= (2 << 6); - break; - case 18: - config[AGCCTRL2] |= (3 << 6); - break; - default: - break; - } - - // Set the MAX_LNA_GAIN from the config - switch(rx_config->max_lna_gain){ - case 3: - config[AGCCTRL2] |= (1 << 3); - break; - case 6: - config[AGCCTRL2] |= (2 << 3); - break; - case 7: - config[AGCCTRL2] |= (3 << 3); - break; - case 9: - config[AGCCTRL2] |= (4 << 3); - break; - case 12: - config[AGCCTRL2] |= (5 << 3); - break; - case 15: - config[AGCCTRL2] |= (6 << 3); - break; - case 17: - config[AGCCTRL2] |= (7 << 3); - break; - default: - break; - } - - // Set the CARRIER_SENSE_REL_THR and CARRIER_SENSE_ABS_THR based on the config value - // Set AGC_LNA_PRIORITY to the default value - if(rx_config->carrier_sense_mode == CS_ABSOLUTE){ - config[AGCCTRL1] = 0x40 | ((char) rx_config->carrier_sense & 0x0F); - } - else if(rx_config->carrier_sense_mode == CS_RELATIVE){ - switch(rx_config->carrier_sense) { - case 6: - config[AGCCTRL1] = 0x58; // Default AGC_LNA_PRIORITY. CARRIER_SENSE_REL_THR 6dB increase in RSSI. CS absolute threshold disabled - break; - case 10: - config[AGCCTRL1] = 0x68; // Default AGC_LNA_PRIORITY. CARRIER_SENSE_REL_THR 10dB increase in RSSI. CS absolute threshold disabled - break; - default: - config[AGCCTRL1] = 0x78; // Default AGC_LNA_PRIORITY. CARRIER_SENSE_REL_THR 14dB increase in RSSI. CS absolute threshold disabled - break; - } - } -} - -/* -* Apply a stored RX config to the hardware -* -* Arguments: -* cc1101: cc1101 device -* -*/ -void cc1101_config_apply_rx(cc1101_t *cc1101) { - cc1101_device_config_t device_config; - - // Convert the configuration to a set of register values - cc1101_config_rx_to_registers(device_config, &cc1101->rx_config); - - // Write the registers to the device - cc1101_spi_write_config_registers(cc1101, device_config, sizeof(cc1101_device_config_t)); -} - -/* -* Get the value for the MDMCFG2 register from common config. This value is needed by the RX interrupt handler -* -* Arguments: -* config: common config for TX or RX -* rx_config: an RX configuration. Can be NULL for TX -*/ -unsigned char cc1101_get_mdmcfg2(const struct cc1101_common_config *config, const struct cc1101_rx_config *rx_config) { - - unsigned char value = (config->modulation << 4) & 0x70; // Enable DC Filter. Modulation from config. - - if(rx_config == NULL || rx_config->carrier_sense_mode == CS_DISABLED) { - if(config->sync_word > 0 && config->sync_word <= 0xFFFF) { - value = value | 0x02; //Manchester encoding disabled. 16 sync bits, carrier sense disabled - } - else if(config->sync_word > 0xFFFF) { - value = value | 0x03; //Manchester encoding disabled. 32 sync bits, carrier sense disabled - } - } - else { - if(config->sync_word == 0){ - value = value | 0x04; //Manchester encoding disabled. no sync word, carrier sense enabled - } - else if(config->sync_word > 0 && config->sync_word <= 0xFFFF) { - value = value | 0x06; //Manchester encoding disabled. 16 sync bits, carrier sense enabled - } - else { - value = value | 0x07; //Manchester encoding disabled. 32 sync bits, carrier sense enabled - } - } - - return value; -} - - - +// SPDX-License-Identifier: GPL-2.0-only +/* +* Copyright (c) 2021 +*/ +#include "cc1101_config.h" + +#include "cc1101_spi.h" + +extern uint max_packet_size; + +//0x@VH@, @<<@// @RN@@<<@ @Rd@ +unsigned char DEFAULT_CONFIG[] = { + 0x29, // IOCFG2 GDO2 Output Pin Configuration + 0x2E, // IOCFG1 GDO1 Output Pin Configuration + 0x3F, // IOCFG0 GDO0 Output Pin Configuration + 0x47, // FIFOTHR RX FIFO and TX FIFO Thresholds + 0xD3, // SYNC1 Sync Word, High Byte + 0x91, // SYNC0 Sync Word, Low Byte + 0xFF, // PKTLEN Packet Length + 0x04, // PKTCTRL1 Packet Automation Control + 0x45, // PKTCTRL0 Packet Automation Control + 0x00, // ADDR Device Address + 0x00, // CHANNR Channel Number + 0x0F, // FSCTRL1 Frequency Synthesizer Control + 0x00, // FSCTRL0 Frequency Synthesizer Control + 0x1E, // FREQ2 Frequency Control Word, High Byte + 0xC4, // FREQ1 Frequency Control Word, Middle Byte + 0xEC, // FREQ0 Frequency Control Word, Low Byte + 0x8C, // MDMCFG4 Modem Configuration + 0x22, // MDMCFG3 Modem Configuration + 0x02, // MDMCFG2 Modem Configuration + 0x22, // MDMCFG1 Modem Configuration + 0xF8, // MDMCFG0 Modem Configuration + 0x47, // DEVIATN Modem Deviation Setting + 0x07, // MCSM2 Main Radio Control State Machine Configuration + 0x30, // MCSM1 Main Radio Control State Machine Configuration + 0x04, // MCSM0 Main Radio Control State Machine Configuration + 0x36, // FOCCFG Frequency Offset Compensation Configuration + 0x6C, // BSCFG Bit Synchronization Configuration + 0x03, // AGCCTRL2 AGC Control + 0x40, // AGCCTRL1 AGC Control + 0x91, // AGCCTRL0 AGC Control + 0x87, // WOREVT1 High Byte Event0 Timeout + 0x6B, // WOREVT0 Low Byte Event0 Timeout + 0xF8, // WORCTRL Wake On Radio Control + 0x56, // FREND1 Front End RX Configuration + 0x10, // FREND0 Front End TX Configuration + 0xEA, // FSCAL3 Frequency Synthesizer Calibration + 0x2A, // FSCAL2 Frequency Synthesizer Calibration + 0x00, // FSCAL1 Frequency Synthesizer Calibration + 0x1F, // FSCAL0 Frequency Synthesizer Calibration + 0x41, // RCCTRL1 RC Oscillator Configuration + 0x00, // RCCTRL0 RC Oscillator Configuration + 0x59, // FSTEST Frequency Synthesizer Calibration Control + 0x7F, // PTEST Production Test + 0x3F, // AGCTEST AGC Test + 0x81, // TEST2 Various Test Settings + 0x35, // TEST1 Various Test Settings + 0x0B, // TEST0 Various Test Settings +}; + +/* +* Checks if a baud rate is valid for a given modulation +* +* Arguments: +* baud_rate - baud rate value (e, m) +* modulation - modulation mode +* +* Returns: +* 1 - Valid +* 0 - Invalid +* +* Min/Max Values (Page 8 - Table 3) +* +* Min: +* e m +* 0.6 - 0x04 0x84 -> 0.601292 +* 26 - 0x0a 0x07 -> 26.0849 +* +* Max: +* e m +* 250 - 0x0d 0x3b -> 249.938965 +* 300 - 0x0d 0x7a -> 299.926758 +* 500 - 0x0e 0x3b -> 499.87793 +*/ + +#define BAUD_RATE_0_6 0x0484 +#define BAUD_RATE_26 0x0a07 +#define BAUD_RATE_250 0x0d3b +#define BAUD_RATE_300 0x0d7a +#define BAUD_RATE_500 0x0e3b + +int validate_baud_rate(unsigned short baud_rate, unsigned char modulation) { + unsigned short min = 0; + unsigned short max = 0; + + switch(modulation) { + case MOD_2FSK: + min = BAUD_RATE_0_6; + max = BAUD_RATE_500; + break; + case MOD_GFSK: + case MOD_OOK: + min = BAUD_RATE_0_6; + max = BAUD_RATE_250; + break; + case MOD_4FSK: + min = BAUD_RATE_0_6; + max = BAUD_RATE_300; + break; + case MOD_MSK: + min = BAUD_RATE_26; + max = BAUD_RATE_500; + break; + } + + if(baud_rate < min || baud_rate > max){ + return 0; + } + else { + return 1; + } +} + +/* +* Validates a common config struct +* +* Arguments: +* cc1101: cc1101 device (only used for error printing) +* config: a Common config struct +* +* Returns: +* 1 - Valid Config +* 0 - Invalid config +*/ +int cc1101_config_validate_common(cc1101_t *cc1101, const struct cc1101_common_config *config) { + + unsigned short baud_rate = config->baud_rate_exponent << 8 | config->baud_rate_mantissa; + + /* Frequency -> Multiplier Formula: + * + * multiplier = (freq * 2**16) / XTAL_FREQUENCY + * + * e.g + * 756184 = (300 * 2**16) / 26 + * + * Valid ranges - 300-348 MHz, 387-464 MHz, 779-928 MHz + * + * 299.999756 -> 756184 + * 347.999939 -> 877174 + * 386.999939 -> 975478 + * 463.999786 -> 1169564 + * 778.999878 -> 1963559 + * 928.000000 -> 2339131 + * + */ + + if (!( + (config->frequency >= 756184 && config->frequency <= 877174) || + (config->frequency >= 975478 && config->frequency <= 1169564) || + (config->frequency >= 1963559 && config->frequency <= 2339131) + )){ + + CC1101_ERROR(cc1101, "Invalid Frequency - %X", config->frequency); + return 0; + } + + if(validate_baud_rate(baud_rate, config->modulation) == 0){ + CC1101_ERROR(cc1101, "Invalid Baud Rate - E:%02x M:%02x", config->baud_rate_exponent, config->baud_rate_mantissa); + return 0; + } + + // Sync word is allowed to be any 16 bit value, or the same 16-bit value twice + if(config->sync_word > 0xFFFF) { + if((config->sync_word & 0xFFFF) != (config->sync_word >> 16)) { + CC1101_ERROR(cc1101, "Invalid Sync Word - %08x", config->sync_word); + return 0; + } + } + + if(config->deviation_exponent > 0x07 || config->deviation_mantissa > 0x07){ + CC1101_ERROR(cc1101, "Invalid Deviation - E: %02x M: %02x", config->deviation_exponent, config->deviation_mantissa); + return 0; + } + + return 1; +} + +#ifndef RXONLY +/* +* Validates a TX config struct +* +* Arguments: +* cc1101: cc1101 device (only used for error printing) +* tx_config: a TX config struct +* +* Returns: +* 1 - Valid Config +* 0 - Invalid config +*/ +int cc1101_config_validate_tx(cc1101_t *cc1101, const struct cc1101_tx_config *tx_config) { + + // Validate the common configuration + if (!cc1101_config_validate_common(cc1101, (struct cc1101_common_config *)tx_config)) { + return 0; + } + + // No additional validation for the TX config. Any byte is valid for tx_power + + return 1; +} + +/* +* Converts a TX config to a set of CC1101 configuration registers that can be written to the device +* +* Arguments: +* config: pointer to a byte array which will contain the configuration register values +* tx_config: a TX config struct +* +*/ +void cc1101_config_tx_to_registers(unsigned char *config, const struct cc1101_tx_config *tx_config) { + // Copy the default config + memcpy(config, &DEFAULT_CONFIG, sizeof(cc1101_device_config_t)); + + // Standard Configuration + config[IOCFG0] = 0x2E; // Disable GDO0 clock output + config[PKTCTRL1] = 0x00; // Disable Preamble Quality Threshold, CRC Autoflush, Status, Address Check + config[PKTCTRL0] = 0x00; // Disable Whitening, CRC. Set fixed packet length mode + config[MDMCFG1] = 0x23; // 2 Preamble bytes. FEC Disabled. Default channel spacing (199.951172 kHZ) + config[MCSM0] = 0x14; // Autocal when going from IDLE to RX or TX. PO_TIMEOUT 2.3-2.4us. Disable radio pin control, XOSC_FORCE + config[TEST0] = 0x09; // Disable VCO Selection calibration + config[FSCAL3] = 0xEA; // Smart RF Studio value + config[FSCAL2] = 0x2A; // Smart RF Studio value + config[FSCAL1] = 0x00; // Smart RF Studio value + config[FSCAL0] = 0x1F; // Smart RF Studio value + + // User Configuration + + // Shift frequency multiplier across registers + config[FREQ2] = tx_config->common.frequency >> 16; + config[FREQ1] = tx_config->common.frequency >> 8; + config[FREQ0] = tx_config->common.frequency & 0xFF; + + // Set baud rate exponent. RX bandwidth exponent and mantissa set to 0 (not used in TX) + config[MDMCFG4] = tx_config->common.baud_rate_exponent; + config[MDMCFG3] = tx_config->common.baud_rate_mantissa; + config[MDMCFG2] = cc1101_get_mdmcfg2(&tx_config->common, NULL); + + config[SYNC1] = (tx_config->common.sync_word & 0xFF00) >> 8; + config[SYNC0] = tx_config->common.sync_word & 0xFF; + + config[DEVIATN] = tx_config->common.deviation_exponent << 4 | tx_config->common.deviation_mantissa; + + if(tx_config->common.modulation == MOD_OOK) { + config[FREND0] = 0x11; // PATABLE position 1 is used for OOK on, position 0 for off (default 0) + } + else { + config[FREND0] = 0x10; // PATABLE position 0 for all other modulations (i.e disable power ramping) + } +} + +/* +* Apply a stored TX config to the hardware +* +* Arguments: +* cc1101: cc1101 device +* +*/ +void cc1101_config_apply_tx(cc1101_t *cc1101) { + cc1101_device_config_t device_config; + cc1101_patable_t patable = {0}; + + // Convert the configuration to a set of register values + cc1101_config_tx_to_registers(device_config, &cc1101->tx_config); + + // Set the PATABLE value + if(cc1101->tx_config.common.modulation == MOD_OOK) { + // OOK uses PATABLE[0] for off power and PATABLE[1] for on power + patable[1] = cc1101->tx_config.tx_power; + } + else { + patable[0] = cc1101->tx_config.tx_power; + } + + // Write the registers and PATABLE to the device + cc1101_spi_write_config_registers(cc1101, device_config, sizeof(cc1101_device_config_t)); + cc1101_spi_write_patable(cc1101, patable, sizeof(patable)); +} +#endif + +/* +* Validates an RX config struct +* +* Arguments: +* cc1101: cc1101 device (only used for error printing) +* rx_config: an RX config struct +* +* Returns: +* 1 - Valid Config +* 0 - Invalid config +*/ +int cc1101_config_validate_rx(cc1101_t *cc1101, const struct cc1101_rx_config *rx_config) { + + // Validate the common configuration + if (!cc1101_config_validate_common(cc1101, (struct cc1101_common_config *)rx_config)) { + return 0; + } + + switch(rx_config->max_lna_gain){ + case 0: + case 3: + case 6: + case 7: + case 9: + case 12: + case 15: + case 17: + break; + default: + CC1101_ERROR(cc1101, "Invalid Max LNA Gain %d dB", rx_config->max_lna_gain); + return 0; + } + + switch(rx_config->max_dvga_gain){ + case 0: + case 6: + case 12: + case 18: + break; + default: + CC1101_ERROR(cc1101, "Invalid Max DVGA Gain %d dB", rx_config->max_dvga_gain); + return 0; + } + + switch(rx_config->magn_target){ + case 24: + case 27: + case 30: + case 33: + case 36: + case 38: + case 40: + case 42: + break; + default: + CC1101_ERROR(cc1101, "Invalid Channel Filter Target Amplitude %d dB", rx_config->magn_target); + return 0; + } + + if(rx_config->carrier_sense_mode == CS_DISABLED){ + // Do nothing + } + else if(rx_config->carrier_sense_mode == CS_ABSOLUTE){ + // Absolute carrier sense threshold must be between -7 dB and 7 dB + if(rx_config->carrier_sense < -7 || rx_config->carrier_sense > 7){ + CC1101_ERROR(cc1101, "Invalid Absolute Carrier Sense Threshold %d dB", rx_config->carrier_sense); + return 0; + } + } + else if (rx_config->carrier_sense_mode == CS_RELATIVE){ + // Relative carrier sense threshold must be either 6, 10 or 14 dB + switch(rx_config->carrier_sense) { + case 6: + case 10: + case 14: + break; + default: + CC1101_ERROR(cc1101, "Invalid Relative Carrier Sense Threshold %d dB", rx_config->carrier_sense); + return 0; + } + } + else { + CC1101_ERROR(cc1101, "Invalid Carrier Sense Mode %d", rx_config->carrier_sense_mode); + return 0; + } + + + // Validate the packet length value provided from userspace + if(rx_config->packet_length == 0 || rx_config->packet_length > max_packet_size) { + CC1101_ERROR(cc1101, "Invalid Receive Packet Length %d", rx_config->packet_length); + return 0; + } + + // Validate Bandwidth + if(rx_config->bandwidth_exponent > 3 || rx_config->bandwidth_mantissa > 3){ + CC1101_ERROR(cc1101, "Invalid Deviation - E: %02x M: %02x", rx_config->bandwidth_exponent, rx_config->bandwidth_mantissa); + return 0; + } + + return 1; +} + +/* +* Converts an RX config to a set of CC1101 configuration registers that can be written to the device +* +* Arguments: +* config: pointer to a byte array which will contain the configuration register values +* rx_config: an RX config struct +* +*/ +void cc1101_config_rx_to_registers(unsigned char *config, const struct cc1101_rx_config *rx_config) { + // Copy the default config + memcpy(config, &DEFAULT_CONFIG, sizeof(cc1101_device_config_t)); + + // Standard Configuration + config[IOCFG2] = 0x00; // Raise GDO2 on RX FIFO threshold + config[IOCFG0] = 0x2E; // Disable GDO0 clock output + config[PKTCTRL1] = 0x00; // Disable Preamble Quality Threshold, CRC Autoflush, Status, Address Check + config[PKTCTRL0] = 0x02; // Disable Whitening, CRC. Set infinite packet length mode + config[MDMCFG1] = 0x23; // 2 Preamble bytes. FEC Disabled. Default channel spacing (199.951172 kHZ) + config[MCSM0] = 0x14; // Autocal when going from IDLE to RX or TX. PO_TIMEOUT 2.3-2.4us. Disable radio pin control, XOSC_FORCE + config[TEST0] = 0x09; // Disable VCO Selection calibration + config[FSCAL3] = 0xEA; // Smart RF Studio value + config[FSCAL2] = 0x2A; // Smart RF Studio value + config[FSCAL1] = 0x00; // Smart RF Studio value + config[FSCAL0] = 0x1F; // Smart RF Studio value + + // User Configuration + config[FREQ2] = rx_config->common.frequency >> 16; + config[FREQ1] = rx_config->common.frequency >> 8; + config[FREQ0] = rx_config->common.frequency & 0xFF; + + config[MDMCFG4] = rx_config->bandwidth_exponent << 6 | rx_config->bandwidth_mantissa << 4 | rx_config->common.baud_rate_exponent; + config[MDMCFG3] = rx_config->common.baud_rate_mantissa; + config[MDMCFG2] = cc1101_get_mdmcfg2(&rx_config->common, rx_config); + + config[SYNC1] = (rx_config->common.sync_word & 0xFF00) >> 8; + config[SYNC0] = rx_config->common.sync_word & 0xFF; + + config[DEVIATN] = rx_config->common.deviation_exponent << 4 | rx_config->common.deviation_mantissa; + + // Set the MAGN_TARGET from the config + switch(rx_config->magn_target){ + case 27: + config[AGCCTRL2] = 1; + break; + case 30: + config[AGCCTRL2] = 2; + break; + case 33: + config[AGCCTRL2] = 3; + break; + case 36: + config[AGCCTRL2] = 4; + break; + case 38: + config[AGCCTRL2] = 5; + break; + case 40: + config[AGCCTRL2] = 6; + break; + case 42: + config[AGCCTRL2] = 7; + break; + default: + config[AGCCTRL2] = 0; + } + + // Set the MAX_DVGA_GAIN from the config + switch(rx_config->max_dvga_gain){ + case 6: + config[AGCCTRL2] |= (1 << 6); + break; + case 12: + config[AGCCTRL2] |= (2 << 6); + break; + case 18: + config[AGCCTRL2] |= (3 << 6); + break; + default: + break; + } + + // Set the MAX_LNA_GAIN from the config + switch(rx_config->max_lna_gain){ + case 3: + config[AGCCTRL2] |= (1 << 3); + break; + case 6: + config[AGCCTRL2] |= (2 << 3); + break; + case 7: + config[AGCCTRL2] |= (3 << 3); + break; + case 9: + config[AGCCTRL2] |= (4 << 3); + break; + case 12: + config[AGCCTRL2] |= (5 << 3); + break; + case 15: + config[AGCCTRL2] |= (6 << 3); + break; + case 17: + config[AGCCTRL2] |= (7 << 3); + break; + default: + break; + } + + // Set the CARRIER_SENSE_REL_THR and CARRIER_SENSE_ABS_THR based on the config value + // Set AGC_LNA_PRIORITY to the default value + if(rx_config->carrier_sense_mode == CS_ABSOLUTE){ + config[AGCCTRL1] = 0x40 | ((char) rx_config->carrier_sense & 0x0F); + } + else if(rx_config->carrier_sense_mode == CS_RELATIVE){ + switch(rx_config->carrier_sense) { + case 6: + config[AGCCTRL1] = 0x58; // Default AGC_LNA_PRIORITY. CARRIER_SENSE_REL_THR 6dB increase in RSSI. CS absolute threshold disabled + break; + case 10: + config[AGCCTRL1] = 0x68; // Default AGC_LNA_PRIORITY. CARRIER_SENSE_REL_THR 10dB increase in RSSI. CS absolute threshold disabled + break; + default: + config[AGCCTRL1] = 0x78; // Default AGC_LNA_PRIORITY. CARRIER_SENSE_REL_THR 14dB increase in RSSI. CS absolute threshold disabled + break; + } + } +} + +/* +* Apply a stored RX config to the hardware +* +* Arguments: +* cc1101: cc1101 device +* +*/ +void cc1101_config_apply_rx(cc1101_t *cc1101) { + cc1101_device_config_t device_config; + + // Convert the configuration to a set of register values + cc1101_config_rx_to_registers(device_config, &cc1101->rx_config); + + // Write the registers to the device + cc1101_spi_write_config_registers(cc1101, device_config, sizeof(cc1101_device_config_t)); +} + +/* +* Get the value for the MDMCFG2 register from common config. This value is needed by the RX interrupt handler +* +* Arguments: +* config: common config for TX or RX +* rx_config: an RX configuration. Can be NULL for TX +*/ +unsigned char cc1101_get_mdmcfg2(const struct cc1101_common_config *config, const struct cc1101_rx_config *rx_config) { + + unsigned char value = (config->modulation << 4) & 0x70; // Enable DC Filter. Modulation from config. + + if(rx_config == NULL || rx_config->carrier_sense_mode == CS_DISABLED) { + if(config->sync_word > 0 && config->sync_word <= 0xFFFF) { + value = value | 0x02; //Manchester encoding disabled. 16 sync bits, carrier sense disabled + } + else if(config->sync_word > 0xFFFF) { + value = value | 0x03; //Manchester encoding disabled. 32 sync bits, carrier sense disabled + } + } + else { + if(config->sync_word == 0){ + value = value | 0x04; //Manchester encoding disabled. no sync word, carrier sense enabled + } + else if(config->sync_word > 0 && config->sync_word <= 0xFFFF) { + value = value | 0x06; //Manchester encoding disabled. 16 sync bits, carrier sense enabled + } + else { + value = value | 0x07; //Manchester encoding disabled. 32 sync bits, carrier sense enabled + } + } + + return value; +} + + + diff --git a/cc1101_config.h b/cc1101_config.h index 6a3b558..8a076ba 100644 --- a/cc1101_config.h +++ b/cc1101_config.h @@ -1,22 +1,22 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* -* Copyright (c) 2021 -*/ -#ifndef CC1101_UTILS_H -#define CC1101_UTILS_H - -#include "cc1101_internal.h" - -unsigned char cc1101_get_mdmcfg2(const struct cc1101_common_config *common_config, const struct cc1101_rx_config *rx_config); - -int cc1101_config_validate_rx(cc1101_t *cc1101, const struct cc1101_rx_config *rx_config); -void cc1101_config_apply_rx(cc1101_t *cc1101); -void cc1101_config_rx_to_registers(unsigned char *config, const struct cc1101_rx_config *rx_config); - -#ifndef RXONLY -int cc1101_config_validate_tx(cc1101_t *cc1101, const struct cc1101_tx_config *tx_config); -void cc1101_config_apply_tx(cc1101_t *cc1101); -void cc1101_config_tx_to_registers(unsigned char *config, const struct cc1101_tx_config *tx_config); -#endif - +// SPDX-License-Identifier: GPL-2.0-only +/* +* Copyright (c) 2021 +*/ +#ifndef CC1101_UTILS_H +#define CC1101_UTILS_H + +#include "cc1101_internal.h" + +unsigned char cc1101_get_mdmcfg2(const struct cc1101_common_config *common_config, const struct cc1101_rx_config *rx_config); + +int cc1101_config_validate_rx(cc1101_t *cc1101, const struct cc1101_rx_config *rx_config); +void cc1101_config_apply_rx(cc1101_t *cc1101); +void cc1101_config_rx_to_registers(unsigned char *config, const struct cc1101_rx_config *rx_config); + +#ifndef RXONLY +int cc1101_config_validate_tx(cc1101_t *cc1101, const struct cc1101_tx_config *tx_config); +void cc1101_config_apply_tx(cc1101_t *cc1101); +void cc1101_config_tx_to_registers(unsigned char *config, const struct cc1101_tx_config *tx_config); +#endif + #endif \ No newline at end of file diff --git a/cc1101_internal.h b/cc1101_internal.h index ebaa64c..4d0a30f 100644 --- a/cc1101_internal.h +++ b/cc1101_internal.h @@ -1,151 +1,151 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* -* Copyright (c) 2021 -*/ -#ifndef CC1101_INTERNAL_H -#define CC1101_INTERNAL_H - -#include -#include - -#include "cc1101.h" - -#define DRIVER_VERSION 4 - -// Configuration Registers - CC1101 Datasheet Table 43 -// Generated in SmartRF Studio with config string #define @RN@ @<<@ 0x@AH@ @<<@ // @Rd@ -#define IOCFG2 0x00 // GDO2 Output Pin Configuration -#define IOCFG1 0x01 // GDO1 Output Pin Configuration -#define IOCFG0 0x02 // GDO0 Output Pin Configuration -#define FIFOTHR 0x03 // RX FIFO and TX FIFO Thresholds -#define SYNC1 0x04 // Sync Word, High Byte -#define SYNC0 0x05 // Sync Word, Low Byte -#define PKTLEN 0x06 // Packet Length -#define PKTCTRL1 0x07 // Packet Automation Control -#define PKTCTRL0 0x08 // Packet Automation Control -#define ADDR 0x09 // Device Address -#define CHANNR 0x0A // Channel Number -#define FSCTRL1 0x0B // Frequency Synthesizer Control -#define FSCTRL0 0x0C // Frequency Synthesizer Control -#define FREQ2 0x0D // Frequency Control Word, High Byte -#define FREQ1 0x0E // Frequency Control Word, Middle Byte -#define FREQ0 0x0F // Frequency Control Word, Low Byte -#define MDMCFG4 0x10 // Modem Configuration -#define MDMCFG3 0x11 // Modem Configuration -#define MDMCFG2 0x12 // Modem Configuration -#define MDMCFG1 0x13 // Modem Configuration -#define MDMCFG0 0x14 // Modem Configuration -#define DEVIATN 0x15 // Modem Deviation Setting -#define MCSM2 0x16 // Main Radio Control State Machine Configuration -#define MCSM1 0x17 // Main Radio Control State Machine Configuration -#define MCSM0 0x18 // Main Radio Control State Machine Configuration -#define FOCCFG 0x19 // Frequency Offset Compensation Configuration -#define BSCFG 0x1A // Bit Synchronization Configuration -#define AGCCTRL2 0x1B // AGC Control -#define AGCCTRL1 0x1C // AGC Control -#define AGCCTRL0 0x1D // AGC Control -#define WOREVT1 0x1E // High Byte Event0 Timeout -#define WOREVT0 0x1F // Low Byte Event0 Timeout -#define WORCTRL 0x20 // Wake On Radio Control -#define FREND1 0x21 // Front End RX Configuration -#define FREND0 0x22 // Front End TX Configuration -#define FSCAL3 0x23 // Frequency Synthesizer Calibration -#define FSCAL2 0x24 // Frequency Synthesizer Calibration -#define FSCAL1 0x25 // Frequency Synthesizer Calibration -#define FSCAL0 0x26 // Frequency Synthesizer Calibration -#define RCCTRL1 0x27 // RC Oscillator Configuration -#define RCCTRL0 0x28 // RC Oscillator Configuration -#define FSTEST 0x29 // Frequency Synthesizer Calibration Control -#define PTEST 0x2A // Production Test -#define AGCTEST 0x2B // AGC Test -#define TEST2 0x2C // Various Test Settings -#define TEST1 0x2D // Various Test Settings -#define TEST0 0x2E // Various Test Settings - -// Command strobes - CC1101 Datasheet Table 42 -#define SRES 0x30 -#define SFSTXON 0x31 -#define SXOFF 0x32 -#define SCAL 0x33 -#define SRX 0x34 -#define STX 0x35 -#define SIDLE 0x36 -#define SAFC 0x37 -#define SWOR 0x38 -#define SPWD 0x39 -#define SFRX 0x3A -#define SFTX 0x3B -#define SWORRST 0x3C -#define SNOP 0x3D - -// Status Registers - CC1101 Datasheet Table 44 -#define PARTNUM 0x30 -#define VERSION 0x31 -#define FREQEST 0x32 -#define LQI 0x33 -#define RSSI 0x34 -#define MARCSTATE 0x35 -#define WORTIME1 0x36 -#define WORTIME0 0x37 -#define PKTSTATUS 0x38 -#define VCO_VC_DAC 0x39 -#define TXBYTES 0x3A -#define RXBYTES 0x3B -#define RCCTRL1_STATUS 0x3C -#define RCCTRL0_STATUS 0x3D - -#define PATABLE 0x3E -#define FIFO 0x3F - -#define FIFO_LEN 64 - -// State transition times - CC1101 Datasheet Page 54/55 -#define TIME_IDLE_TO_RX_NOCAL 76 // Rounded up from 75.1 -#define TIME_IDLE_TO_RX_CAL 799 -#define TIME_IDLE_TO_TX_NOCAL 76 // Rounded up from 75.2 -#define TIME_IDLE_TO_TX_CAL 799 -#define TIME_TX_TO_RX 32 // Rounded up from 31.1 - compensates for variable baud rate -#define TIME_RX_TO_TX 32 -#define TIME_TX_TO_IDLE_NOCAL 1 // Rounded up from 0.25/baud - compensates for variable baud rate -#define TIME_TX_TO_IDLE_CAL 726 // Rounded up from 725 to compensate for variable baud rate -#define TIME_RX_TO_IDLE_NOCAL 1 // Rounded up from ~0.1 -#define TIME_RX_TO_IDLE_CAL 724 -#define TIME_MANUAL_CAL 735 - -// Error macros -#ifndef CONFIG_DYNAMIC_DEBUG -extern uint debug; -#define CC1101_DEBUG(cc1101, format, ...) if (debug) dev_info(&cc1101->spi->dev, format "\n", ##__VA_ARGS__) -#else -#define CC1101_DEBUG(cc1101, format, ...) dev_dbg(&cc1101->spi->dev, format "\n", ##__VA_ARGS__) -#endif - -#define CC1101_INFO(cc1101, format, ...) dev_info(&cc1101->spi->dev, format "\n", ##__VA_ARGS__) -#define CC1101_ERROR(cc1101, format, ...) dev_err(&cc1101->spi->dev, format "\n", ##__VA_ARGS__) - -// Driver state -typedef enum { - MODE_IDLE, - MODE_TX, - MODE_RX -} cc1101_mode_t; - - -// Struct to hold per-device state -typedef struct { - struct spi_device *spi; - dev_t devt; - int irq; - struct mutex chrdev_lock; - struct mutex device_lock; - cc1101_mode_t mode; - struct cc1101_tx_config tx_config; - struct cc1101_rx_config rx_config; - unsigned char *current_packet; - u32 bytes_remaining; - DECLARE_KFIFO_PTR(rx_fifo, unsigned char); - struct timer_list rx_timeout; - struct work_struct rx_timeout_work; -} cc1101_t; - +// SPDX-License-Identifier: GPL-2.0-only +/* +* Copyright (c) 2021 +*/ +#ifndef CC1101_INTERNAL_H +#define CC1101_INTERNAL_H + +#include +#include + +#include "cc1101.h" + +#define DRIVER_VERSION 4 + +// Configuration Registers - CC1101 Datasheet Table 43 +// Generated in SmartRF Studio with config string #define @RN@ @<<@ 0x@AH@ @<<@ // @Rd@ +#define IOCFG2 0x00 // GDO2 Output Pin Configuration +#define IOCFG1 0x01 // GDO1 Output Pin Configuration +#define IOCFG0 0x02 // GDO0 Output Pin Configuration +#define FIFOTHR 0x03 // RX FIFO and TX FIFO Thresholds +#define SYNC1 0x04 // Sync Word, High Byte +#define SYNC0 0x05 // Sync Word, Low Byte +#define PKTLEN 0x06 // Packet Length +#define PKTCTRL1 0x07 // Packet Automation Control +#define PKTCTRL0 0x08 // Packet Automation Control +#define ADDR 0x09 // Device Address +#define CHANNR 0x0A // Channel Number +#define FSCTRL1 0x0B // Frequency Synthesizer Control +#define FSCTRL0 0x0C // Frequency Synthesizer Control +#define FREQ2 0x0D // Frequency Control Word, High Byte +#define FREQ1 0x0E // Frequency Control Word, Middle Byte +#define FREQ0 0x0F // Frequency Control Word, Low Byte +#define MDMCFG4 0x10 // Modem Configuration +#define MDMCFG3 0x11 // Modem Configuration +#define MDMCFG2 0x12 // Modem Configuration +#define MDMCFG1 0x13 // Modem Configuration +#define MDMCFG0 0x14 // Modem Configuration +#define DEVIATN 0x15 // Modem Deviation Setting +#define MCSM2 0x16 // Main Radio Control State Machine Configuration +#define MCSM1 0x17 // Main Radio Control State Machine Configuration +#define MCSM0 0x18 // Main Radio Control State Machine Configuration +#define FOCCFG 0x19 // Frequency Offset Compensation Configuration +#define BSCFG 0x1A // Bit Synchronization Configuration +#define AGCCTRL2 0x1B // AGC Control +#define AGCCTRL1 0x1C // AGC Control +#define AGCCTRL0 0x1D // AGC Control +#define WOREVT1 0x1E // High Byte Event0 Timeout +#define WOREVT0 0x1F // Low Byte Event0 Timeout +#define WORCTRL 0x20 // Wake On Radio Control +#define FREND1 0x21 // Front End RX Configuration +#define FREND0 0x22 // Front End TX Configuration +#define FSCAL3 0x23 // Frequency Synthesizer Calibration +#define FSCAL2 0x24 // Frequency Synthesizer Calibration +#define FSCAL1 0x25 // Frequency Synthesizer Calibration +#define FSCAL0 0x26 // Frequency Synthesizer Calibration +#define RCCTRL1 0x27 // RC Oscillator Configuration +#define RCCTRL0 0x28 // RC Oscillator Configuration +#define FSTEST 0x29 // Frequency Synthesizer Calibration Control +#define PTEST 0x2A // Production Test +#define AGCTEST 0x2B // AGC Test +#define TEST2 0x2C // Various Test Settings +#define TEST1 0x2D // Various Test Settings +#define TEST0 0x2E // Various Test Settings + +// Command strobes - CC1101 Datasheet Table 42 +#define SRES 0x30 +#define SFSTXON 0x31 +#define SXOFF 0x32 +#define SCAL 0x33 +#define SRX 0x34 +#define STX 0x35 +#define SIDLE 0x36 +#define SAFC 0x37 +#define SWOR 0x38 +#define SPWD 0x39 +#define SFRX 0x3A +#define SFTX 0x3B +#define SWORRST 0x3C +#define SNOP 0x3D + +// Status Registers - CC1101 Datasheet Table 44 +#define PARTNUM 0x30 +#define VERSION 0x31 +#define FREQEST 0x32 +#define LQI 0x33 +#define RSSI 0x34 +#define MARCSTATE 0x35 +#define WORTIME1 0x36 +#define WORTIME0 0x37 +#define PKTSTATUS 0x38 +#define VCO_VC_DAC 0x39 +#define TXBYTES 0x3A +#define RXBYTES 0x3B +#define RCCTRL1_STATUS 0x3C +#define RCCTRL0_STATUS 0x3D + +#define PATABLE 0x3E +#define FIFO 0x3F + +#define FIFO_LEN 64 + +// State transition times - CC1101 Datasheet Page 54/55 +#define TIME_IDLE_TO_RX_NOCAL 76 // Rounded up from 75.1 +#define TIME_IDLE_TO_RX_CAL 799 +#define TIME_IDLE_TO_TX_NOCAL 76 // Rounded up from 75.2 +#define TIME_IDLE_TO_TX_CAL 799 +#define TIME_TX_TO_RX 32 // Rounded up from 31.1 - compensates for variable baud rate +#define TIME_RX_TO_TX 32 +#define TIME_TX_TO_IDLE_NOCAL 1 // Rounded up from 0.25/baud - compensates for variable baud rate +#define TIME_TX_TO_IDLE_CAL 726 // Rounded up from 725 to compensate for variable baud rate +#define TIME_RX_TO_IDLE_NOCAL 1 // Rounded up from ~0.1 +#define TIME_RX_TO_IDLE_CAL 724 +#define TIME_MANUAL_CAL 735 + +// Error macros +#ifndef CONFIG_DYNAMIC_DEBUG +extern uint debug; +#define CC1101_DEBUG(cc1101, format, ...) if (debug) dev_info(&cc1101->spi->dev, format "\n", ##__VA_ARGS__) +#else +#define CC1101_DEBUG(cc1101, format, ...) dev_dbg(&cc1101->spi->dev, format "\n", ##__VA_ARGS__) +#endif + +#define CC1101_INFO(cc1101, format, ...) dev_info(&cc1101->spi->dev, format "\n", ##__VA_ARGS__) +#define CC1101_ERROR(cc1101, format, ...) dev_err(&cc1101->spi->dev, format "\n", ##__VA_ARGS__) + +// Driver state +typedef enum { + MODE_IDLE, + MODE_TX, + MODE_RX +} cc1101_mode_t; + + +// Struct to hold per-device state +typedef struct { + struct spi_device *spi; + dev_t devt; + int irq; + struct mutex chrdev_lock; + struct mutex device_lock; + cc1101_mode_t mode; + struct cc1101_tx_config tx_config; + struct cc1101_rx_config rx_config; + unsigned char *current_packet; + u32 bytes_remaining; + DECLARE_KFIFO_PTR(rx_fifo, unsigned char); + struct timer_list rx_timeout; + struct work_struct rx_timeout_work; +} cc1101_t; + #endif \ No newline at end of file diff --git a/cc1101_main.c b/cc1101_main.c index a684adf..fde434c 100755 --- a/cc1101_main.c +++ b/cc1101_main.c @@ -1,190 +1,190 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* -* Copyright (c) 2021 -*/ - -#include -#include -#include -#include -#include - -#include "cc1101_internal.h" -#include "cc1101_chrdev.h" -#include "cc1101_spi.h" -#include "cc1101_radio.h" - -// Module parameter for the largest packet that can be transmitted/received -uint max_packet_size = 1024; -module_param(max_packet_size, uint, 0660); - -// Module parameter for the number of received packets to be held in the kernel FIFO waiting to be read -uint rx_fifo_size = 10; -module_param(rx_fifo_size, uint, 0660); - -#ifndef CONFIG_DYNAMIC_DEBUG -uint debug = 0; -module_param(debug, uint, 0660); -#endif - -#ifdef RXONLY - #define EXTRA_STATUS "[RX Only] -" -#else - #define EXTRA_STATUS "-" -#endif - -/* -* Function called on module insertion for each CC1101 entry in device tree -*/ -static int cc1101_spi_probe(struct spi_device *spi) -{ - cc1101_t *cc1101; - struct gpio_desc *gpio; - spi_transaction_t partnum, version; - - // Memory allocations - use devm_* functions so they are automatically freed - // Allocate a new CC1101 struct - cc1101 = devm_kzalloc(&spi->dev, sizeof(*cc1101), GFP_KERNEL); - if (cc1101 == NULL) - { - dev_err(&spi->dev, "Failed to allocate memory"); - return -ENOMEM; - } - - // Allocate a buffer to hold partial packets being received - cc1101->current_packet = devm_kzalloc(&spi->dev, max_packet_size, GFP_KERNEL); - if (cc1101->current_packet == NULL) - { - dev_err(&spi->dev, "Failed to allocate packet buffer memory"); - return -ENOMEM; - } - - // Associate the SPI device to the CC1101 - cc1101->spi = spi; - - // Initialise the device locks - mutex_init(&cc1101->device_lock); - mutex_init(&cc1101->chrdev_lock); - - // Setup the RX timeout timer - timer_setup(&cc1101->rx_timeout, cc1101_rx_timeout, 0); - - // Read the CC1101 part and version numbers from the device registers - partnum = cc1101_spi_read_status_register(cc1101, PARTNUM); - version = cc1101_spi_read_status_register(cc1101, VERSION); - - // Check the device is a CC1101 - if (partnum.data != 0x00 || (version.data != 0x04 && version.data != 0x14)) - { - dev_info(&spi->dev, "Device not found (Partnum: 0x%02x, Version: 0x%02x)", partnum.data, version.data); - return -ENODEV; - } - - // Reset the device, which will place it in idle and load the default config - cc1101_reset(cc1101); - - // Get the GPIO associated with the device in device tree - gpio = devm_gpiod_get_index(&spi->dev, "int", 0, GPIOD_IN); - if (IS_ERR(gpio)) - { - dev_err(&spi->dev, "Failed to get GPIO"); - return -ENODEV; - } - - // Get an IRQ for the GPIO - cc1101->irq = gpiod_to_irq(gpio); - - // Attach the interrupt handler to the GPIO - if(devm_request_threaded_irq(&spi->dev, cc1101->irq, NULL, cc1101_rx_interrupt, IRQF_TRIGGER_RISING | IRQF_ONESHOT, dev_name(&spi->dev), cc1101) != 0){ - dev_err(&spi->dev, "Failed to setup interrupt handler"); - return -ENODEV; - } - - // Add a /dev/cc1101.x.x character device for the device - if(cc1101_chrdev_add_device(cc1101) != 0){ - dev_err(&spi->dev, "Failed to create character device"); - return -ENODEV; - } - - // Associate the device struct to the parent SPI device - spi_set_drvdata(spi, cc1101); - - CC1101_INFO(cc1101, "Ready " EXTRA_STATUS " (Partnum: 0x%02x, Version: 0x%02x)", partnum.data, version.data); - - return 0; -} - -/* -* Function called on module removal for each CC1101 entry in device tree -*/ -static int cc1101_spi_remove(struct spi_device *spi) -{ - cc1101_t* cc1101; - cc1101 = spi_get_drvdata(spi); - - // Remove the RX timeout timer - del_timer(&cc1101->rx_timeout); - - // Reset the hardware, placing it in idle mode - cc1101_reset(cc1101); - - // Remove /dev/cc1101.x.x - cc1101_chrdev_remove_device(cc1101); - CC1101_INFO(cc1101, "Removed"); - return 0; -} - -/* -* Register the module to handle cc1101 entries in device tree -*/ -static const struct of_device_id cc1101_dt_ids[] = { - {.compatible = "ti,cc1101"}, - {}, -}; -MODULE_DEVICE_TABLE(of, cc1101_dt_ids); - -static const struct spi_device_id cc1101_id[] = { - { - .name = "cc1101", - }, - {} -}; -MODULE_DEVICE_TABLE(spi, cc1101_id); - -static struct spi_driver cc1101_driver = { - .driver = { - .name = "cc1101", - .owner = THIS_MODULE, - .of_match_table = cc1101_dt_ids, - }, - .probe = cc1101_spi_probe, - .remove = cc1101_spi_remove, - .id_table = cc1101_id -}; - -/* -* Functions executed on module insertion/removal -* Setup/remove the CC1101 character device class -*/ -static int __init cc1101_init(void) -{ - #ifndef CONFIG_DYNAMIC_DEBUG - if(debug > 1) { - return -EINVAL; - } - #endif - - return cc1101_chrdev_setup(&cc1101_driver); -} -module_init(cc1101_init); - -static void __exit cc1101_exit(void) -{ - cc1101_chrdev_teardown(&cc1101_driver); -} -module_exit(cc1101_exit); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR(""); -MODULE_DESCRIPTION("TI CC1101 Device Driver"); -MODULE_VERSION("1.4.0"); +// SPDX-License-Identifier: GPL-2.0-only +/* +* Copyright (c) 2021 +*/ + +#include +#include +#include +#include +#include + +#include "cc1101_internal.h" +#include "cc1101_chrdev.h" +#include "cc1101_spi.h" +#include "cc1101_radio.h" + +// Module parameter for the largest packet that can be transmitted/received +uint max_packet_size = 1024; +module_param(max_packet_size, uint, 0660); + +// Module parameter for the number of received packets to be held in the kernel FIFO waiting to be read +uint rx_fifo_size = 10; +module_param(rx_fifo_size, uint, 0660); + +#ifndef CONFIG_DYNAMIC_DEBUG +uint debug = 0; +module_param(debug, uint, 0660); +#endif + +#ifdef RXONLY + #define EXTRA_STATUS "[RX Only] -" +#else + #define EXTRA_STATUS "-" +#endif + +/* +* Function called on module insertion for each CC1101 entry in device tree +*/ +static int cc1101_spi_probe(struct spi_device *spi) +{ + cc1101_t *cc1101; + struct gpio_desc *gpio; + spi_transaction_t partnum, version; + + // Memory allocations - use devm_* functions so they are automatically freed + // Allocate a new CC1101 struct + cc1101 = devm_kzalloc(&spi->dev, sizeof(*cc1101), GFP_KERNEL); + if (cc1101 == NULL) + { + dev_err(&spi->dev, "Failed to allocate memory"); + return -ENOMEM; + } + + // Allocate a buffer to hold partial packets being received + cc1101->current_packet = devm_kzalloc(&spi->dev, max_packet_size, GFP_KERNEL); + if (cc1101->current_packet == NULL) + { + dev_err(&spi->dev, "Failed to allocate packet buffer memory"); + return -ENOMEM; + } + + // Associate the SPI device to the CC1101 + cc1101->spi = spi; + + // Initialise the device locks + mutex_init(&cc1101->device_lock); + mutex_init(&cc1101->chrdev_lock); + + // Setup the RX timeout timer + timer_setup(&cc1101->rx_timeout, cc1101_rx_timeout, 0); + + // Read the CC1101 part and version numbers from the device registers + partnum = cc1101_spi_read_status_register(cc1101, PARTNUM); + version = cc1101_spi_read_status_register(cc1101, VERSION); + + // Check the device is a CC1101 + if (partnum.data != 0x00 || (version.data != 0x04 && version.data != 0x14)) + { + dev_info(&spi->dev, "Device not found (Partnum: 0x%02x, Version: 0x%02x)", partnum.data, version.data); + return -ENODEV; + } + + // Reset the device, which will place it in idle and load the default config + cc1101_reset(cc1101); + + // Get the GPIO associated with the device in device tree + gpio = devm_gpiod_get_index(&spi->dev, "int", 0, GPIOD_IN); + if (IS_ERR(gpio)) + { + dev_err(&spi->dev, "Failed to get GPIO"); + return -ENODEV; + } + + // Get an IRQ for the GPIO + cc1101->irq = gpiod_to_irq(gpio); + + // Attach the interrupt handler to the GPIO + if(devm_request_threaded_irq(&spi->dev, cc1101->irq, NULL, cc1101_rx_interrupt, IRQF_TRIGGER_RISING | IRQF_ONESHOT, dev_name(&spi->dev), cc1101) != 0){ + dev_err(&spi->dev, "Failed to setup interrupt handler"); + return -ENODEV; + } + + // Add a /dev/cc1101.x.x character device for the device + if(cc1101_chrdev_add_device(cc1101) != 0){ + dev_err(&spi->dev, "Failed to create character device"); + return -ENODEV; + } + + // Associate the device struct to the parent SPI device + spi_set_drvdata(spi, cc1101); + + CC1101_INFO(cc1101, "Ready " EXTRA_STATUS " (Partnum: 0x%02x, Version: 0x%02x)", partnum.data, version.data); + + return 0; +} + +/* +* Function called on module removal for each CC1101 entry in device tree +*/ +static int cc1101_spi_remove(struct spi_device *spi) +{ + cc1101_t* cc1101; + cc1101 = spi_get_drvdata(spi); + + // Remove the RX timeout timer + del_timer(&cc1101->rx_timeout); + + // Reset the hardware, placing it in idle mode + cc1101_reset(cc1101); + + // Remove /dev/cc1101.x.x + cc1101_chrdev_remove_device(cc1101); + CC1101_INFO(cc1101, "Removed"); + return 0; +} + +/* +* Register the module to handle cc1101 entries in device tree +*/ +static const struct of_device_id cc1101_dt_ids[] = { + {.compatible = "ti,cc1101"}, + {}, +}; +MODULE_DEVICE_TABLE(of, cc1101_dt_ids); + +static const struct spi_device_id cc1101_id[] = { + { + .name = "cc1101", + }, + {} +}; +MODULE_DEVICE_TABLE(spi, cc1101_id); + +static struct spi_driver cc1101_driver = { + .driver = { + .name = "cc1101", + .owner = THIS_MODULE, + .of_match_table = cc1101_dt_ids, + }, + .probe = cc1101_spi_probe, + .remove = cc1101_spi_remove, + .id_table = cc1101_id +}; + +/* +* Functions executed on module insertion/removal +* Setup/remove the CC1101 character device class +*/ +static int __init cc1101_init(void) +{ + #ifndef CONFIG_DYNAMIC_DEBUG + if(debug > 1) { + return -EINVAL; + } + #endif + + return cc1101_chrdev_setup(&cc1101_driver); +} +module_init(cc1101_init); + +static void __exit cc1101_exit(void) +{ + cc1101_chrdev_teardown(&cc1101_driver); +} +module_exit(cc1101_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR(""); +MODULE_DESCRIPTION("TI CC1101 Device Driver"); +MODULE_VERSION("1.4.0"); diff --git a/cc1101_radio.c b/cc1101_radio.c index b0c1050..f2fd56f 100755 --- a/cc1101_radio.c +++ b/cc1101_radio.c @@ -1,515 +1,515 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* -* Copyright (c) 2021 -*/ -#include "cc1101_internal.h" -#include "cc1101_radio.h" -#include "cc1101_config.h" -#include "cc1101_spi.h" - -#include -#include -#include - -/* -* Change the hardware and driver state. Delays based on the state transition times in the datasheet -* -* Arguments: -* cc1101: device struct -* to: mode to change the device state to -*/ -static void change_state(cc1101_t* cc1101, cc1101_mode_t to){ - - unsigned long delay; - unsigned char command; - - switch(to){ - case MODE_IDLE: - command = SIDLE; - switch(cc1101->mode) { - case MODE_TX: - CC1101_DEBUG(cc1101, "TX -> IDLE"); - delay = TIME_TX_TO_IDLE_CAL; - break; - case MODE_RX: - CC1101_DEBUG(cc1101, "RX -> IDLE"); - delay = TIME_RX_TO_IDLE_CAL; - break; - case MODE_IDLE: - CC1101_DEBUG(cc1101, "IDLE -> IDLE"); - return; - default: - CC1101_DEBUG(cc1101, "%d -> IDLE", to); - return; - } - break; - case MODE_TX: - command = STX; - switch(cc1101->mode) { - case MODE_IDLE: - CC1101_DEBUG(cc1101, "IDLE -> TX"); - delay = TIME_IDLE_TO_TX_CAL; - break; - case MODE_RX: - CC1101_DEBUG(cc1101, "RX -> TX"); - delay = TIME_RX_TO_TX; - break; - case MODE_TX: - CC1101_DEBUG(cc1101, "TX -> TX"); - return; - default: - CC1101_DEBUG(cc1101, "%d -> TX", to); - return; - } - break; - case MODE_RX: - command = SRX; - switch(cc1101->mode) { - case MODE_IDLE: - CC1101_DEBUG(cc1101, "IDLE -> RX"); - delay = TIME_IDLE_TO_RX_CAL; - break; - case MODE_TX: - CC1101_DEBUG(cc1101, "TX -> RX"); - delay = TIME_TX_TO_RX; - break; - case MODE_RX: - CC1101_DEBUG(cc1101, "RX -> RX"); - return; - default: - CC1101_DEBUG(cc1101, "%d -> RX", to); - return; - } - break; - default: - return; - } - // Send the command via SPI - cc1101->mode = to; - cc1101_spi_send_command(cc1101, command); - udelay(delay); -} - -/* -* Flush the device's RXFIFO, returning to the idle state -* -* Arguments: -* cc1101: device struct -*/ -void cc1101_flush_rx_fifo(cc1101_t *cc1101){ - cc1101_spi_send_command(cc1101, SFRX); - cc1101->mode = MODE_IDLE; -} - -/* -* Flush the device's TXFIFO, returning to the idle state -* -* Arguments: -* cc1101: device struct -*/ -void cc1101_flush_tx_fifo(cc1101_t *cc1101){ - cc1101_spi_send_command(cc1101, SFTX); - cc1101->mode = MODE_IDLE; -} - -/* -* Set the device to idle -* -* Arguments: -* cc1101: device struct -*/ -void cc1101_idle(cc1101_t* cc1101) -{ - CC1101_DEBUG(cc1101, "Idle Mode"); - change_state(cc1101, MODE_IDLE); -} - -/* -* Set the device to RX -* -* Arguments: -* cc1101: device struct -*/ -void cc1101_rx(cc1101_t* cc1101) -{ - CC1101_DEBUG(cc1101, "Receive Mode"); - change_state(cc1101, MODE_RX); -} - -# ifndef RXONLY -/* -* Function to transmit an arbitrary length packet (> 64 bytes) -* Arguments: -* cc1101: device struct -* buf: bytes to transmit -* len: length of buf -* -* -*/ -static void tx_multi(cc1101_t* cc1101, const char* buf, size_t len){ - - size_t bytes_remaining, fragment_size; - unsigned char tx_bytes; - int fixed_packet_mode = 0; - - // Before transmission, the full packet length is left to transmit - bytes_remaining = len; - - // Write the value that the packet counter will be at when transmission finishes into PKTLEN - // This is recommended by the datasheet, but will not be used until the radio - // is placed in fixed length packet mode - cc1101_spi_write_config_register(cc1101, PKTLEN, len % 256); - - // Set to continual transmit mode - cc1101_spi_write_config_register(cc1101, PKTCTRL0, 0x02); - - // Write the first 32 byte fragment of the packet to the device - cc1101_spi_write_txfifo(cc1101, &buf[0], 32); - - // Decrement the number of bytes remaining to transmit - bytes_remaining -= 32; - - // Instruct the device to begin transmission - change_state(cc1101, MODE_TX); - - CC1101_DEBUG(cc1101, "Transmitting 32 bytes, %zu remaining, %zu Total", bytes_remaining, len); - - // While there are still bytes to transmit - while(bytes_remaining != 0){ - - // Read the current number bytes in the FIFO to be transmitted - tx_bytes = cc1101_spi_read_status_register(cc1101, TXBYTES).data; - - // Check for underflow, exit - // Caller will change state to IDLE and flush the TXFIFO, so handle the error by returning - if(tx_bytes > FIFO_LEN) { - CC1101_DEBUG(cc1101, "TXFIFO Underflow, Aborting Transmission"); - return; - } - - // If there are less than 32 bytes left of the current fragment to transmit, there is room in the - // device's FIFO to fit another fragment - if(tx_bytes < 32){ - - // Switch to fixed packet mode if we're safely within the last 256 bytes and haven't already - if(!fixed_packet_mode && bytes_remaining < 128){ - cc1101_spi_write_config_register(cc1101, PKTCTRL0, 0x00); - fixed_packet_mode = 1; - } - - // If there are less than 32 bytes left to transmit, set the number of bytes to transmit to the remaining length - // Otherwise, transmit a full 32 bytes - fragment_size = bytes_remaining < 32 ? bytes_remaining : 32; - - // Write the fragment to the device's TX FIFO and decrement the number of bytes left in the packet - cc1101_spi_write_txfifo(cc1101, &buf[len-bytes_remaining], fragment_size); - bytes_remaining-=fragment_size; - CC1101_DEBUG(cc1101, "Transmitting %zu bytes, %zu remaining, %zu Total", fragment_size, bytes_remaining, len); - } - } - - // Wait until transmission has finished - do { - bytes_remaining = cc1101_spi_read_status_register(cc1101, TXBYTES).data; - - // Check for underflow, this shouldn't occur in fixed length mode. - // Caller will change state to IDLE and flush the TXFIFO, so handle the error by exiting the loop - if(bytes_remaining > FIFO_LEN){ - CC1101_DEBUG(cc1101, "TXFIFO Underflow"); - break; - } - - } while(bytes_remaining > 0); -} - -/* -* Function to transmit a packet that will fit completely within the CC1101's TX FIFO (<= 64 bytes) -* -* Arguments: -* cc1101: device struct -* buf: bytes to transmit -* len: length of buf -* -*/ -static void tx_single(cc1101_t* cc1101, const char* buf, size_t len) -{ - int bytes_remaining; - - // Write the packet to device's TX FIFO - cc1101_spi_write_txfifo(cc1101, buf, len); - - // Write the length of the packet to the device's Packet Length register - cc1101_spi_write_config_register(cc1101, PKTLEN, len); - - // Set the device's transmission mode to fixed length - cc1101_spi_write_config_register(cc1101, PKTCTRL0, 0x00); - - // Instruct the device to begin transmission - change_state(cc1101, MODE_TX); - - CC1101_DEBUG(cc1101, "Transmitting %zu bytes, 0 remaining, %zu Total", len, len); - - // Wait until transmission has finished. Handle overflow condition - do { - bytes_remaining = cc1101_spi_read_status_register(cc1101, TXBYTES).data; - - // Check for underflow, this shouldn't occur in fixed length mode. - // Handle the error by exiting the loop, as the device will be sent to idle afterwards anyway - if(bytes_remaining > FIFO_LEN){ - CC1101_DEBUG(cc1101, "TXFIFO Underflow"); - break; - } - - } while(bytes_remaining > 0) ; -} - -/* -* Function to transmit a packet -* -* Arguments: -* cc1101: device struct -* buf: bytes to transmit -* len: length of buf -* -*/ -void cc1101_tx(cc1101_t* cc1101, const char* buf, size_t len){ - - CC1101_DEBUG(cc1101, "Transmit Mode"); - // Put the device into idle mode - change_state(cc1101, MODE_IDLE); - - // TX method based on whether the packet will fully fit in the device's FIFO - if(len > FIFO_LEN){ - tx_multi(cc1101, buf, len); - } - else{ - tx_single(cc1101, buf, len); - } - - // Set the device to idle - cc1101_idle(cc1101); - - // Flush the TXFIFO - cc1101_flush_tx_fifo(cc1101); - - // Return to RX mode if configured - if(cc1101->rx_config.packet_length > 0){ - // Restore RX config - cc1101_config_apply_rx(cc1101); - cc1101_rx(cc1101); - } -} -#endif - -/* -* Function to reset the device and the driver's internal state -* -* Arguments: -* cc1101: device struct -*/ -void cc1101_reset(cc1101_t* cc1101) -{ - CC1101_DEBUG(cc1101, "Reset"); - - // Reset the device - cc1101->mode = MODE_IDLE; - cc1101_spi_send_command(cc1101, SRES); - - // Reset the current packet counter - cc1101->bytes_remaining = 0; - - // Clear the RX FIFO - kfifo_reset(&cc1101->rx_fifo); -} - -// Longest to expect between interrupts. At 0.6 kBaud (75 bytes/sec), time to fill to FIFOTHR (32 bytes) should be ~425ms, so 1000ms here seems reasonable -// If an interrupt is missed, the RXFIFO will overflow and the device will not send any further interrupts, causing the device to lock indefinitely. -// This value is used to set a timer that ensures the interrupt handler will trigger and unlock the device if this occurs. -#define RX_TIMEOUT_MS 1000 - -/* -* Interrupt handler function called when the device raises GDO2 -* The default receive configuration instructs it to raise GDO2 when the RX FIFO has received x bytes -* -* Arguments: -* irq: IRQ number -* handle: device struct -*/ -irqreturn_t cc1101_rx_interrupt(int irq, void *handle) -{ - cc1101_t* cc1101 = handle; - - size_t i; - int fifo_available; - unsigned char rx_bytes; - - CC1101_DEBUG(cc1101, "Interrupt"); - - // Interrupt is only used when the driver is in receive mode - if(cc1101->mode == MODE_RX){ - - // Stop the RX timeout timer (if it's running) while processing the interrupt - del_timer(&cc1101->rx_timeout); - - // Read the number of bytes in the device's RX FIFO - rx_bytes = cc1101_spi_read_status_register(cc1101, RXBYTES).data; - - // If an overflow has occured part of the packet will have been missed, so reset and wait for the next packet - if(rx_bytes > FIFO_LEN) { - CC1101_ERROR(cc1101, "RXFIFO Overflow. If this error persists, decrease baud rate"); - - // Flush the RXFIFO - cc1101_flush_rx_fifo(cc1101); - - // Reset SYNC_MODE to the value from the config - cc1101_spi_write_config_register(cc1101, MDMCFG2, cc1101_get_mdmcfg2(&cc1101->rx_config.common, &cc1101->rx_config)); - - // Put the device back into receive mode ready to receive the next packet - change_state(cc1101, MODE_RX); - - // Unlock mutex and reset packet count - cc1101->bytes_remaining = 0; - mutex_unlock(&cc1101->device_lock); - - return IRQ_HANDLED; - } - - // If 0 bytes remaining, this is a new packet - if(cc1101->bytes_remaining == 0){ - - // Try to lock the device - if(mutex_trylock(&cc1101->device_lock) != 1){ - // If this fails, it is because a process has /dev/cc1101.x.x open - CC1101_DEBUG(cc1101, "Interrupt Handler Failed To Acquire Lock"); - - // Drain the device's RX FIFO, otherwise it will overflow, causing RX to stop - // This can be drained into the temp buffer as the next interrupt will start a new packet and overwrite it anyway - cc1101_spi_read_rxfifo(cc1101, &cc1101->current_packet[0], rx_bytes - 1); - - // Return and wait for the next interrupt - return IRQ_HANDLED; - } - // If the lock is held, a packet can be received - CC1101_DEBUG(cc1101, "Receiving Packet"); - - // Update the number of bytes to receive from the RX configuration - cc1101->bytes_remaining = cc1101->rx_config.packet_length; - - // Set SYNC_MODE to "No Preamble/Sync" - this will cause RX to continue even if carrier-sense drops below the defined threshold - // This prevents a situation where more bytes are expected for the current packet but another interrupt doesn't occur - cc1101_spi_write_config_register(cc1101, MDMCFG2, cc1101_get_mdmcfg2(&cc1101->rx_config.common, &cc1101->rx_config) & 0xF8); - - // Start a timer for how long we should wait for another interrupt to arrive - mod_timer(&cc1101->rx_timeout, jiffies + msecs_to_jiffies(RX_TIMEOUT_MS)); - } - - // Something went wrong and there aren't any bytes in the RX FIFO even though GDO2 went high - if(rx_bytes == 0){ - // Reset the receive counter, so the next interrupt will be the start of a new packet - CC1101_ERROR(cc1101, "Receive Error, Waiting for Next Packet"); - cc1101->bytes_remaining = 0; - - // Reset SYNC_MODE to the value from the config - cc1101_spi_write_config_register(cc1101, MDMCFG2, cc1101_get_mdmcfg2(&cc1101->rx_config.common, &cc1101->rx_config)); - - // Release the device lock - mutex_unlock(&cc1101->device_lock); - } - // Received some bytes, but there are still some remaining in the packet to be received - else if(rx_bytes < cc1101->bytes_remaining){ - - CC1101_DEBUG(cc1101, "Received %d Bytes, Read %d Bytes, %d Bytes Remaining", rx_bytes, rx_bytes - 1, cc1101->bytes_remaining - (rx_bytes - 1)); - - // Read the received number of bytes from the device's RX FIFO into the temporary buffer - cc1101_spi_read_rxfifo(cc1101, &cc1101->current_packet[cc1101->rx_config.packet_length - cc1101->bytes_remaining], rx_bytes - 1); - - // Decrement the number of bytes left to receive in the packet - cc1101->bytes_remaining = cc1101->bytes_remaining - (rx_bytes - 1); - - // Restart the timer for how long we should wait for another interrupt to arrive - mod_timer(&cc1101->rx_timeout, jiffies + msecs_to_jiffies(RX_TIMEOUT_MS)); - - //Return without releasing the lock. The device is in the middle of a receive and can't be reconfigured - } - // Received a number of bytes greater than or equal to the number left in the packet - else { - del_timer(&cc1101->rx_timeout); - - CC1101_DEBUG(cc1101, "Received %d Bytes, Read %d Bytes, %d Bytes Remaining", rx_bytes, cc1101->bytes_remaining, 0); - - // RX has finished and the required bytes are in the device's RX FIFO, so put the device in idle mode - change_state(cc1101, MODE_IDLE); - - // Read the remaining bytes from the device's RX FIFO - cc1101_spi_read_rxfifo(cc1101, &cc1101->current_packet[cc1101->rx_config.packet_length - cc1101->bytes_remaining], cc1101->bytes_remaining); - - // Get the amount of space left in the received packet buffer - fifo_available = kfifo_avail(&cc1101->rx_fifo); - - // Remove oldest packet from the received packet buffer if there is less than is required to hold the newly received packet - if(fifo_available < cc1101->rx_config.packet_length){ - CC1101_DEBUG(cc1101, "RX FIFO Full - Removing Packet"); - for(i = 0; i < cc1101->rx_config.packet_length; i++){ - kfifo_skip(&cc1101->rx_fifo); - } - } - - // Add the new packet to the received packet buffer - kfifo_in(&cc1101->rx_fifo, cc1101->current_packet, cc1101->rx_config.packet_length); - - CC1101_DEBUG(cc1101, "Packet Received"); - - // Reset the number of bytes remaining - cc1101->bytes_remaining = 0; - - // Flush the device's RX FIFO - cc1101_flush_rx_fifo(cc1101); - - // Reset SYNC_MODE to the value from the config - cc1101_spi_write_config_register(cc1101, MDMCFG2, cc1101_get_mdmcfg2(&cc1101->rx_config.common, &cc1101->rx_config)); - - // Put the device back into receive mode ready to receive the next packet - change_state(cc1101, MODE_RX); - - // Release the lock so the device can be reconfigured if necessary - mutex_unlock(&cc1101->device_lock); - } - } - return IRQ_HANDLED; -} - -/* -* Work function to recover from a missed interrupt in RX -* -* Arguments: -* work: rx_timeout_work struct of the device that has missed an interrupt -*/ -void cc1101_rx_timeout_work(struct work_struct *work){ - // Get the device from the work_struct - cc1101_t *cc1101; - cc1101 = container_of(work, cc1101_t, rx_timeout_work); - - // Call the interrupt handler, which will detect the RXFIFO overflow state from the device and recover - cc1101_rx_interrupt(cc1101->irq, cc1101); -} - -/* -* Receive timer callback. Called when an interrupt is expected during RX, but never arrives -* -* This can occur at high baud rates if the interrupt handler has not finished execution when the next interrupt arrives -* -* RXFIFO will have overflowed by the time this is called. -* -* Arguments: -* cc1101: device struct -*/ -void cc1101_rx_timeout(struct timer_list *t){ - // Get the device the timeout has occured on - cc1101_t *cc1101 = from_timer(cc1101, t, rx_timeout); - CC1101_ERROR(cc1101, "RX Interrupt Missed"); - - // Schedule the handler to be called in the process context to recover - INIT_WORK(&cc1101->rx_timeout_work, cc1101_rx_timeout_work); - schedule_work(&cc1101->rx_timeout_work); -} - +// SPDX-License-Identifier: GPL-2.0-only +/* +* Copyright (c) 2021 +*/ +#include "cc1101_internal.h" +#include "cc1101_radio.h" +#include "cc1101_config.h" +#include "cc1101_spi.h" + +#include +#include +#include + +/* +* Change the hardware and driver state. Delays based on the state transition times in the datasheet +* +* Arguments: +* cc1101: device struct +* to: mode to change the device state to +*/ +static void change_state(cc1101_t* cc1101, cc1101_mode_t to){ + + unsigned long delay; + unsigned char command; + + switch(to){ + case MODE_IDLE: + command = SIDLE; + switch(cc1101->mode) { + case MODE_TX: + CC1101_DEBUG(cc1101, "TX -> IDLE"); + delay = TIME_TX_TO_IDLE_CAL; + break; + case MODE_RX: + CC1101_DEBUG(cc1101, "RX -> IDLE"); + delay = TIME_RX_TO_IDLE_CAL; + break; + case MODE_IDLE: + CC1101_DEBUG(cc1101, "IDLE -> IDLE"); + return; + default: + CC1101_DEBUG(cc1101, "%d -> IDLE", to); + return; + } + break; + case MODE_TX: + command = STX; + switch(cc1101->mode) { + case MODE_IDLE: + CC1101_DEBUG(cc1101, "IDLE -> TX"); + delay = TIME_IDLE_TO_TX_CAL; + break; + case MODE_RX: + CC1101_DEBUG(cc1101, "RX -> TX"); + delay = TIME_RX_TO_TX; + break; + case MODE_TX: + CC1101_DEBUG(cc1101, "TX -> TX"); + return; + default: + CC1101_DEBUG(cc1101, "%d -> TX", to); + return; + } + break; + case MODE_RX: + command = SRX; + switch(cc1101->mode) { + case MODE_IDLE: + CC1101_DEBUG(cc1101, "IDLE -> RX"); + delay = TIME_IDLE_TO_RX_CAL; + break; + case MODE_TX: + CC1101_DEBUG(cc1101, "TX -> RX"); + delay = TIME_TX_TO_RX; + break; + case MODE_RX: + CC1101_DEBUG(cc1101, "RX -> RX"); + return; + default: + CC1101_DEBUG(cc1101, "%d -> RX", to); + return; + } + break; + default: + return; + } + // Send the command via SPI + cc1101->mode = to; + cc1101_spi_send_command(cc1101, command); + udelay(delay); +} + +/* +* Flush the device's RXFIFO, returning to the idle state +* +* Arguments: +* cc1101: device struct +*/ +void cc1101_flush_rx_fifo(cc1101_t *cc1101){ + cc1101_spi_send_command(cc1101, SFRX); + cc1101->mode = MODE_IDLE; +} + +/* +* Flush the device's TXFIFO, returning to the idle state +* +* Arguments: +* cc1101: device struct +*/ +void cc1101_flush_tx_fifo(cc1101_t *cc1101){ + cc1101_spi_send_command(cc1101, SFTX); + cc1101->mode = MODE_IDLE; +} + +/* +* Set the device to idle +* +* Arguments: +* cc1101: device struct +*/ +void cc1101_idle(cc1101_t* cc1101) +{ + CC1101_DEBUG(cc1101, "Idle Mode"); + change_state(cc1101, MODE_IDLE); +} + +/* +* Set the device to RX +* +* Arguments: +* cc1101: device struct +*/ +void cc1101_rx(cc1101_t* cc1101) +{ + CC1101_DEBUG(cc1101, "Receive Mode"); + change_state(cc1101, MODE_RX); +} + +# ifndef RXONLY +/* +* Function to transmit an arbitrary length packet (> 64 bytes) +* Arguments: +* cc1101: device struct +* buf: bytes to transmit +* len: length of buf +* +* +*/ +static void tx_multi(cc1101_t* cc1101, const char* buf, size_t len){ + + size_t bytes_remaining, fragment_size; + unsigned char tx_bytes; + int fixed_packet_mode = 0; + + // Before transmission, the full packet length is left to transmit + bytes_remaining = len; + + // Write the value that the packet counter will be at when transmission finishes into PKTLEN + // This is recommended by the datasheet, but will not be used until the radio + // is placed in fixed length packet mode + cc1101_spi_write_config_register(cc1101, PKTLEN, len % 256); + + // Set to continual transmit mode + cc1101_spi_write_config_register(cc1101, PKTCTRL0, 0x02); + + // Write the first 32 byte fragment of the packet to the device + cc1101_spi_write_txfifo(cc1101, &buf[0], 32); + + // Decrement the number of bytes remaining to transmit + bytes_remaining -= 32; + + // Instruct the device to begin transmission + change_state(cc1101, MODE_TX); + + CC1101_DEBUG(cc1101, "Transmitting 32 bytes, %zu remaining, %zu Total", bytes_remaining, len); + + // While there are still bytes to transmit + while(bytes_remaining != 0){ + + // Read the current number bytes in the FIFO to be transmitted + tx_bytes = cc1101_spi_read_status_register(cc1101, TXBYTES).data; + + // Check for underflow, exit + // Caller will change state to IDLE and flush the TXFIFO, so handle the error by returning + if(tx_bytes > FIFO_LEN) { + CC1101_DEBUG(cc1101, "TXFIFO Underflow, Aborting Transmission"); + return; + } + + // If there are less than 32 bytes left of the current fragment to transmit, there is room in the + // device's FIFO to fit another fragment + if(tx_bytes < 32){ + + // Switch to fixed packet mode if we're safely within the last 256 bytes and haven't already + if(!fixed_packet_mode && bytes_remaining < 128){ + cc1101_spi_write_config_register(cc1101, PKTCTRL0, 0x00); + fixed_packet_mode = 1; + } + + // If there are less than 32 bytes left to transmit, set the number of bytes to transmit to the remaining length + // Otherwise, transmit a full 32 bytes + fragment_size = bytes_remaining < 32 ? bytes_remaining : 32; + + // Write the fragment to the device's TX FIFO and decrement the number of bytes left in the packet + cc1101_spi_write_txfifo(cc1101, &buf[len-bytes_remaining], fragment_size); + bytes_remaining-=fragment_size; + CC1101_DEBUG(cc1101, "Transmitting %zu bytes, %zu remaining, %zu Total", fragment_size, bytes_remaining, len); + } + } + + // Wait until transmission has finished + do { + bytes_remaining = cc1101_spi_read_status_register(cc1101, TXBYTES).data; + + // Check for underflow, this shouldn't occur in fixed length mode. + // Caller will change state to IDLE and flush the TXFIFO, so handle the error by exiting the loop + if(bytes_remaining > FIFO_LEN){ + CC1101_DEBUG(cc1101, "TXFIFO Underflow"); + break; + } + + } while(bytes_remaining > 0); +} + +/* +* Function to transmit a packet that will fit completely within the CC1101's TX FIFO (<= 64 bytes) +* +* Arguments: +* cc1101: device struct +* buf: bytes to transmit +* len: length of buf +* +*/ +static void tx_single(cc1101_t* cc1101, const char* buf, size_t len) +{ + int bytes_remaining; + + // Write the packet to device's TX FIFO + cc1101_spi_write_txfifo(cc1101, buf, len); + + // Write the length of the packet to the device's Packet Length register + cc1101_spi_write_config_register(cc1101, PKTLEN, len); + + // Set the device's transmission mode to fixed length + cc1101_spi_write_config_register(cc1101, PKTCTRL0, 0x00); + + // Instruct the device to begin transmission + change_state(cc1101, MODE_TX); + + CC1101_DEBUG(cc1101, "Transmitting %zu bytes, 0 remaining, %zu Total", len, len); + + // Wait until transmission has finished. Handle overflow condition + do { + bytes_remaining = cc1101_spi_read_status_register(cc1101, TXBYTES).data; + + // Check for underflow, this shouldn't occur in fixed length mode. + // Handle the error by exiting the loop, as the device will be sent to idle afterwards anyway + if(bytes_remaining > FIFO_LEN){ + CC1101_DEBUG(cc1101, "TXFIFO Underflow"); + break; + } + + } while(bytes_remaining > 0) ; +} + +/* +* Function to transmit a packet +* +* Arguments: +* cc1101: device struct +* buf: bytes to transmit +* len: length of buf +* +*/ +void cc1101_tx(cc1101_t* cc1101, const char* buf, size_t len){ + + CC1101_DEBUG(cc1101, "Transmit Mode"); + // Put the device into idle mode + change_state(cc1101, MODE_IDLE); + + // TX method based on whether the packet will fully fit in the device's FIFO + if(len > FIFO_LEN){ + tx_multi(cc1101, buf, len); + } + else{ + tx_single(cc1101, buf, len); + } + + // Set the device to idle + cc1101_idle(cc1101); + + // Flush the TXFIFO + cc1101_flush_tx_fifo(cc1101); + + // Return to RX mode if configured + if(cc1101->rx_config.packet_length > 0){ + // Restore RX config + cc1101_config_apply_rx(cc1101); + cc1101_rx(cc1101); + } +} +#endif + +/* +* Function to reset the device and the driver's internal state +* +* Arguments: +* cc1101: device struct +*/ +void cc1101_reset(cc1101_t* cc1101) +{ + CC1101_DEBUG(cc1101, "Reset"); + + // Reset the device + cc1101->mode = MODE_IDLE; + cc1101_spi_send_command(cc1101, SRES); + + // Reset the current packet counter + cc1101->bytes_remaining = 0; + + // Clear the RX FIFO + kfifo_reset(&cc1101->rx_fifo); +} + +// Longest to expect between interrupts. At 0.6 kBaud (75 bytes/sec), time to fill to FIFOTHR (32 bytes) should be ~425ms, so 1000ms here seems reasonable +// If an interrupt is missed, the RXFIFO will overflow and the device will not send any further interrupts, causing the device to lock indefinitely. +// This value is used to set a timer that ensures the interrupt handler will trigger and unlock the device if this occurs. +#define RX_TIMEOUT_MS 1000 + +/* +* Interrupt handler function called when the device raises GDO2 +* The default receive configuration instructs it to raise GDO2 when the RX FIFO has received x bytes +* +* Arguments: +* irq: IRQ number +* handle: device struct +*/ +irqreturn_t cc1101_rx_interrupt(int irq, void *handle) +{ + cc1101_t* cc1101 = handle; + + size_t i; + int fifo_available; + unsigned char rx_bytes; + + CC1101_DEBUG(cc1101, "Interrupt"); + + // Interrupt is only used when the driver is in receive mode + if(cc1101->mode == MODE_RX){ + + // Stop the RX timeout timer (if it's running) while processing the interrupt + del_timer(&cc1101->rx_timeout); + + // Read the number of bytes in the device's RX FIFO + rx_bytes = cc1101_spi_read_status_register(cc1101, RXBYTES).data; + + // If an overflow has occured part of the packet will have been missed, so reset and wait for the next packet + if(rx_bytes > FIFO_LEN) { + CC1101_ERROR(cc1101, "RXFIFO Overflow. If this error persists, decrease baud rate"); + + // Flush the RXFIFO + cc1101_flush_rx_fifo(cc1101); + + // Reset SYNC_MODE to the value from the config + cc1101_spi_write_config_register(cc1101, MDMCFG2, cc1101_get_mdmcfg2(&cc1101->rx_config.common, &cc1101->rx_config)); + + // Put the device back into receive mode ready to receive the next packet + change_state(cc1101, MODE_RX); + + // Unlock mutex and reset packet count + cc1101->bytes_remaining = 0; + mutex_unlock(&cc1101->device_lock); + + return IRQ_HANDLED; + } + + // If 0 bytes remaining, this is a new packet + if(cc1101->bytes_remaining == 0){ + + // Try to lock the device + if(mutex_trylock(&cc1101->device_lock) != 1){ + // If this fails, it is because a process has /dev/cc1101.x.x open + CC1101_DEBUG(cc1101, "Interrupt Handler Failed To Acquire Lock"); + + // Drain the device's RX FIFO, otherwise it will overflow, causing RX to stop + // This can be drained into the temp buffer as the next interrupt will start a new packet and overwrite it anyway + cc1101_spi_read_rxfifo(cc1101, &cc1101->current_packet[0], rx_bytes - 1); + + // Return and wait for the next interrupt + return IRQ_HANDLED; + } + // If the lock is held, a packet can be received + CC1101_DEBUG(cc1101, "Receiving Packet"); + + // Update the number of bytes to receive from the RX configuration + cc1101->bytes_remaining = cc1101->rx_config.packet_length; + + // Set SYNC_MODE to "No Preamble/Sync" - this will cause RX to continue even if carrier-sense drops below the defined threshold + // This prevents a situation where more bytes are expected for the current packet but another interrupt doesn't occur + cc1101_spi_write_config_register(cc1101, MDMCFG2, cc1101_get_mdmcfg2(&cc1101->rx_config.common, &cc1101->rx_config) & 0xF8); + + // Start a timer for how long we should wait for another interrupt to arrive + mod_timer(&cc1101->rx_timeout, jiffies + msecs_to_jiffies(RX_TIMEOUT_MS)); + } + + // Something went wrong and there aren't any bytes in the RX FIFO even though GDO2 went high + if(rx_bytes == 0){ + // Reset the receive counter, so the next interrupt will be the start of a new packet + CC1101_ERROR(cc1101, "Receive Error, Waiting for Next Packet"); + cc1101->bytes_remaining = 0; + + // Reset SYNC_MODE to the value from the config + cc1101_spi_write_config_register(cc1101, MDMCFG2, cc1101_get_mdmcfg2(&cc1101->rx_config.common, &cc1101->rx_config)); + + // Release the device lock + mutex_unlock(&cc1101->device_lock); + } + // Received some bytes, but there are still some remaining in the packet to be received + else if(rx_bytes < cc1101->bytes_remaining){ + + CC1101_DEBUG(cc1101, "Received %d Bytes, Read %d Bytes, %d Bytes Remaining", rx_bytes, rx_bytes - 1, cc1101->bytes_remaining - (rx_bytes - 1)); + + // Read the received number of bytes from the device's RX FIFO into the temporary buffer + cc1101_spi_read_rxfifo(cc1101, &cc1101->current_packet[cc1101->rx_config.packet_length - cc1101->bytes_remaining], rx_bytes - 1); + + // Decrement the number of bytes left to receive in the packet + cc1101->bytes_remaining = cc1101->bytes_remaining - (rx_bytes - 1); + + // Restart the timer for how long we should wait for another interrupt to arrive + mod_timer(&cc1101->rx_timeout, jiffies + msecs_to_jiffies(RX_TIMEOUT_MS)); + + //Return without releasing the lock. The device is in the middle of a receive and can't be reconfigured + } + // Received a number of bytes greater than or equal to the number left in the packet + else { + del_timer(&cc1101->rx_timeout); + + CC1101_DEBUG(cc1101, "Received %d Bytes, Read %d Bytes, %d Bytes Remaining", rx_bytes, cc1101->bytes_remaining, 0); + + // RX has finished and the required bytes are in the device's RX FIFO, so put the device in idle mode + change_state(cc1101, MODE_IDLE); + + // Read the remaining bytes from the device's RX FIFO + cc1101_spi_read_rxfifo(cc1101, &cc1101->current_packet[cc1101->rx_config.packet_length - cc1101->bytes_remaining], cc1101->bytes_remaining); + + // Get the amount of space left in the received packet buffer + fifo_available = kfifo_avail(&cc1101->rx_fifo); + + // Remove oldest packet from the received packet buffer if there is less than is required to hold the newly received packet + if(fifo_available < cc1101->rx_config.packet_length){ + CC1101_DEBUG(cc1101, "RX FIFO Full - Removing Packet"); + for(i = 0; i < cc1101->rx_config.packet_length; i++){ + kfifo_skip(&cc1101->rx_fifo); + } + } + + // Add the new packet to the received packet buffer + kfifo_in(&cc1101->rx_fifo, cc1101->current_packet, cc1101->rx_config.packet_length); + + CC1101_DEBUG(cc1101, "Packet Received"); + + // Reset the number of bytes remaining + cc1101->bytes_remaining = 0; + + // Flush the device's RX FIFO + cc1101_flush_rx_fifo(cc1101); + + // Reset SYNC_MODE to the value from the config + cc1101_spi_write_config_register(cc1101, MDMCFG2, cc1101_get_mdmcfg2(&cc1101->rx_config.common, &cc1101->rx_config)); + + // Put the device back into receive mode ready to receive the next packet + change_state(cc1101, MODE_RX); + + // Release the lock so the device can be reconfigured if necessary + mutex_unlock(&cc1101->device_lock); + } + } + return IRQ_HANDLED; +} + +/* +* Work function to recover from a missed interrupt in RX +* +* Arguments: +* work: rx_timeout_work struct of the device that has missed an interrupt +*/ +void cc1101_rx_timeout_work(struct work_struct *work){ + // Get the device from the work_struct + cc1101_t *cc1101; + cc1101 = container_of(work, cc1101_t, rx_timeout_work); + + // Call the interrupt handler, which will detect the RXFIFO overflow state from the device and recover + cc1101_rx_interrupt(cc1101->irq, cc1101); +} + +/* +* Receive timer callback. Called when an interrupt is expected during RX, but never arrives +* +* This can occur at high baud rates if the interrupt handler has not finished execution when the next interrupt arrives +* +* RXFIFO will have overflowed by the time this is called. +* +* Arguments: +* cc1101: device struct +*/ +void cc1101_rx_timeout(struct timer_list *t){ + // Get the device the timeout has occured on + cc1101_t *cc1101 = from_timer(cc1101, t, rx_timeout); + CC1101_ERROR(cc1101, "RX Interrupt Missed"); + + // Schedule the handler to be called in the process context to recover + INIT_WORK(&cc1101->rx_timeout_work, cc1101_rx_timeout_work); + schedule_work(&cc1101->rx_timeout_work); +} + diff --git a/cc1101_radio.h b/cc1101_radio.h index 9f09043..696b96b 100755 --- a/cc1101_radio.h +++ b/cc1101_radio.h @@ -1,16 +1,16 @@ -#ifndef CC1101_RADIO_H -#define CC1101_RADIO_H - -#include -#include - -irqreturn_t cc1101_rx_interrupt(int irq, void *handle); -void cc1101_rx_timeout(struct timer_list *timer); -void cc1101_idle(cc1101_t* cc1101); -void cc1101_rx(cc1101_t* cc1101); -#ifndef RXONLY -void cc1101_tx(cc1101_t* cc1101, const char* buf, size_t len); -#endif -void cc1101_reset(cc1101_t* cc1101); - +#ifndef CC1101_RADIO_H +#define CC1101_RADIO_H + +#include +#include + +irqreturn_t cc1101_rx_interrupt(int irq, void *handle); +void cc1101_rx_timeout(struct timer_list *timer); +void cc1101_idle(cc1101_t* cc1101); +void cc1101_rx(cc1101_t* cc1101); +#ifndef RXONLY +void cc1101_tx(cc1101_t* cc1101, const char* buf, size_t len); +#endif +void cc1101_reset(cc1101_t* cc1101); + #endif \ No newline at end of file diff --git a/cc1101_spi.c b/cc1101_spi.c index 3f9bf77..c950711 100755 --- a/cc1101_spi.c +++ b/cc1101_spi.c @@ -1,338 +1,338 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* -* Copyright (c) 2021 -*/ -#include "cc1101_spi.h" - -#include "cc1101_internal.h" - -#define MAX_REPEATS 10 - -#define SPI_BURST 0x40 -#define SPI_READ 0x80 - -/* -* Read a single byte from one of the device's configuration registers via SPI -* -* Arguments: -* cc1101: device struct -* reg: address of CC1101 register to read -* -* Returns: -* Transaction struct containing register contents and device status -*/ -spi_transaction_t cc1101_spi_read_config_register(cc1101_t* cc1101, unsigned char reg) { - struct spi_transfer t = {0}; - struct spi_message m; - spi_transaction_t transaction; - - WARN_ON(reg > 0x2E); - - transaction.header = reg | SPI_READ; - transaction.data = 0; - - t.tx_buf = &transaction; - t.rx_buf = &transaction; - t.len = 2; - t.cs_change = 0; - - spi_message_init(&m); - spi_message_add_tail(&t, &m); - spi_sync(cc1101->spi, &m); - - return transaction; -} - -/* -* Writes a single byte to one of the device's registers via SPI -* -* Arguments: -* cc1101: device struct -* reg: address of CC1101 register to write -* value: byte to write to the register -* -* Returns: -* Device status -*/ -unsigned char cc1101_spi_write_config_register(cc1101_t* cc1101, unsigned char reg, unsigned char value) { - struct spi_transfer t = {0}; - struct spi_message m; - spi_transaction_t transaction; - - WARN_ON(reg > 0x2E); - - transaction.header = reg; - transaction.data = value; - - t.tx_buf = &transaction, - t.rx_buf = &transaction, - t.len = 2, - t.cs_change = 0, - - spi_message_init(&m); - spi_message_add_tail(&t, &m); - spi_sync(cc1101->spi, &m); - - return transaction.header; -} - -/* -* Read a sequence of bytes from the device's registers via SPI -* -* Arguments: -* cc1101: device struct -* reg: address of CC1101 registers to start reading from. -* out: pointer to a buffer that will contain the register values read from the device -* len: size of out, number of bytes to read from the device -* -* Returns: -* Device status -*/ -static unsigned char read_registers(cc1101_t* cc1101, unsigned char reg, unsigned char* out, unsigned char len) { - struct spi_transfer t = {0}; - struct spi_message m; - unsigned char transaction[FIFO_LEN + 1]; - - transaction[0] = reg | SPI_BURST | SPI_READ; - - t.tx_buf = &transaction; - t.rx_buf = &transaction; - t.len = len + 1; - t.cs_change = 0; - - spi_message_init(&m); - spi_message_add_tail(&t, &m); - spi_sync(cc1101->spi, &m); - - memcpy(out, &transaction[1], len); - - return transaction[0]; -} - -/* -* Read a sequence of bytes from the device's config registers via SPI -* -* Arguments: -* cc1101: device struct -* registers: pointer to a buffer that will contain the values read from the device -* len: size of out, number of bytes to read from the device -* -* Returns: -* Device status -* -*/ -unsigned char cc1101_spi_read_config_registers(cc1101_t* cc1101, unsigned char* registers, unsigned char len) { - BUG_ON(len > CONFIG_REGISTERS_LEN); - return read_registers(cc1101, 0x00, registers, len); -} - -/* -* Read a sequence of bytes from the device's PATABLE via SPI -* -* Arguments: -* cc1101: device struct -* patable: pointer to a buffer that will contain the values read from the device -* len: size of out, number of bytes to read from the device -* -* Returns: -* Device status -*/ -unsigned char cc1101_spi_read_patable(cc1101_t* cc1101, unsigned char* patable, unsigned char len) { - BUG_ON(len > PATABLE_LEN); - return read_registers(cc1101, PATABLE, patable, len); -} - -/* -* Read a sequence of bytes from the device's RXFIFO via SPI -* -* Arguments: -* cc1101: device struct -* rx_bytes: pointer to a buffer that will contain the values read from the device -* len: size of out, number of bytes to read from the device -* -* Returns: -* Device status -*/ -unsigned char cc1101_spi_read_rxfifo(cc1101_t* cc1101, unsigned char* rx_bytes, unsigned char len) { - BUG_ON(len > FIFO_LEN); - return read_registers(cc1101, FIFO, rx_bytes, len); -} - -/* -* Write a sequence of bytes to the device's registers via SPI -* -* Arguments: -* cc1101: device struct -* reg: address of CC1101 registers to start writing to. Must have the burst bit set -* in: pointer to a buffer containing a byte sequence to write to the device registers -* len: number of bytes in buffer that will be written to the device -* -* Returns: -* Device status -*/ -static unsigned char write_registers(cc1101_t* cc1101, unsigned char reg, const unsigned char* in, unsigned char len) { - struct spi_transfer t = {0}; - struct spi_message m; - unsigned char transaction[FIFO_LEN + 1]; - - transaction[0] = reg | SPI_BURST; - memcpy(&transaction[1], in, len); - - t.tx_buf = &transaction; - t.rx_buf = &transaction; - t.len = len + 1; - t.cs_change = 0; - - spi_message_init(&m); - spi_message_add_tail(&t, &m); - spi_sync(cc1101->spi, &m); - - return transaction[0]; -} - -/* -* Write a sequence of bytes to the device's configuration registers via SPI -* -* Arguments: -* cc1101: device struct -* registers: pointer to a buffer containing a byte sequence to write to the device registers -* len: number of bytes in buffer that will be written to the device -* -* Returns: -* Device status -*/ -unsigned char cc1101_spi_write_config_registers(cc1101_t* cc1101, const unsigned char* registers, unsigned char len) { - BUG_ON(len > CONFIG_REGISTERS_LEN); - return write_registers(cc1101, 0x00, registers, len); -} - -/* -* Write a sequence of bytes to the device's PATABLE registers via SPI -* -* Arguments: -* cc1101: device struct -* patable: pointer to a buffer containing a byte sequence to write to the PATABLE -* len: number of bytes in buffer that will be written to the device -* -* Returns: -* Device status -*/ -unsigned char cc1101_spi_write_patable(cc1101_t* cc1101, const unsigned char* patable, unsigned char len) { - BUG_ON(len > PATABLE_LEN); - return write_registers(cc1101, PATABLE, patable, len); -} - -/* -* Write a sequence of bytes to the device's TXFIFO via SPI -* -* Arguments: -* cc1101: device struct -* tx_bytes: pointer to a buffer containing a byte sequence to write to the TXFIFO -* len: number of bytes in buffer that will be written to the device -* -* Returns: -* Device status -*/ -unsigned char cc1101_spi_write_txfifo(cc1101_t* cc1101, const unsigned char* tx_bytes, unsigned char len) { - BUG_ON(len > FIFO_LEN); - return write_registers(cc1101, FIFO, tx_bytes, len); -} - -/* -* Sends a command to the device via SPI -* -* Arguments: -* cc1101: device struct -* command: command to send to the device -* -* Returns: -* Device status -*/ -unsigned char cc1101_spi_send_command(cc1101_t* cc1101, unsigned char command) { - struct spi_transfer t = {0}; - struct spi_message m; - unsigned char transaction; - - WARN_ON(command < 0x30 || command > 0x3D); - - transaction = command; - - t.tx_buf = &transaction; - t.rx_buf = &transaction; - t.len = 1; - t.cs_change = 0; - - spi_message_init(&m); - spi_message_add_tail(&t, &m); - spi_sync(cc1101->spi, &m); - - return transaction; -} - -/* -* Read a single byte from one of the device's status registers via SPI -* -* Arguments: -* cc1101: device struct -* reg: address of CC1101 status register to read -* -* Returns: -* Transaction struct containing register contents and device status -*/ -spi_transaction_t cc1101_spi_read_status_register_once(cc1101_t* cc1101, unsigned char reg) { - struct spi_transfer t = {0}; - struct spi_message m; - spi_transaction_t transaction; - - WARN_ON(reg < 0x30 || reg > 0x3D); - - // Status registers require the burst bit to be set - transaction.header = reg | SPI_BURST | SPI_READ; - transaction.data = 0; - - t.tx_buf = &transaction; - t.rx_buf = &transaction; - t.len = 2; - t.cs_change = 0; - - spi_message_init(&m); - spi_message_add_tail(&t, &m); - spi_sync(cc1101->spi, &m); - - return transaction; -} - -/* -* Read a single byte from one of the device's status registers via SPI -* -* The double-read implemented here is recommended by the CC1101 Errata to prevent corrupt values -* from being read from the device. -* -* Arguments: -* cc1101: device struct -* reg: address of CC1101 status register to read -* -* Returns: -* Transaction struct containing register contents and device status -*/ -spi_transaction_t cc1101_spi_read_status_register(cc1101_t* cc1101, unsigned char reg) { - spi_transaction_t result, result_check; - - // Read status register - result = cc1101_spi_read_status_register_once(cc1101, reg); - while (1) { - // Read again - result_check = cc1101_spi_read_status_register_once(cc1101, reg); - - // If the values match, the value was valid - if (result.data == result_check.data) { - break; - } - // If not, continue until two reads agree - else { - result = result_check; - } - } - - return result; +// SPDX-License-Identifier: GPL-2.0-only +/* +* Copyright (c) 2021 +*/ +#include "cc1101_spi.h" + +#include "cc1101_internal.h" + +#define MAX_REPEATS 10 + +#define SPI_BURST 0x40 +#define SPI_READ 0x80 + +/* +* Read a single byte from one of the device's configuration registers via SPI +* +* Arguments: +* cc1101: device struct +* reg: address of CC1101 register to read +* +* Returns: +* Transaction struct containing register contents and device status +*/ +spi_transaction_t cc1101_spi_read_config_register(cc1101_t* cc1101, unsigned char reg) { + struct spi_transfer t = {0}; + struct spi_message m; + spi_transaction_t transaction; + + WARN_ON(reg > 0x2E); + + transaction.header = reg | SPI_READ; + transaction.data = 0; + + t.tx_buf = &transaction; + t.rx_buf = &transaction; + t.len = 2; + t.cs_change = 0; + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + spi_sync(cc1101->spi, &m); + + return transaction; +} + +/* +* Writes a single byte to one of the device's registers via SPI +* +* Arguments: +* cc1101: device struct +* reg: address of CC1101 register to write +* value: byte to write to the register +* +* Returns: +* Device status +*/ +unsigned char cc1101_spi_write_config_register(cc1101_t* cc1101, unsigned char reg, unsigned char value) { + struct spi_transfer t = {0}; + struct spi_message m; + spi_transaction_t transaction; + + WARN_ON(reg > 0x2E); + + transaction.header = reg; + transaction.data = value; + + t.tx_buf = &transaction, + t.rx_buf = &transaction, + t.len = 2, + t.cs_change = 0, + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + spi_sync(cc1101->spi, &m); + + return transaction.header; +} + +/* +* Read a sequence of bytes from the device's registers via SPI +* +* Arguments: +* cc1101: device struct +* reg: address of CC1101 registers to start reading from. +* out: pointer to a buffer that will contain the register values read from the device +* len: size of out, number of bytes to read from the device +* +* Returns: +* Device status +*/ +static unsigned char read_registers(cc1101_t* cc1101, unsigned char reg, unsigned char* out, unsigned char len) { + struct spi_transfer t = {0}; + struct spi_message m; + unsigned char transaction[FIFO_LEN + 1]; + + transaction[0] = reg | SPI_BURST | SPI_READ; + + t.tx_buf = &transaction; + t.rx_buf = &transaction; + t.len = len + 1; + t.cs_change = 0; + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + spi_sync(cc1101->spi, &m); + + memcpy(out, &transaction[1], len); + + return transaction[0]; +} + +/* +* Read a sequence of bytes from the device's config registers via SPI +* +* Arguments: +* cc1101: device struct +* registers: pointer to a buffer that will contain the values read from the device +* len: size of out, number of bytes to read from the device +* +* Returns: +* Device status +* +*/ +unsigned char cc1101_spi_read_config_registers(cc1101_t* cc1101, unsigned char* registers, unsigned char len) { + BUG_ON(len > CONFIG_REGISTERS_LEN); + return read_registers(cc1101, 0x00, registers, len); +} + +/* +* Read a sequence of bytes from the device's PATABLE via SPI +* +* Arguments: +* cc1101: device struct +* patable: pointer to a buffer that will contain the values read from the device +* len: size of out, number of bytes to read from the device +* +* Returns: +* Device status +*/ +unsigned char cc1101_spi_read_patable(cc1101_t* cc1101, unsigned char* patable, unsigned char len) { + BUG_ON(len > PATABLE_LEN); + return read_registers(cc1101, PATABLE, patable, len); +} + +/* +* Read a sequence of bytes from the device's RXFIFO via SPI +* +* Arguments: +* cc1101: device struct +* rx_bytes: pointer to a buffer that will contain the values read from the device +* len: size of out, number of bytes to read from the device +* +* Returns: +* Device status +*/ +unsigned char cc1101_spi_read_rxfifo(cc1101_t* cc1101, unsigned char* rx_bytes, unsigned char len) { + BUG_ON(len > FIFO_LEN); + return read_registers(cc1101, FIFO, rx_bytes, len); +} + +/* +* Write a sequence of bytes to the device's registers via SPI +* +* Arguments: +* cc1101: device struct +* reg: address of CC1101 registers to start writing to. Must have the burst bit set +* in: pointer to a buffer containing a byte sequence to write to the device registers +* len: number of bytes in buffer that will be written to the device +* +* Returns: +* Device status +*/ +static unsigned char write_registers(cc1101_t* cc1101, unsigned char reg, const unsigned char* in, unsigned char len) { + struct spi_transfer t = {0}; + struct spi_message m; + unsigned char transaction[FIFO_LEN + 1]; + + transaction[0] = reg | SPI_BURST; + memcpy(&transaction[1], in, len); + + t.tx_buf = &transaction; + t.rx_buf = &transaction; + t.len = len + 1; + t.cs_change = 0; + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + spi_sync(cc1101->spi, &m); + + return transaction[0]; +} + +/* +* Write a sequence of bytes to the device's configuration registers via SPI +* +* Arguments: +* cc1101: device struct +* registers: pointer to a buffer containing a byte sequence to write to the device registers +* len: number of bytes in buffer that will be written to the device +* +* Returns: +* Device status +*/ +unsigned char cc1101_spi_write_config_registers(cc1101_t* cc1101, const unsigned char* registers, unsigned char len) { + BUG_ON(len > CONFIG_REGISTERS_LEN); + return write_registers(cc1101, 0x00, registers, len); +} + +/* +* Write a sequence of bytes to the device's PATABLE registers via SPI +* +* Arguments: +* cc1101: device struct +* patable: pointer to a buffer containing a byte sequence to write to the PATABLE +* len: number of bytes in buffer that will be written to the device +* +* Returns: +* Device status +*/ +unsigned char cc1101_spi_write_patable(cc1101_t* cc1101, const unsigned char* patable, unsigned char len) { + BUG_ON(len > PATABLE_LEN); + return write_registers(cc1101, PATABLE, patable, len); +} + +/* +* Write a sequence of bytes to the device's TXFIFO via SPI +* +* Arguments: +* cc1101: device struct +* tx_bytes: pointer to a buffer containing a byte sequence to write to the TXFIFO +* len: number of bytes in buffer that will be written to the device +* +* Returns: +* Device status +*/ +unsigned char cc1101_spi_write_txfifo(cc1101_t* cc1101, const unsigned char* tx_bytes, unsigned char len) { + BUG_ON(len > FIFO_LEN); + return write_registers(cc1101, FIFO, tx_bytes, len); +} + +/* +* Sends a command to the device via SPI +* +* Arguments: +* cc1101: device struct +* command: command to send to the device +* +* Returns: +* Device status +*/ +unsigned char cc1101_spi_send_command(cc1101_t* cc1101, unsigned char command) { + struct spi_transfer t = {0}; + struct spi_message m; + unsigned char transaction; + + WARN_ON(command < 0x30 || command > 0x3D); + + transaction = command; + + t.tx_buf = &transaction; + t.rx_buf = &transaction; + t.len = 1; + t.cs_change = 0; + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + spi_sync(cc1101->spi, &m); + + return transaction; +} + +/* +* Read a single byte from one of the device's status registers via SPI +* +* Arguments: +* cc1101: device struct +* reg: address of CC1101 status register to read +* +* Returns: +* Transaction struct containing register contents and device status +*/ +spi_transaction_t cc1101_spi_read_status_register_once(cc1101_t* cc1101, unsigned char reg) { + struct spi_transfer t = {0}; + struct spi_message m; + spi_transaction_t transaction; + + WARN_ON(reg < 0x30 || reg > 0x3D); + + // Status registers require the burst bit to be set + transaction.header = reg | SPI_BURST | SPI_READ; + transaction.data = 0; + + t.tx_buf = &transaction; + t.rx_buf = &transaction; + t.len = 2; + t.cs_change = 0; + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + spi_sync(cc1101->spi, &m); + + return transaction; +} + +/* +* Read a single byte from one of the device's status registers via SPI +* +* The double-read implemented here is recommended by the CC1101 Errata to prevent corrupt values +* from being read from the device. +* +* Arguments: +* cc1101: device struct +* reg: address of CC1101 status register to read +* +* Returns: +* Transaction struct containing register contents and device status +*/ +spi_transaction_t cc1101_spi_read_status_register(cc1101_t* cc1101, unsigned char reg) { + spi_transaction_t result, result_check; + + // Read status register + result = cc1101_spi_read_status_register_once(cc1101, reg); + while (1) { + // Read again + result_check = cc1101_spi_read_status_register_once(cc1101, reg); + + // If the values match, the value was valid + if (result.data == result_check.data) { + break; + } + // If not, continue until two reads agree + else { + result = result_check; + } + } + + return result; } \ No newline at end of file diff --git a/cc1101_spi.h b/cc1101_spi.h index 0e4ecc3..388b819 100755 --- a/cc1101_spi.h +++ b/cc1101_spi.h @@ -1,32 +1,32 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* -* Copyright (c) 2021 -*/ -#ifndef CC1101_SPI_H -#define CC1101_SPI_H - -#include - -#include "cc1101_internal.h" - -typedef struct { - unsigned char header; - unsigned char data; -} spi_transaction_t; - -spi_transaction_t cc1101_spi_read_config_register(cc1101_t* cc1101, unsigned char reg); -unsigned char cc1101_spi_write_config_register(cc1101_t* cc1101, unsigned char reg, unsigned char value); - -unsigned char cc1101_spi_read_config_registers(cc1101_t* cc1101, unsigned char* registers, unsigned char len); -unsigned char cc1101_spi_read_patable(cc1101_t* cc1101, unsigned char* patable, unsigned char len); -unsigned char cc1101_spi_read_rxfifo(cc1101_t* cc1101, unsigned char* rx_bytes, unsigned char len); - -unsigned char cc1101_spi_write_config_registers(cc1101_t* cc1101, const unsigned char* registers, unsigned char len); -unsigned char cc1101_spi_write_patable(cc1101_t* cc1101, const unsigned char* patable, unsigned char len); -unsigned char cc1101_spi_write_txfifo(cc1101_t* cc1101, const unsigned char* tx_bytes, unsigned char len); - -spi_transaction_t cc1101_spi_read_status_register_once(cc1101_t* cc1101, unsigned char reg); -spi_transaction_t cc1101_spi_read_status_register(cc1101_t* cc1101, unsigned char reg); -unsigned char cc1101_spi_send_command(cc1101_t* cc1101, unsigned char command); - +// SPDX-License-Identifier: GPL-2.0-only +/* +* Copyright (c) 2021 +*/ +#ifndef CC1101_SPI_H +#define CC1101_SPI_H + +#include + +#include "cc1101_internal.h" + +typedef struct { + unsigned char header; + unsigned char data; +} spi_transaction_t; + +spi_transaction_t cc1101_spi_read_config_register(cc1101_t* cc1101, unsigned char reg); +unsigned char cc1101_spi_write_config_register(cc1101_t* cc1101, unsigned char reg, unsigned char value); + +unsigned char cc1101_spi_read_config_registers(cc1101_t* cc1101, unsigned char* registers, unsigned char len); +unsigned char cc1101_spi_read_patable(cc1101_t* cc1101, unsigned char* patable, unsigned char len); +unsigned char cc1101_spi_read_rxfifo(cc1101_t* cc1101, unsigned char* rx_bytes, unsigned char len); + +unsigned char cc1101_spi_write_config_registers(cc1101_t* cc1101, const unsigned char* registers, unsigned char len); +unsigned char cc1101_spi_write_patable(cc1101_t* cc1101, const unsigned char* patable, unsigned char len); +unsigned char cc1101_spi_write_txfifo(cc1101_t* cc1101, const unsigned char* tx_bytes, unsigned char len); + +spi_transaction_t cc1101_spi_read_status_register_once(cc1101_t* cc1101, unsigned char reg); +spi_transaction_t cc1101_spi_read_status_register(cc1101_t* cc1101, unsigned char reg); +unsigned char cc1101_spi_send_command(cc1101_t* cc1101, unsigned char command); + #endif \ No newline at end of file -- 2.49.0