├── .gitattributes ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── beepy-kbd.dts ├── beepy-kbd.map └── src ├── bbq10kbd_featherwing_codes.h ├── bbq10kbd_pmod_codes.h ├── bbq20kbd_pmod_codes.h ├── bbqX0kbd_registers.h ├── config.h ├── debug_levels.h ├── i2c_helper.h ├── indicators.h ├── input_display.c ├── input_fw.c ├── input_iface.c ├── input_iface.h ├── input_meta.c ├── input_modifiers.c ├── input_rtc.c ├── input_touch.c ├── main.c ├── params_iface.c ├── params_iface.h ├── registers.h ├── sysfs_iface.c └── sysfs_iface.h /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.vscode/* 2 | *.vscode-ctags 3 | *.json 4 | *.ko 5 | *.dtbo 6 | *.symvers 7 | *.mod 8 | *.mod.c 9 | *.mod.o 10 | *.o 11 | *.log 12 | *.order 13 | *.cmd 14 | *.o.d 15 | *.tmp 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 2018 khawes 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | obj-m += beepy-kbd.o 2 | beepy-kbd-objs += src/main.o src/params_iface.o src/sysfs_iface.o \ 3 | src/input_iface.o src/input_fw.o src/input_rtc.o src/input_display.o \ 4 | src/input_modifiers.o src/input_touch.o src/input_meta.o 5 | ccflags-y := -g -std=gnu99 -Wno-declaration-after-statement 6 | 7 | .PHONY: all clean install install_modules install_aux uninstall 8 | 9 | # KERNELRELEASE is set by DKMS, can be different inside chroot 10 | ifeq ($(KERNELRELEASE),) 11 | KERNELRELEASE := $(shell uname -r) 12 | endif 13 | # LINUX_DIR is set by Buildroot 14 | ifeq ($(LINUX_DIR),) 15 | LINUX_DIR := /lib/modules/$(KERNELRELEASE)/build 16 | endif 17 | 18 | # BUILD_DIR is set by DKMS, but not if running manually 19 | ifeq ($(BUILD_DIR),) 20 | BUILD_DIR := . 21 | endif 22 | 23 | BOOT_CONFIG_LINE := dtoverlay=beepy-kbd,irq_pin=4 24 | KMAP_LINE := KMAP=/usr/share/kbd/keymaps/beepy-kbd.map 25 | 26 | # Raspbian 12 moved config and cmdline to firmware 27 | ifeq ($(wildcard /boot/firmware/config.txt),) 28 | CONFIG=/boot/config.txt 29 | else 30 | CONFIG=/boot/firmware/config.txt 31 | endif 32 | 33 | all: beepy-kbd.dtbo 34 | $(MAKE) -C '$(LINUX_DIR)' M='$(shell pwd)' 35 | 36 | beepy-kbd.dtbo: beepy-kbd.dts 37 | dtc -@ -I dts -O dtb -W no-unit_address_vs_reg -o $@ $< 38 | 39 | install_modules: 40 | $(MAKE) -C '$(LINUX_DIR)' M='$(shell pwd)' modules_install 41 | # Rebuild dependencies 42 | depmod -A 43 | 44 | install: install_modules install_aux 45 | 46 | # Separate rule to be called from DKMS 47 | install_aux: beepy-kbd.dtbo 48 | # Install keymap 49 | @mkdir -p /usr/share/kbd/keymaps/ 50 | install -D -m 0644 $(BUILD_DIR)/beepy-kbd.map /usr/share/kbd/keymaps/ 51 | # Install device tree overlay 52 | install -D -m 0644 $(BUILD_DIR)/beepy-kbd.dtbo /boot/overlays/ 53 | # Add configuration line if it wasn't already there 54 | @grep -qxF '$(BOOT_CONFIG_LINE)' $(CONFIG) \ 55 | || printf '[all]\ndtparam=i2c_arm=on\n$(BOOT_CONFIG_LINE)\n' >> $(CONFIG) 56 | # Add auto-load module line if it wasn't already there 57 | @grep -qxF 'beepy-kbd' /etc/modules \ 58 | || printf 'i2c-dev\nbeepy-kbd\n' >> /etc/modules 59 | # Configure keymap as default 60 | @grep -qxF '$(KMAP_LINE)' /etc/default/keyboard \ 61 | || echo '$(KMAP_LINE)' >> /etc/default/keyboard 62 | @rm -f /etc/console-setup/cached_setup_keyboard.sh 63 | @DEBIAN_FRONTEND=noninteractive dpkg-reconfigure keyboard-configuration \ 64 | || echo "dpkg-reconfigure failed, keymap may not be applied" 65 | 66 | uninstall: 67 | # Remove auto-load module line and create a backup file 68 | @sed -i.save '/beepy-kbd/d' /etc/modules 69 | # Remove configuration line and create a backup file 70 | @sed -i.save '/$(BOOT_CONFIG_LINE)/d' $(CONFIG) 71 | # Remove device tree overlay 72 | rm -f /boot/overlays/beepy-kbd.dtbo 73 | # Remove keymap 74 | rm -f /usr/share/kbd/keymaps/beepy-kbd.map 75 | # Remove keymap setting 76 | @sed -i.save '\|$(KMAP_LINE)|d' /etc/default/keyboard 77 | rm -f /etc/console-setup/cached_setup_keyboard.sh 78 | @DEBIAN_FRONTEND=noninteractive dpkg-reconfigure keyboard-configuration \ 79 | || echo "dpkg-reconfigure failed, old keymap may not be applied" 80 | 81 | clean: 82 | $(MAKE) -C '$(LINUX_DIR)' M='$(shell pwd)' clean 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Beepy Keyboard Driver 3 | layout: default 4 | --- 5 | 6 | # Beepy Keyboard Driver 7 | 8 | - [User Guide](#user-guide) 9 | - [Indicators](#indicators) 10 | - [Basic key mappings](#basic-key-mappings) 11 | - [Sticky modifier keys](#sticky-modifier-keys) 12 | - [Meta mode](#meta-mode) 13 | - [Touchpad mode](#touchpad-mode) 14 | - [`sysfs` interface](#sysfs-interface) 15 | - [Module parameters](#module-parameters) 16 | - [Custom keymap](#custom-keymap) 17 | - [Developer Reference](#developer-reference) 18 | - [Building from source](#building-from-source) 19 | - [Direct write firmware updates](#direct-write-firmware-updates) 20 | 21 | ## User Guide 22 | 23 | The Beepy keyboard driver, `beepy-kbd`, controls interaction between the [RP2040 firmware `beepy-fw`](beepy-fw.html) and Linux running on the Raspberry Pi. It has several responsibilities, including 24 | 25 | * Communicating keyboard and touchpad input to Linux 26 | * Power and standby management 27 | * Synchronizing the real-time clock 28 | * Firmware update management 29 | 30 | ### Indicators 31 | 32 | Due to the limited number of keys, there are different shortcuts and modes mapped by the keyboard driver. You will see different types of indicators in the top right corner of the screen depending on the mode: 33 | 34 | * Shift indicator [Shift modifier](#sticky-modifier-keys) 35 | * Physical Alt indicator [Physical Alt modifier](#sticky-modifier-keys) 36 | * Control indicator [Control modifier](#basic-key-mappings) 37 | * Alt indicator [Alt modifier](#key-mappings) 38 | * Symbol indicator [Symbol mode modifier](#alt-and-sym-modifiers) 39 | * Meta mode indicator [Meta mode modifier](#meta-mode) 40 | * Touchpad mode indicator [Touchpad mode modifier](#touchpad-mode) 41 | 42 | ### Basic key mappings 43 | 44 | From left to right, the top row of the Beepy keyboard has the following keys: 45 | 46 | * "Call", a phone facing up. 47 | * Single click: Enter Control key ([sticky modifier](#sticky-modifier-keys)). 48 | * Short hold: Lock Control key ([sticky modifier](#sticky-modifier-keys)). 49 | * "Berry": a collection of sections shaped like a fruit. 50 | * Single click: Enter [Meta mode](#meta-mode). 51 | * Short hold (1s): Display [Meta mode reference overlay](#meta-mode). 52 | * "Touchpad": pressing the touchpad sensor will click and produce a key event. 53 | * Single click: by default, enable [touchpad mode](#touchpad-mode), sending arrow keys. If touchpad mode is already on, a single click will send `Enter`. 54 | * "Back": an arrow looping back onto itself. 55 | * Single click: send `Escape`. Commonly used to exit menus in utilities. 56 | * "End Call": a phone facing down, with a line underneath. 57 | * Single click: Send the [tmux prefix](beepy-tmux-menus.html). Prefix is [configurable in the driver keymap](#custom-keymaps). 58 | * Short hold (1s): Open the [tmux menu](beepy-tmux-menus.html). If the Pi has been shut down, a short hold will turn the Pi back on. 59 | * Long hold (5s): send a shutdown signal to the Pi. 60 | 61 | The alternate symbols printed directly on the keys are sent by pressing the `Physical Alt` key on the bottom left corner of the keyboard, then pressing the key on which the desired symbol is printed. While `Physical Alt` is active, you will see an `a` indicator in the top right corner of the screen: Physical Alt indicator. The combination `Physical Alt` + `Enter` is also mapped to `Tab`. `Physical Alt` is a "[sticky modifier key](#sticky-modifier-keys)". 62 | 63 | For additional symbols not printed directly on the keys, use the `Symbol` key on the bottom row of the keyboard. While `Symbol` is active, you will see an `S` indicator in the top right of the screen: Symbol indicator. Internally, `Symbol` sends AltGr (Right Alt), which is mapped to more symbols via the keymap file at `/usr/share/kbd/keymaps/beepy-kbd.map`. `Symbol` is a "[sticky modifier key](#sticky-modifier-keys)". You can view the Symbol key map by holding the `Symbol` key for 1 second. Modifying the keymap file will also update the Symbol key map displayed; below is the default key map: 64 | 65 | ![Default Symbol key map](assets/overlay-symbol.png) 66 | 67 | Arrow keys and other movement keys such as Page Up and Page Down are accessible in [Meta mode](#meta-mode). 68 | 69 | ### Sticky modifier keys 70 | 71 | For easier typing, the keyboard driver implements sticky modifier keys. Pressing and releasing a modifier applies the modifier to the next alpha keypress only. If the same modifier key is pressed and released again, it will be canceled. 72 | 73 | Holding a modifier key while typing an alpha key will apply the modifier to all alpha keys until the modifier is released. 74 | 75 | While a modifier key is active, [visual indicators](#indicators) are drawn in the top right corner of the display, with indicators for Shift indicator [Shift](#sticky-modifier-keys), Physical Alt indicator [Physical Alt](#sticky-modifier-keys), Control indicator [Control](#basic-key-mappings), Alt indicator [Alt](#key-mappings), Symbol indicator [Symbol](#alt-and-sym-modifiers), Meta mode indicator [Meta mode](#meta-mode), and Touchpad mode indicator [Touchpad mode](#touchpad-mode). 76 | 77 | For example, to type a dot `.`, you can use `Physical Alt` sticky mode: 78 | 79 | * Press and release the `Physical Alt` key 80 | * The `a` indicator Physical Alt indicator appears, showing that `Physical Alt` mode will be applied to the next keypress only 81 | * Type the `M` key to input a dot `.` 82 | * The `a` indicator disappears 83 | 84 | Alternatively, to type a dot `.` followed by a comma `,`, you can also press and hold `Physical Alt`: 85 | 86 | * Press the `Physical Alt` key 87 | * The `a` indicator Physical Alt indicator appears, showing that `Physical Alt` mode is active 88 | * While holding `Physical Alt`, type the `M` key to input a dot `.` 89 | * While holding `Physical Alt`, type the `N` key to input a comma `,` 90 | * Release the `Physical Alt` key 91 | * The `a` indicator disappears 92 | 93 | ### Meta mode 94 | 95 | Meta mode is a modal layer that assists in rapidly moving the cursor and scrolling with single keypresses. To enter Meta mode, click the `Berry` key once. The Meta mode indicator Meta mode indicator will appear in the top right corner of the screen, and the following keymap will be applied until Meta mode is dismissed using the `Back` key, or otherwise noted: 96 | 97 | - `E` Up `S` Down `W` Left `D` Right 98 | - Why not `WASD`? This way, you can place your thumb in the middle of all four of these keys, and fluidly move the cursor without mistyping on e.g. `Q` or `E` 99 | - `R` Home `F` End `O` Page Up `P` Page Down 100 | - `Q` Back one word (`Alt + Left`) `A` Forward one word (`Alt + Right`) 101 | - `T` Tab (dismisses Meta mode) 102 | - `X` Apply `Control` to next key (dismisses Meta mode) 103 | - `C` Apply `Alt` to next key (dismisses Meta mode) 104 | - `0` Toggle display inversion from white-on-black to black-on-white 105 | - `N` Decrease keyboard brightness 106 | - `M` Increase keyboard brightness 107 | - `$` Toggle keyboard backlight 108 | - `Back` Exit Meta mode 109 | 110 | Typing any other key while in Meta mode will exit Meta mode and send the key as if it was typed normally. 111 | 112 | You can view the Meta mode key map by holding the `Berry` key for 1 second: 113 | 114 | ![Meta mode key map](assets/overlay-meta.png) 115 | 116 | ### Touchpad mode 117 | 118 | The Beepy touchpad is not actually touch sensitive, rather it is an optical trackpad. This has the downside of sending touchpad input if material other than a finger moves across it, such as a pocket. To reduce false positives, the default mode of the Beepy touchpad is to remain off until a key is used to turn it on. Touchpad behavior, including mouse and activation, can be configured via [module parameters](#module-parameters). 119 | 120 | Press the touchpad itself to turn on touchpad mode, and start sending arrow keys when you move your finger across the touchpad. While active, you will see the touchpad indicator Touchpad indicator in the top-right corner of the screen. 121 | 122 | Clicking the touchpad itself again while the touchpad is active will send `Enter`. Pressing the `Back` key will exit touchpad mode. 123 | 124 | You can also hold the `Shift` key to temporarily turn on the touchpad until the `Shift` key is released. You will see the Shift indicator Shift indicator instead of the touch indicator. 125 | 126 | If you release the `Shift` key *without* using the touchpad, you will instead get the [sticky modifier behavior](#sticky-modifier-keys) of applying Shift to the next alpha keypress. In this case, the Shift indicator will remain on the screen. Press and release the `Shift` key again to un-stick the modifier and hide the indicator. 127 | 128 | ### `sysfs` interface 129 | 130 | The keyboard driver creates several sysfs entries under `/sys/firmware/beepy` to expose different parts of the firmware. These entries can be manipulated like a normal file using traditional Unix tools such as `cat` and `tee`, and in shell scripts. 131 | 132 | * `battery_raw` Raw ADC output value for the battery level. Read-only. 133 | * `battery_volts` Approximate battery voltage. Read-only. 134 | * `battery_percent` Approximate battery percentage remaining. Read-only. 135 | * `led_red`, `led_green`, `led_blue` set LED color intensity from 0 to 255. Apply by writing to `led`. Write-only. 136 | * `led`: Also applies color settings. Write-only. 137 | - `0` Turn off LED. 138 | - `1` Turn on LED. 139 | - `2` Flash LED. 140 | - `3` Flash LED until key pressed. Overlays on top of an existing LED setting. For example, the following write sequence will set the LED to be solid red, but with a blue flash. Then, when a key is pressed, it will return to a solid red. 141 | 142 | # Set LED to solid red 143 | echo 255 | sudo tee /sys/firmware/beepy/led_red 144 | echo 0 | sudo tee /sys/firmware/beepy/led_green 145 | echo 0 | sudo tee /sys/firmware/beepy/led_blue 146 | echo 1 | sudo tee /sys/firmware/beepy/led 147 | 148 | # Set LED to flash blue until key pressed 149 | echo 0 | sudo tee /sys/firmware/beepy/led_red 150 | echo 0 | sudo tee /sys/firmware/beepy/led_green 151 | echo 255 | sudo tee /sys/firmware/beepy/led_blue 152 | echo 3 | sudo tee /sys/firmware/beepy/led 153 | 154 | * `keyboard_backlight` Set keyboard brightness from 0 to 255. Write-only. The default brightness is low, but still on. Setting the brightness to maximum will noticeably increase power draw. 155 | * `rewake_timer` Write to shut down the Pi, then power-on in that many minutes. Write-only. Useful for polling services in conjunction with `startup_reason`, such as with the [beepy-poll](beepy-poll.html) service. 156 | * `startup_reason` Contains the reason why the Pi was booted. Useful for polling services in conjunction with `rewake_timer`. 157 | - `fw_init` RP2040 initialized and booted Pi. 158 | - `power_button` Power button held to turn Pi back on. 159 | - `rewake` Rewake triggered from `rewake_timer`. 160 | - `rewake_canceled` During rewake polling, 0 was written to `rewake_timer`. This allows the `beepy-poll` service to cancel the poll and proceeded with a full boot. 161 | * `fw_version` Installed firmware version. Read-only. 162 | * `fw_update` Write to update firmware. Read-write See [Firmware updates](#firmware-updates). 163 | * `last_keypress` Milliseconds since last keypress. Read-only. 164 | 165 | ### Module parameters 166 | 167 | Configure various aspects of the driver itself. Write to `/sys/module/beepy_kbd/parameters/` to set with `echo | sudo tee /sys/module/beepy_kbd/parameters/`. Or unload and reload the module with `sudo modprobe -r beepy-kbd; sudo modprobe beepy-kbd param=val`. 168 | 169 | * `touch_act` One of `click` or `always`. 170 | - `click` Default, will disable touchpad until the touchpad button is clicked. 171 | - `always` Touchpad always on, swiping sends touch input, clicking sends `Enter`. 172 | * `touch_as`: one of `keys` or `mouse`. 173 | - `keys` Default, send arrow keys with the touchpad. 174 | - `mouse` Send mouse input (useful for X11). 175 | * `touch_shift` Default on. Send touch input while the Shift key is held. 176 | * `touch_min_squal` Reject touchpad input if surface quality as reported by touchpad sensor is lower than this threshold. Default `16`. 177 | * `touch_led_setting` One of `low`, `med`, `high`. Touchpad LED power setting. `high` is recommended for reliable input. Default `high`. 178 | * `touch_threshold` Touchpad movement amount required to send arrow key. Range `0 - 255`, default `8`. 179 | - `shutdown_grace` To avoid powering off the Pi while it is still running, this is set to the number of seconds to wait between a shutdown signal and the firmware removing power from the Pi. This helps ensure that the Pi has time to process the power-off command and to shut down cleanly. Default `30` seconds. 180 | - `auto_off` In most cases, the keyboard driver is loaded on boot and unloaded during shutdown. For substantial power savings, the default-enabled `auto_off` setting will trigger when the driver is unloaded. After a 30 second wait to allow for the driver to potentially be reloaded, the Pi will shutdown, wait for `shutdown_grace` seconds, then power off the Pi and enter deep sleep. Default on. 181 | * `sharp_path` Sharp DRM device to send overlay commands. Default: `/dev/dri/card0`. 182 | * `sysfs_gid` Group ID of sysfs entries in `/sys/firmware/beepy`. Set this to the result of `id -g` to allow access without `sudo`. Beepy Raspbian configures the first user group by default. 183 | * `handle_poweroff` Enable to have driver invoke `/sbin/poweroff` when power key held. not necessary for Beepy Raspbian, may be necessary if running a custom build of the driver on another Linux distribution. Default off. 184 | 185 | ### Custom keymap 186 | 187 | The `Physical Alt` and `Symbol` keymaps and the Tmux prefix sent by the `Berry` key can be edited in the file `/usr/share/kbd/keymaps/beepy-kbd.map`. To reapply the keymap without rebooting, run `loadkeys /usr/share/kbd/keymaps/beepy-kbd.map`. 188 | 189 | Holding the `Symbol` key to display the keymap will display the keymap directly from this file. 190 | 191 | ## Developer Reference 192 | 193 | ### Building from source 194 | 195 | * Install build prerequisites. 196 | 197 | sudo apt-get install raspberrypi-kernel-headers 198 | 199 | * Enable I2C Peripheral. Run `sudo raspi-config`, then under `Interface Options`, enable `I2C`. 200 | 201 | * Build, install, and enable the kernel module. 202 | 203 | make 204 | sudo make install 205 | 206 | To remove, run `sudo make uninstall`, then verify that `/etc/modules` and `/boot/firmware/config.txt` have removed the driver lines. 207 | 208 | ### Direct write firmware updates 209 | 210 | In most cases, you will use the [`update-beepy-fw` utility](beepy-fw.html#firmware-update-utility) to update firmware from a firmware package or file. However, you can also manually write firmware update data to the sysfs entry at `/sys/firmware/beepy/fw_update`. 211 | 212 | RP2040 firmware is loaded in two stages. The first stage is a modified version of [pico-flashloader](https://github.com/rhulme/pico-flashloader). It allows updates to be flashed to the second stage firmware while booted. The second stage is the actual Beepy firmware. 213 | 214 | Firmware updates are flashed by writing to `/sys/firmware/beepy/fw_update`: 215 | 216 | - Header line beginning with `+` e.g. `+Beepy` 217 | - Followed by the contents of an image in Intel HEX format 218 | 219 | Please wait until the system reboots on its own before removing power. If the update failed, will contain an error code and the firmware will not be modified. 220 | 221 | The header line `+...` will reset the update process, so an interrupted or failed update can be retried by restarting the firmware write. 222 | 223 | If the update completes successfully, the system will be rebooted. 224 | There is a delay configurable at `/sys/firmware/beepy/shutdown_grace` to allow the operating system to cleanly shut down before the Pi is powered off. 225 | The firmware is flashed right before the Pi boots back up, so please wait until the system reboots on its own before removing power. 226 | -------------------------------------------------------------------------------- /beepy-kbd.dts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | /dts-v1/; 3 | /plugin/; 4 | 5 | /{ 6 | compatible = "brcm,bcm2835"; 7 | 8 | fragment@0 { 9 | target = <&i2c1>; 10 | 11 | __overlay__ { 12 | #address-cells = <1>; 13 | #size-cells = <0>; 14 | beepy_kbd: beepy_kbd@1f { 15 | #address-cells = <1>; 16 | compatible = "beepy-kbd"; 17 | reg = <0x1F>; 18 | irq-gpio = <&gpio 11 0x2>; 19 | interrupts = <11 2>; 20 | interrupt-parent = <&gpio>; 21 | interrupt-controller; 22 | #interrupt-cells = <1>; 23 | }; 24 | }; 25 | }; 26 | __overrides__ { 27 | irq_pin = <&beepy_kbd>,"interrupts:0"; 28 | }; 29 | }; 30 | 31 | -------------------------------------------------------------------------------- /beepy-kbd.map: -------------------------------------------------------------------------------- 1 | # Reference 2 | # https://github.com/SFML/SFML/blob/master/src/SFML/Window/Unix/KeySymToUnicodeMapping.cpp 3 | 4 | # Symbol key 5 | altgr keycode 16 = asciitilde 6 | altgr keycode 17 = grave 7 | altgr keycode 18 = braceleft 8 | altgr keycode 19 = braceright 9 | altgr keycode 20 = bracketleft 10 | altgr keycode 21 = bracketright 11 | altgr keycode 22 = less 12 | altgr keycode 23 = greater 13 | altgr keycode 24 = asciicircum 14 | altgr keycode 25 = percent 15 | 16 | altgr keycode 30 = equal 17 | altgr keycode 31 = division 18 | altgr keycode 32 = plusminus 19 | altgr keycode 33 = periodcentered 20 | altgr keycode 34 = backslash 21 | altgr keycode 35 = bar 22 | altgr keycode 36 = ampersand 23 | altgr keycode 37 = quotedbl # leftdoublequotemark 24 | altgr keycode 38 = quotedbl # rightdoublequotemark 25 | 26 | altgr keycode 44 = yen 27 | altgr keycode 45 = euro 28 | altgr keycode 46 = sterling 29 | altgr keycode 47 = questiondown 30 | altgr keycode 48 = exclamdown 31 | altgr keycode 49 = guillemotleft 32 | altgr keycode 50 = guillemotright 33 | 34 | altgr keycode 113 = dollar 35 | 36 | # Press power key sends Control + this code 37 | # Use it for Tmux prefix 38 | control keycode 171 = Control_b 39 | # Short hold power key sends Tmux prefix + this key 40 | control keycode 174 = e 41 | 42 | # Meta Q / A move between words 43 | keycode 172 = F172 44 | keycode 173 = F173 45 | string F172 = "\033[1;5D" 46 | string F173 = "\033[1;5C" 47 | 48 | # Dollar key 49 | keycode 113 = dollar 50 | 51 | # Alt + enter 52 | keycode 147 = Tab 53 | # Alt + backspace 54 | keycode 133 = Delete 55 | # Alt + dollar key 56 | keycode 232 = asciitilde 57 | 58 | # Physical Alt key 59 | keycode 130 = zero 60 | keycode 135 = numbersign 61 | keycode 136 = one 62 | keycode 137 = two 63 | keycode 138 = three 64 | keycode 139 = parenleft 65 | keycode 140 = parenright 66 | keycode 141 = underscore 67 | keycode 142 = minus 68 | keycode 143 = plus 69 | keycode 144 = at 70 | 71 | keycode 149 = asterisk 72 | keycode 150 = four 73 | keycode 151 = five 74 | keycode 152 = six 75 | keycode 153 = slash 76 | keycode 154 = colon 77 | keycode 155 = semicolon 78 | keycode 156 = apostrophe 79 | keycode 157 = quotedbl 80 | 81 | keycode 163 = seven 82 | keycode 164 = eight 83 | keycode 165 = nine 84 | keycode 166 = question 85 | keycode 167 = exclam 86 | keycode 168 = comma 87 | keycode 169 = period 88 | -------------------------------------------------------------------------------- /src/bbq10kbd_featherwing_codes.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 */ 2 | /* 3 | * Keyboard Driver for Blackberry Keyboards BBQ10 from arturo182. Software written by wallComputer. 4 | * bbq10kbd_codes.h: Keyboard Layout and Scancodes-Keycodes mapping. 5 | */ 6 | 7 | #ifndef BBQ10KBD_FEATHERWING_CODES_H_ 8 | #define BBQ10KBD_FEATHERWING_CODES_H_ 9 | 10 | /* 11 | * BBQ10KBD FEATHERWING KEYBOARD LAYOUT 12 | * 13 | * +------+-----+----+----+----+----+----+-----+-----+-------+ 14 | * | | | UP | | | 15 | * | Ctrl | PgDn | LEFT HOME RIGHT | PgUp | MENU | 16 | * | | | DOWN | | | 17 | * +------+-----+----+----+----+----+----+-----+-----+-------+ 18 | * | | 19 | * +------+-----+----+----+----+----+----+-----+-----+-------+ 20 | * |# |1 |2 |3 |( | )|_ | -| +| @| 21 | * | Q | W | E | R | T | Y | U | I | O | P | 22 | * | S+| S-|PgDn|PgUp| \|UP |^ |= |{ |} | 23 | * +------+-----+----+----+----+----+----+-----+-----+-------+ 24 | * |* |4 |5 |6 |/ | :|; | '| "| ESC| 25 | * | A | S | D | F | G | H | J | K | L | BKSP | 26 | * | ?| Sx| [| ]|LEFT|HOME|RGHT|V+ |V- |DLT | 27 | * +------+-----+----+----+----+----+----+-----+-----+-------+ 28 | * | |7 |8 |9 |? | !|, | .| `| | 29 | * |LFTALT| Z | X | C | V | B | N | M | $ | ENTER | 30 | * | | K+| K-| °| <|DOWN|> |MENU |Vx | | 31 | * +------+-----+----+----+----+----+----+-----+-----+-------+ 32 | * | |0 |TAB | | | 33 | * | LEFT_SHIFT | ~ | SPACE |RTALT| RIGHT_SHIFT | 34 | * | | Kx| &| | | 35 | * +------------+----+-------------------+-----+-------------+ 36 | * 37 | * Notes: 38 | * Most Important to know: 39 | * To present a character as mentioned above a key in the above layout, use LFTALT + Key. 40 | * To present a character as mentioned below a key in the above layout, use RTALT + Key. 41 | * 42 | * Changes from arturo182's layout, if you are used to that, it's good to know these changes. 43 | * 1. Sym Key (Scancode 0x1D) on Keyboard is mapped to RTALT. 44 | * 3. The external buttons on the Keyboard Featherwing have been changed as below: 45 | *__3.1 Leftmost Button changed from Menu Key to Left Ctrl Key. 46 | *__3.2 Innet Left Button changed from Left Ctrl Key to Page Up Key. 47 | *__3.3 Inner Right Button changed from Back Key to Page Down Key. 48 | *__3.4 Outer Right Button changed from Left Shift Key to Menu Key. 49 | *__3.5 Center Key of 5-way Button changed from Enter Key to Home Key. 50 | */ 51 | 52 | 53 | #define NUM_KEYCODES 256 54 | 55 | static unsigned short keycodes[NUM_KEYCODES] = { 56 | [0x01] = KEY_UP, 57 | [0x02] = KEY_DOWN, 58 | [0x03] = KEY_LEFT, 59 | [0x04] = KEY_RIGHT, 60 | [0x05] = KEY_HOME, 61 | 62 | [0x06] = KEY_LEFTCTRL, 63 | [0x11] = KEY_PAGEDOWN, 64 | [0x07] = KEY_PAGEUP, 65 | [0x12] = KEY_MENU, 66 | 67 | [0x1A] = KEY_LEFTALT, 68 | [0x1B] = KEY_LEFTSHIFT, 69 | [0x1D] = KEY_RIGHTALT, 70 | [0x1C] = KEY_RIGHTSHIFT, 71 | 72 | ['A'] = KEY_A, 73 | ['B'] = KEY_B, 74 | ['C'] = KEY_C, 75 | ['D'] = KEY_D, 76 | ['E'] = KEY_E, 77 | ['F'] = KEY_F, 78 | ['G'] = KEY_G, 79 | ['H'] = KEY_H, 80 | ['I'] = KEY_I, 81 | ['J'] = KEY_J, 82 | ['K'] = KEY_K, 83 | ['L'] = KEY_L, 84 | ['M'] = KEY_M, 85 | ['N'] = KEY_N, 86 | ['O'] = KEY_O, 87 | ['P'] = KEY_P, 88 | ['Q'] = KEY_Q, 89 | ['R'] = KEY_R, 90 | ['S'] = KEY_S, 91 | ['T'] = KEY_T, 92 | ['U'] = KEY_U, 93 | ['V'] = KEY_V, 94 | ['W'] = KEY_W, 95 | ['X'] = KEY_X, 96 | ['Y'] = KEY_Y, 97 | ['Z'] = KEY_Z, 98 | 99 | [' '] = KEY_SPACE, 100 | ['~'] = KEY_0, 101 | ['$'] = KEY_GRAVE, 102 | 103 | ['\b'] = KEY_BACKSPACE, 104 | ['\n'] = KEY_ENTER, 105 | /* 106 | * As per the kernel, a keyboard needs to indicate, in advance, which key values it can report. 107 | * In order to that, it should have unique scancodes pointing those scancode-keycode pairs. 108 | * With the configuration set for now, the keyboard never outputs lower case letters, numbers, and equal to sign. 109 | * We can use these as bogus scancodes, and map the keys we want the keyboard to say its pressed when modifier keys are used. 110 | * This can change however, in case future software versions of the keyboard micrcontroller itself changes to output other stuff. 111 | */ 112 | ['1'] = KEY_1, 113 | ['2'] = KEY_2, 114 | ['3'] = KEY_3, 115 | ['4'] = KEY_4, 116 | ['5'] = KEY_5, 117 | ['6'] = KEY_6, 118 | ['7'] = KEY_7, 119 | ['8'] = KEY_8, 120 | ['9'] = KEY_9, 121 | ['a'] = KEY_VOLUMEDOWN, 122 | ['b'] = KEY_VOLUMEUP, 123 | ['c'] = KEY_MUTE, 124 | ['d'] = KEY_TAB, 125 | ['e'] = KEY_DELETE, 126 | ['f'] = KEY_ESC, 127 | ['='] = KEY_EQUAL, 128 | }; 129 | 130 | 131 | #endif 132 | -------------------------------------------------------------------------------- /src/bbq10kbd_pmod_codes.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 */ 2 | /* 3 | * Keyboard Driver for Blackberry Keyboards BBQ10 from arturo182. Software written by wallComputer. 4 | * bbq10kbd_codes.h: Keyboard Layout and Scancodes-Keycodes mapping. 5 | */ 6 | 7 | #ifndef BBQ10KBD_PMOD_CODES_H_ 8 | #define BBQ10KBD_PMOD_CODES_H_ 9 | 10 | /* 11 | * BBQ10KBD PMOD KEYBOARD LAYOUT 12 | * 13 | * +------+-----+----+----+----+----+----+-----+-----+-------+ 14 | * |# |1 |2 |3 |( | )|_ | -| +| @| 15 | * | Q | W | E | R | T | Y | U | I | O | P | 16 | * | S+| S-|PgUp|PgDn| \|UP |^ |= |{ |} | 17 | * +------+-----+----+----+----+----+----+-----+-----+-------+ 18 | * |* |4 |5 |6 |/ | :|; | '| "| ESC| 19 | * | A | S | D | F | G | H | J | K | L | BKSP | 20 | * | ?| Sx| [| ]|LEFT|HOME|RGHT|V+ |V- |DLT | 21 | * +------+-----+----+----+----+----+----+-----+-----+-------+ 22 | * | |7 |8 |9 |? | !|, | .| `| | 23 | * |LFTALT| Z | X | C | V | B | N | M | $ | ENTER | 24 | * | | K+| K-| °| <|DOWN|> |MENU |Vx | | 25 | * +------+-----+----+----+----+----+----+-----+-----+-------+ 26 | * | |0 |TAO | | | 27 | * | LEFT_SHIFT | ~ | SPACE |RTALT| RIGHT_SHIFT | 28 | * | | Kx| &| | | 29 | * +------------+----+-------------------+-----+-------------+ 30 | * 31 | * Notes: 32 | * Most Important to know: 33 | * To present a character as mentioned above a key in the above layout, use LFTALT + Key. 34 | * To present a character as mentioned below a key in the above layout, use RTALT + Key. 35 | * 36 | * Changes from arturo182's layout, if you are used to that, it's good to know these changes. 37 | * 1. Sym Key (Scancode 0x1D) on Keyboard is mapped to RTALT. 38 | * 2. Note "|" above Enter Key as per original keyboard layout is removed and added under "A" 39 | * 3. The external buttons on the Keyboard Featherwing have been changed as below: 40 | *__3.1 Leftmost Button changed from Menu Key to Left Ctrl Key. 41 | *__3.2 Innet Left Button changed from Left Ctrl Key to Page Up Key. 42 | *__3.3 Inner Right Button changed from Back Key to Page Down Key. 43 | *__3.4 Outer Right Button changed from Left Shift Key to Menu Key. 44 | *__3.5 Center Key of 5-way Button changed from Enter Key to Home Key. 45 | */ 46 | 47 | 48 | #define NUM_KEYCODES 256 49 | 50 | static unsigned short keycodes[NUM_KEYCODES] = { 51 | 52 | 53 | [0x1A] = KEY_LEFTALT, 54 | [0x1B] = KEY_LEFTSHIFT, 55 | [0x1D] = KEY_RIGHTALT, 56 | [0x1C] = KEY_RIGHTSHIFT, 57 | 58 | ['A'] = KEY_A, 59 | ['B'] = KEY_B, 60 | ['C'] = KEY_C, 61 | ['D'] = KEY_D, 62 | ['E'] = KEY_E, 63 | ['F'] = KEY_F, 64 | ['G'] = KEY_G, 65 | ['H'] = KEY_H, 66 | ['I'] = KEY_I, 67 | ['J'] = KEY_J, 68 | ['K'] = KEY_K, 69 | ['L'] = KEY_L, 70 | ['M'] = KEY_M, 71 | ['N'] = KEY_N, 72 | ['O'] = KEY_O, 73 | ['P'] = KEY_P, 74 | ['Q'] = KEY_Q, 75 | ['R'] = KEY_R, 76 | ['S'] = KEY_S, 77 | ['T'] = KEY_T, 78 | ['U'] = KEY_U, 79 | ['V'] = KEY_V, 80 | ['W'] = KEY_W, 81 | ['X'] = KEY_X, 82 | ['Y'] = KEY_Y, 83 | ['Z'] = KEY_Z, 84 | 85 | [' '] = KEY_SPACE, 86 | ['~'] = KEY_0, 87 | ['$'] = KEY_GRAVE, 88 | 89 | ['\b'] = KEY_BACKSPACE, 90 | ['\n'] = KEY_ENTER, 91 | /* 92 | * As per the kernel, a keyboard needs to indicate, in advance, which key values it can report. 93 | * In order to that, it should have unique scancodes pointing those scancode-keycode pairs. 94 | * With the configuration set for now, the keyboard never outputs lower case letters, numbers, and equal to sign. 95 | * We can use these as bogus scancodes, and map the keys we want the keyboard to say its pressed when modifier keys are used. 96 | * This can change however, in case future software versions of the keyboard micrcontroller itself changes to output other stuff. 97 | */ 98 | ['1'] = KEY_1, 99 | ['2'] = KEY_2, 100 | ['3'] = KEY_3, 101 | ['4'] = KEY_4, 102 | ['5'] = KEY_5, 103 | ['6'] = KEY_6, 104 | ['7'] = KEY_7, 105 | ['8'] = KEY_8, 106 | ['9'] = KEY_9, 107 | ['a'] = KEY_VOLUMEDOWN, 108 | ['b'] = KEY_VOLUMEUP, 109 | ['c'] = KEY_MUTE, 110 | ['d'] = KEY_TAB, 111 | ['e'] = KEY_DELETE, 112 | ['f'] = KEY_ESC, 113 | ['='] = KEY_EQUAL, 114 | ['g'] = KEY_UP, 115 | ['h'] = KEY_DOWN, 116 | ['i'] = KEY_LEFT, 117 | ['j'] = KEY_RIGHT, 118 | ['k'] = KEY_HOME, 119 | ['m'] = KEY_PAGEUP, 120 | ['n'] = KEY_PAGEDOWN, 121 | }; 122 | 123 | 124 | #endif 125 | -------------------------------------------------------------------------------- /src/bbq20kbd_pmod_codes.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 */ 2 | /* 3 | * Keyboard Driver for Blackberry Keyboards BBQ10 from arturo182. Software written by wallComputer. 4 | * bbq10kbd_codes.h: Keyboard Layout and Scancodes-Keycodes mapping. 5 | */ 6 | 7 | #ifndef BBQ20KBD_PMOD_CODES_H_ 8 | #define BBQ20KBD_PMOD_CODES_H_ 9 | 10 | #include 11 | 12 | #define NUM_KEYCODES 256 13 | 14 | static const uint8_t keycodes[NUM_KEYCODES] = { 15 | 16 | // Map USB HID scancodes to Linux keycode 17 | [4] = KEY_A, 18 | [5] = KEY_B, 19 | [6] = KEY_C, 20 | [7] = KEY_D, 21 | [8] = KEY_E, 22 | [9] = KEY_F, 23 | [10] = KEY_G, 24 | [11] = KEY_H, 25 | [12] = KEY_I, 26 | [13] = KEY_J, 27 | [14] = KEY_K, 28 | [15] = KEY_L, 29 | [16] = KEY_M, 30 | [17] = KEY_N, 31 | [18] = KEY_O, 32 | [19] = KEY_P, 33 | [20] = KEY_Q, 34 | [21] = KEY_R, 35 | [22] = KEY_S, 36 | [23] = KEY_T, 37 | [24] = KEY_U, 38 | [25] = KEY_V, 39 | [26] = KEY_W, 40 | [27] = KEY_X, 41 | [28] = KEY_Y, 42 | [29] = KEY_Z, 43 | [30] = KEY_1, 44 | [31] = KEY_2, 45 | [33] = KEY_4, 46 | [34] = KEY_5, 47 | [35] = KEY_6, 48 | [36] = KEY_7, 49 | [37] = KEY_8, 50 | [38] = KEY_9, 51 | [39] = KEY_0, 52 | [40] = KEY_ENTER, 53 | [41] = KEY_ESC, 54 | [42] = KEY_BACKSPACE, 55 | [43] = KEY_TAB, 56 | [44] = KEY_SPACE, 57 | [45] = KEY_MINUS, 58 | [46] = KEY_EQUAL, 59 | [47] = KEY_LEFTBRACE, 60 | [48] = KEY_RIGHTBRACE, 61 | [49] = KEY_BACKSLASH, 62 | // No HASHTILDE 63 | [51] = KEY_SEMICOLON, 64 | [52] = KEY_APOSTROPHE, 65 | [53] = KEY_GRAVE, 66 | [54] = KEY_COMMA, 67 | [55] = KEY_DOT, 68 | [56] = KEY_SLASH, 69 | [57] = KEY_CAPSLOCK, 70 | [58] = KEY_F1, 71 | [59] = KEY_F2, 72 | [60] = KEY_F3, 73 | [61] = KEY_F4, 74 | [62] = KEY_F5, 75 | [63] = KEY_F6, 76 | [64] = KEY_F7, 77 | [65] = KEY_F8, 78 | [66] = KEY_F9, 79 | [67] = KEY_F10, 80 | [68] = KEY_F11, 81 | [69] = KEY_F12, 82 | [70] = KEY_SYSRQ, 83 | [71] = KEY_SCROLLLOCK, 84 | [72] = KEY_PAUSE, 85 | [73] = KEY_INSERT, 86 | [74] = KEY_HOME, 87 | [75] = KEY_PAGEUP, 88 | [76] = KEY_DELETE, 89 | [77] = KEY_END, 90 | [78] = KEY_PAGEDOWN, 91 | [79] = KEY_RIGHT, 92 | [80] = KEY_LEFT, 93 | [81] = KEY_DOWN, 94 | [82] = KEY_UP, 95 | [83] = KEY_NUMLOCK, 96 | [84] = KEY_KPSLASH, 97 | [85] = KEY_KPASTERISK, 98 | [86] = KEY_KPMINUS, 99 | [87] = KEY_KPPLUS, 100 | [88] = KEY_KPENTER, 101 | [89] = KEY_KP1, 102 | [90] = KEY_KP2, 103 | [91] = KEY_KP3, 104 | [92] = KEY_KP4, 105 | [93] = KEY_KP5, 106 | [94] = KEY_KP6, 107 | [95] = KEY_KP7, 108 | [96] = KEY_KP8, 109 | [97] = KEY_KP9, 110 | [98] = KEY_KP0, 111 | [99] = KEY_KPDOT, 112 | [100] = KEY_102ND, 113 | [101] = KEY_COMPOSE, 114 | [102] = KEY_POWER, 115 | [103] = KEY_KPEQUAL, 116 | [104] = KEY_F13, 117 | [105] = KEY_F14, 118 | [106] = KEY_F15, 119 | [107] = KEY_F16, 120 | [108] = KEY_F17, 121 | [109] = KEY_F18, 122 | [110] = KEY_F19, 123 | [111] = KEY_F20, 124 | [112] = KEY_F21, 125 | [113] = KEY_F22, 126 | [114] = KEY_F23, 127 | [115] = KEY_F24, 128 | [116] = KEY_OPEN, 129 | [117] = KEY_HELP, 130 | [118] = KEY_PROPS, 131 | [119] = KEY_FRONT, 132 | [120] = KEY_STOP, 133 | [121] = KEY_AGAIN, 134 | [122] = KEY_UNDO, 135 | [123] = KEY_CUT, 136 | [124] = KEY_COPY, 137 | [125] = KEY_PASTE, 138 | [126] = KEY_FIND, 139 | [127] = KEY_MUTE, 140 | [128] = KEY_VOLUMEUP, 141 | [129] = KEY_VOLUMEDOWN, 142 | [133] = KEY_KPCOMMA, 143 | [182] = KEY_KPLEFTPAREN, 144 | [183] = KEY_KPRIGHTPAREN, 145 | [224] = KEY_LEFTCTRL, 146 | [225] = KEY_LEFTSHIFT, 147 | [226] = KEY_LEFTALT, 148 | [227] = KEY_LEFTMETA, 149 | [228] = KEY_RIGHTCTRL, 150 | [229] = KEY_RIGHTSHIFT, 151 | [230] = KEY_RIGHTALT, 152 | [231] = KEY_RIGHTMETA, 153 | 154 | /* 155 | * As per the kernel, a keyboard needs to indicate, in advance, which key values it can report. 156 | * In order to that, it should have unique scancodes pointing those scancode-keycode pairs. 157 | * With the configuration set for now, the keyboard never outputs lower case letters, numbers, and equal to sign. 158 | * We can use these as bogus scancodes, and map the keys we want the keyboard to say its pressed when modifier keys are used. 159 | * This can change however, in case future software versions of the keyboard micrcontroller itself changes to output other stuff. 160 | */ 161 | 162 | // These values are not expected to be generated by a keypress, but 163 | // reported to the input system by alternate modifiers such as the 164 | // physical Alt key. 165 | // If there were more than AltGr available in the Linux keymap for 166 | // modifiers, we wouldn't have to do this. Customize the actual 167 | // key for these values in the keymap file. 168 | // This is the largest contiguous gap in the HID -> Linux input code 169 | // list. HID scancodes on the list below are not safe to send from 170 | // the keyboard firmware, they will get converted by the driver 171 | // to alternate keycodes. 172 | 173 | // Produced by firmware for phys. Alt + key 174 | [135] = 135, 175 | [136] = 136, 176 | [137] = 137, 177 | [138] = 138, 178 | [139] = 139, 179 | [140] = 140, 180 | [141] = 141, 181 | [142] = 142, 182 | [143] = 143, 183 | [144] = 144, 184 | [145] = 145, 185 | [146] = 146, 186 | [147] = 147, 187 | [148] = 148, 188 | [149] = 149, 189 | [150] = 150, 190 | [151] = 151, 191 | [152] = 152, 192 | [153] = 153, 193 | [154] = 154, 194 | [155] = 155, 195 | [156] = 156, 196 | [157] = 157, 197 | [158] = 158, 198 | [159] = 159, 199 | [160] = 160, 200 | [161] = 161, 201 | [162] = 162, 202 | [163] = 163, 203 | [164] = 164, 204 | [165] = 165, 205 | [166] = 166, 206 | [167] = 167, 207 | [168] = 168, 208 | [169] = 169, 209 | [170] = 170, 210 | [171] = 171, 211 | [172] = 172, 212 | [173] = 173, 213 | [174] = 174, 214 | [175] = 175, 215 | [176] = 176, 216 | [177] = 177, 217 | [178] = 178, 218 | [179] = 179, 219 | [180] = 180, 220 | [181] = 181, 221 | [232] = 232, 222 | }; 223 | 224 | #endif 225 | -------------------------------------------------------------------------------- /src/bbqX0kbd_registers.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 */ 2 | /* 3 | * Keyboard Driver for Blackberry Keyboards BBQ10 from arturo182. Software written by wallComputer. 4 | * bbq10kbd_registers.h: Registers in BBQ10 Keyboard Software. 5 | */ 6 | 7 | #ifndef BBQX0KBD_REGISTERS_H_ 8 | #define BBQX0KBD_REGISTERS_H_ 9 | 10 | #include "config.h" 11 | 12 | 13 | #define BBQX0KBD_I2C_ADDRESS BBQX0KBD_ASSIGNED_I2C_ADDRESS 14 | 15 | #define BBQX0KBD_WRITE_MASK 0x80 16 | #define BBQX0KBD_FIFO_SIZE 31 17 | 18 | #define REG_VER 0x01 19 | #if (BBQX0KBD_TYPE == BBQ10KBD_FEATHERWING) 20 | #define BBQX0KBD_I2C_SW_VERSION 0x04 // Unused for now since the version numbering has reset. 21 | #endif 22 | #if (BBQX0KBD_TYPE == BBQ20KBD_PMOD) 23 | #define BBQX0KBD_I2C_SW_VERSION 0x10 24 | #endif 25 | #define REG_CFG 0x02 26 | #define REG_CFG_USE_MODS BIT(7) 27 | #define REG_CFG_REPORT_MODS BIT(6) 28 | #define REG_CFG_PANIC_INT BIT(5) 29 | #define REG_CFG_KEY_INT BIT(4) 30 | #define REG_CFG_NUMLOCK_INT BIT(3) 31 | #define REG_CFG_CAPSLOCK_INT BIT(2) 32 | #define REG_CFG_OVERFLOW_INT BIT(1) 33 | #define REG_CFG_OVERFLOW_ON BIT(0) 34 | #define REG_CFG_DEFAULT_SETTING (REG_CFG_REPORT_MODS | REG_CFG_KEY_INT | REG_CFG_OVERFLOW_ON | REG_CFG_OVERFLOW_INT) 35 | // Configurations Used By Arturo182's Code. 36 | // #define REG_CFG_DEFAULT_SETTING (REG_CFG_OVERFLOW_ON | REG_CFG_OVERFLOW_INT | REG_CFG_CAPSLOCK_INT | REG_CFG_NUMLOCK_INT | REG_CFG_KEY_INT | REG_CFG_REPORT_MODS ) 37 | 38 | #define REG_INT 0x03 39 | #if (BBQX0KBD_TYPE == BBQ20KBD_PMOD) 40 | #define REG_INT_TOUCH BIT(6) 41 | #endif 42 | #define REG_INT_GPIO BIT(5) 43 | #define REG_INT_PANIC BIT(4) 44 | #define REG_INT_KEY BIT(3) 45 | #define REG_INT_NUMLOCK BIT(2) 46 | #define REG_INT_CAPSLOCK BIT(1) 47 | #define REG_INT_OVERFLOW BIT(0) 48 | #define REG_INT_RESET_VALUE 0x00 49 | 50 | #define REG_KEY 0x04 51 | #define REG_KEY_NUMLOCK BIT(6) 52 | #define REG_KEY_CAPSLOCK BIT(5) 53 | #define REG_KEY_KEYCOUNT_MASK 0x1F 54 | 55 | #define REG_BKL 0x05 56 | 57 | #define REG_DEB 0x06 58 | 59 | #define REG_FRQ 0x07 60 | 61 | #define REG_RST 0x08 62 | #define REG_FIF 0x09 63 | 64 | #define REG_BK2 0x0A 65 | 66 | #define REG_DIR 0x0B 67 | #define REG_DIR_INPUT 1 68 | #define REG_DIR_OUTPUT 0 69 | 70 | #define REG_PUE 0x0C 71 | #define REG_PUE_ENABLE 1 72 | #define REG_PUE_DISABLE 0 73 | 74 | 75 | #define REG_PUD 0x0D 76 | #define REG_PUD_PULL_UP 1 77 | #define REG_PUD_PULL_DOWN 0 78 | 79 | 80 | #define REG_GIO 0x0E 81 | 82 | #define REG_GIC 0x0F 83 | #define REG_GIC_INTERRUPT_TRIGGER 1 84 | #define REG_GIC_NO_INTERRUPT_TRIGGER 0 85 | 86 | #define REG_GIN 0x10 87 | #define REG_GIN_RESET_VALUE 0x00 88 | 89 | #define BBQ10_BRIGHTNESS_DELTA 16 90 | 91 | #if (BBQX0KBD_TYPE == BBQ20KBD_PMOD) 92 | #define REG_CF2 0x14 93 | #define REG_CF2_USB_MOUSE_ON BIT(2) 94 | #define REG_CF2_USB_KEYB_ON BIT(1) 95 | #define REG_CF2_TOUCH_INT BIT(0) 96 | #define REG_CFG2_DEFAULT_SETTING (REG_CF2_TOUCH_INT) 97 | 98 | #define REG_TOX 0x15 99 | #define REG_TOY 0x16 100 | 101 | #define REG_ADC 0x17 102 | #define REG_LED 0x20 103 | #define REG_LED_R 0x21 104 | #define REG_LED_G 0x22 105 | #define REG_LED_B 0x23 106 | #endif 107 | 108 | 109 | #endif 110 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 */ 2 | /* 3 | * Keyboard Driver for Blackberry Keyboards BBQ10 from arturo182. Software written by wallComputer. 4 | * config.h: File to store driver configurations. 5 | */ 6 | 7 | #ifndef CONFIG_H_ 8 | #define CONFIG_H_ 9 | 10 | #define BBQX0KBD_DEFAULT_I2C_ADDRESS 0x1F 11 | #define BBQX0KBD_ASSIGNED_I2C_ADDRESS BBQX0KBD_DEFAULT_I2C_ADDRESS 12 | 13 | #define BBQ10KBD_PMOD 0 14 | #define BBQ10KBD_FEATHERWING 1 15 | #define BBQ20KBD_PMOD 2 16 | #define BBQX0KBD_TYPE BBQ20KBD_PMOD 17 | 18 | #if (BBQX0KBD_TYPE == BBQ20KBD_PMOD) 19 | #define BBQ20KBD_TRACKPAD_AS_MOUSE 0 20 | #define BBQ20KBD_TRACKPAD_AS_KEYS 1 21 | #define BBQ20KBD_TRACKPAD_USE BBQ20KBD_TRACKPAD_AS_KEYS 22 | #endif 23 | 24 | #define BBQX0KBD_USE_INT 0 25 | #define BBQX0KBD_NO_INT 1 26 | #define BBQX0KBD_INT BBQX0KBD_USE_INT 27 | 28 | #if (BBQX0KBD_INT == BBQX0KBD_NO_INT) 29 | #define BBQX0KBD_POLL_PERIOD 40 30 | #else 31 | #define BBQX0KBD_INT_PIN 4 32 | #endif 33 | 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /src/debug_levels.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | /* 3 | * Keyboard Driver for Blackberry Keyboards BBQ10 from arturo182. Software written by wallComputer. 4 | * debug_levels.h: Describes the debug levels that can be set for viewing code flow in dmesg. 5 | * Use dmesg -wH | grep beepy-kbd for easy viewing. 6 | */ 7 | 8 | #ifndef DEBUG_LEVELS_H_ 9 | #define DEBUG_LEVELS_H_ 10 | 11 | /* 12 | * Set Debug Level with DEBUG_LEVEL. 13 | * Types of Debug Messages: 14 | * 1. Functional Entries. printk() & dev_info() used to show Entries in init & exit, probe, irq, logic. 15 | * 2. Read/Write Values. dev_info() used to show Values read and written from i2c functions. 16 | * 3. Logical Debugs. dev_info() used for showing Logic flow. 17 | * For All Informations -> DEBUG_LEVEL set as (DEBUG_LEVEL_FE | DEBUG_LEVEL_RW | DEBUG_LEVEL_LD) 18 | * For Only Read/Write in I2C Client -> DEBUG_LEVEL set as (DEBUG_LEVEL_RW) 19 | * For Informations on function entires-> DEBUG_LEVEL set as (DEBUG_LEVEL_FE) 20 | * For No Debug -> DEBUG_LEVEL set as (DEBUG_LEVEL_OFF) 21 | */ 22 | 23 | #define DEBUG_LEVEL_OFF 0 24 | #define DEBUG_LEVEL_FE 1 25 | #define DEBUG_LEVEL_RW 2 26 | #define DEBUG_LEVEL_LD 4 27 | 28 | #define DEBUG_LEVEL DEBUG_LEVEL_OFF 29 | // #define DEBUG_LEVEL DEBUG_LEVEL_FE 30 | // #define DEBUG_LEVEL (DEBUG_LEVEL_LD) 31 | // #define DEBUG_LEVEL (DEBUG_LEVEL_FE | DEBUG_LEVEL_RW | DEBUG_LEVEL_LD) 32 | #endif 33 | 34 | #if (DEBUG_LEVEL & DEBUG_LEVEL_FE) 35 | #define dev_info_fe(...) dev_info(__VA_ARGS__) 36 | #else 37 | #define dev_info_fe(...) 38 | #endif 39 | 40 | #if (DEBUG_LEVEL & DEBUG_LEVEL_LD) 41 | #define dev_info_ld(...) dev_info(__VA_ARGS__) 42 | #else 43 | #define dev_info_ld(...) 44 | #endif 45 | -------------------------------------------------------------------------------- /src/i2c_helper.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 */ 2 | 3 | #ifndef I2C_HELPER_H_ 4 | #define I2C_HELPER_H_ 5 | 6 | #include 7 | 8 | #include "config.h" 9 | #include "registers.h" 10 | #include "debug_levels.h" 11 | 12 | // Parse 0 to 255 from string 13 | static inline int parse_u8(char const* buf) 14 | { 15 | int rc, result; 16 | 17 | // Parse string value 18 | if ((rc = kstrtoint(buf, 10, &result)) || (result < 0) || (result > 0xff)) { 19 | return -EINVAL; 20 | } 21 | return result; 22 | } 23 | 24 | // Read a single uint8_t value from I2C register 25 | static inline int kbd_read_i2c_u8(struct i2c_client* i2c_client, uint8_t reg_addr, 26 | uint8_t* dst) 27 | { 28 | int reg_value; 29 | 30 | // Read value over I2C 31 | if ((reg_value = i2c_smbus_read_byte_data(i2c_client, reg_addr)) < 0) { 32 | dev_err(&i2c_client->dev, 33 | "%s Could not read from register 0x%02X, error: %d\n", 34 | __func__, reg_addr, reg_value); 35 | return reg_value; 36 | } 37 | 38 | // Assign result to buffer 39 | *dst = reg_value & 0xFF; 40 | 41 | return 0; 42 | } 43 | 44 | // Write a single uint8_t value to I2C register 45 | static inline int kbd_write_i2c_u8(struct i2c_client* i2c_client, uint8_t reg_addr, 46 | uint8_t src) 47 | { 48 | int rc; 49 | 50 | // Write value over I2C 51 | if ((rc = i2c_smbus_write_byte_data(i2c_client, 52 | reg_addr | BBQX0KBD_WRITE_MASK, src))) { 53 | 54 | dev_err(&i2c_client->dev, 55 | "%s Could not write to register 0x%02X, Error: %d\n", 56 | __func__, reg_addr, rc); 57 | return rc; 58 | } 59 | 60 | return 0; 61 | } 62 | 63 | // Read a pair of uint8_t values from I2C register 64 | static inline int kbd_read_i2c_2u8(struct i2c_client* i2c_client, uint8_t reg_addr, 65 | uint8_t* dst) 66 | { 67 | int word_value; 68 | 69 | // Read value over I2C 70 | if ((word_value = i2c_smbus_read_word_data(i2c_client, reg_addr)) < 0) { 71 | dev_err(&i2c_client->dev, 72 | "%s Could not read from register 0x%02X, error: %d\n", 73 | __func__, reg_addr, word_value); 74 | return word_value; 75 | } 76 | 77 | // Assign result to buffer 78 | *dst = (uint8_t)(word_value & 0xFF); 79 | *(dst + 1) = (uint8_t)((word_value & 0xFF00) >> 8); 80 | 81 | return 0; 82 | } 83 | 84 | #endif 85 | -------------------------------------------------------------------------------- /src/indicators.h: -------------------------------------------------------------------------------- 1 | #ifndef INDICATORS_H_ 2 | #define INDICATORS_H_ 3 | 4 | #define MAX_INDICATORS 7 5 | #define INDICATOR_WIDTH 14 6 | #define INDICATOR_HEIGHT 14 7 | #define INDICATORS_WIDTH (INDICATOR_WIDTH * MAX_INDICATORS) 8 | 9 | static u8 const ind_shift[] = { 10 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 11 | 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 12 | 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 13 | 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 14 | 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 15 | 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 16 | 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 17 | 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 18 | 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 19 | 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 20 | 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 21 | 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 22 | 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 23 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 24 | }; 25 | static u8 const ind_phys_alt[] = { 26 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 27 | 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 28 | 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 29 | 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 30 | 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 31 | 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 32 | 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 33 | 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 34 | 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 35 | 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 36 | 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 37 | 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 38 | 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 39 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 40 | }; 41 | static u8 const ind_control[] = { 42 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 43 | 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 44 | 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 45 | 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 46 | 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 47 | 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 48 | 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 49 | 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 50 | 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 51 | 0x00, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0x00, 52 | 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 53 | 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 54 | 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 55 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 56 | }; 57 | static u8 const ind_alt[] = { 58 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 59 | 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 60 | 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 61 | 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 62 | 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 63 | 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 64 | 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 65 | 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 66 | 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 67 | 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 68 | 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 69 | 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 70 | 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 71 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 72 | }; 73 | static u8 const ind_altgr[] = { 74 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 75 | 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 76 | 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 77 | 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 78 | 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 79 | 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 80 | 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 81 | 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 82 | 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 83 | 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 84 | 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 85 | 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 86 | 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 87 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 88 | }; 89 | static u8 const ind_meta[] = { 90 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 91 | 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 92 | 0x00, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0x00, 93 | 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 94 | 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 95 | 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 96 | 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 97 | 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 98 | 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 99 | 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 100 | 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 101 | 0x00, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0x00, 102 | 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 103 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 104 | }; 105 | static u8 const ind_touch[] = { 106 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 107 | 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 108 | 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 109 | 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 110 | 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 111 | 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 112 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 113 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 114 | 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 115 | 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 116 | 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 117 | 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 118 | 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 119 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 120 | }; 121 | 122 | #endif 123 | -------------------------------------------------------------------------------- /src/input_display.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | // Input display subsystem 3 | 4 | #include 5 | 6 | #include "config.h" 7 | #include "input_iface.h" 8 | #include "params_iface.h" 9 | 10 | #include "indicators.h" 11 | 12 | // Display ioctl 13 | 14 | #define DRM_SHARP_REDRAW 0x00 15 | 16 | // Globals 17 | 18 | static uint32_t g_mono_invert; 19 | 20 | // Loaded from display driver 21 | 22 | extern void sharp_memory_set_invert(int setting); 23 | void (*__sharp_memory_set_invert)(int setting); 24 | 25 | extern void* sharp_memory_add_overlay(int x, int y, int width, int height, 26 | unsigned char const* pixels); 27 | void* (*__sharp_memory_add_overlay)(int x, int y, int width, int height, 28 | unsigned char const* pixels); 29 | extern void sharp_memory_remove_overlay(void* entry); 30 | void (*__sharp_memory_remove_overlay)(void* entry); 31 | extern void* sharp_memory_show_overlay(void* storage); 32 | void* (*__sharp_memory_show_overlay)(void* storage); 33 | extern void sharp_memory_hide_overlay(void* display); 34 | void (*__sharp_memory_hide_overlay)(void* display); 35 | extern void sharp_memory_clear_overlays(void); 36 | void (*__sharp_memory_clear_overlays)(void); 37 | 38 | struct mod_overlay_t 39 | { 40 | void *storage; 41 | void *display; 42 | }; 43 | 44 | struct mod_overlay_t g_mod_overlays[MAX_INDICATORS]; 45 | 46 | // Display helpers 47 | 48 | static int ioctl_call_uint32(char const* path, unsigned int cmd, uint32_t value) 49 | { 50 | struct file *filp; 51 | 52 | // Open file 53 | if (IS_ERR((filp = filp_open(path, O_WRONLY, 0)))) { 54 | // Silently return if display driver was not loaded 55 | return 0; 56 | } 57 | 58 | filp->f_op->unlocked_ioctl(filp, cmd, value); 59 | 60 | // Close file 61 | filp_close(filp, NULL); 62 | 63 | return 0; 64 | } 65 | 66 | static int ioctl_sharp_redraw(void) 67 | { 68 | return ioctl_call_uint32(params_get_sharp_path(), 69 | DRM_IO(DRM_COMMAND_BASE + DRM_SHARP_REDRAW), 0); 70 | } 71 | 72 | // Whether this is a path to a valid Sharp display device 73 | int input_display_valid_sharp_path(char const* path) 74 | { 75 | // Try to refresh screen 76 | return ioctl_call_uint32(path, 77 | DRM_IO(DRM_COMMAND_BASE + DRM_SHARP_REDRAW), 0); 78 | } 79 | 80 | // Invert display colors by writing to display driver parameter 81 | void input_display_invert(struct kbd_ctx* ctx) 82 | { 83 | // Update saved invert value 84 | g_mono_invert = (g_mono_invert) ? 0 : 1; 85 | 86 | if (__sharp_memory_set_invert == NULL) { 87 | __sharp_memory_set_invert = symbol_get(sharp_memory_set_invert); 88 | } 89 | if (__sharp_memory_set_invert == NULL) { 90 | return; 91 | } 92 | 93 | // Apply invert value 94 | __sharp_memory_set_invert(g_mono_invert); 95 | (void)ioctl_sharp_redraw(); 96 | } 97 | 98 | // Set display indicator 99 | void input_display_set_indicator(int idx, unsigned char const* pixels) 100 | { 101 | int x; 102 | 103 | if ((idx >= MAX_INDICATORS) || (pixels == NULL)) { 104 | return; 105 | } 106 | 107 | if (g_mod_overlays[idx].storage == NULL) { 108 | x = - ((idx + 1) * INDICATOR_WIDTH); 109 | 110 | // Get overlay add function 111 | if (__sharp_memory_add_overlay == NULL) { 112 | __sharp_memory_add_overlay = symbol_get(sharp_memory_add_overlay); 113 | } 114 | if (__sharp_memory_add_overlay == NULL) { 115 | return; 116 | } 117 | 118 | // Add indicator overlay 119 | g_mod_overlays[idx].storage = __sharp_memory_add_overlay( 120 | x, 0, INDICATOR_WIDTH, INDICATOR_HEIGHT, pixels); 121 | } 122 | 123 | if (g_mod_overlays[idx].display == NULL) { 124 | 125 | if (__sharp_memory_show_overlay == NULL) { 126 | __sharp_memory_show_overlay = symbol_get(sharp_memory_show_overlay); 127 | } 128 | if (__sharp_memory_show_overlay == NULL) { 129 | return; 130 | } 131 | 132 | // Display indicator overlay and set display handle 133 | g_mod_overlays[idx].display = __sharp_memory_show_overlay( 134 | g_mod_overlays[idx].storage); 135 | } 136 | 137 | // Refresh display 138 | (void)ioctl_sharp_redraw(); 139 | } 140 | 141 | // Clear display indicator 142 | void input_display_clear_indicator(int idx) 143 | { 144 | if (g_mod_overlays[idx].display != NULL) { 145 | 146 | // Get overlay hide function 147 | if (__sharp_memory_hide_overlay == NULL) { 148 | __sharp_memory_hide_overlay = symbol_get(sharp_memory_hide_overlay); 149 | } 150 | if (__sharp_memory_hide_overlay == NULL) { 151 | return; 152 | } 153 | 154 | // Hide overlay and reset display handle 155 | __sharp_memory_hide_overlay(g_mod_overlays[idx].display); 156 | g_mod_overlays[idx].display = NULL; 157 | (void)ioctl_sharp_redraw(); 158 | } 159 | } 160 | 161 | // Clear all overlays 162 | void input_display_clear_overlays(void) 163 | { 164 | int i; 165 | 166 | // Get overlay clear function 167 | if (__sharp_memory_clear_overlays == NULL) { 168 | __sharp_memory_clear_overlays = symbol_get(sharp_memory_clear_overlays); 169 | } 170 | if (__sharp_memory_clear_overlays == NULL) { 171 | return; 172 | } 173 | 174 | // Invalidate all overlays 175 | for (i = 0; i < MAX_INDICATORS; i++) { 176 | g_mod_overlays[i].storage = NULL; 177 | g_mod_overlays[i].display = NULL; 178 | } 179 | 180 | // Clear all overlays 181 | __sharp_memory_clear_overlays(); 182 | 183 | // Refresh display 184 | (void)ioctl_sharp_redraw(); 185 | } 186 | 187 | int input_display_probe(struct i2c_client* i2c_client, struct kbd_ctx *ctx) 188 | { 189 | g_mono_invert = 0; 190 | 191 | // Clear all overlays 192 | input_display_clear_overlays(); 193 | 194 | return 0; 195 | } 196 | 197 | void input_display_shutdown(struct i2c_client* i2c_client, struct kbd_ctx *ctx) 198 | { 199 | // Clear all overlays 200 | input_display_clear_overlays(); 201 | } 202 | -------------------------------------------------------------------------------- /src/input_fw.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | // Input firmware subsystem 3 | 4 | #include 5 | 6 | #include "config.h" 7 | #include "i2c_helper.h" 8 | #include "input_iface.h" 9 | 10 | #include "bbq20kbd_pmod_codes.h" 11 | 12 | // Globals 13 | static uint8_t g_brightness; 14 | static uint8_t g_last_brightness; 15 | static uint8_t g_handle_poweroff; 16 | 17 | // Helpers 18 | 19 | static void input_fw_run_poweroff(struct kbd_ctx* ctx) 20 | { 21 | // Set LED to red 22 | kbd_write_i2c_u8(ctx->i2c_client, REG_LED_R, 0xff); 23 | kbd_write_i2c_u8(ctx->i2c_client, REG_LED_G, 0x0); 24 | kbd_write_i2c_u8(ctx->i2c_client, REG_LED_B, 0x0); 25 | kbd_write_i2c_u8(ctx->i2c_client, REG_LED, 0x1); 26 | 27 | // Run poweroff 28 | static const char * const poweroff_argv[] = { 29 | "/sbin/poweroff", "now", NULL }; 30 | call_usermodehelper(poweroff_argv[0], (char**)poweroff_argv, NULL, UMH_NO_WAIT); 31 | } 32 | 33 | int input_fw_probe(struct i2c_client* i2c_client, struct kbd_ctx *ctx) 34 | { 35 | int rc; 36 | uint8_t reg_value; 37 | 38 | // Initialize keyboard context 39 | g_brightness = 0x10; 40 | g_last_brightness = 0x00; 41 | g_handle_poweroff = 0; 42 | 43 | // Get firmware version 44 | if (kbd_read_i2c_u8(i2c_client, REG_VER, &ctx->version_number)) { 45 | return -ENODEV; 46 | } 47 | dev_info(&i2c_client->dev, 48 | "%s BBQX0KBD Software version: 0x%02X\n", __func__, 49 | g_ctx->version_number); 50 | 51 | // Write configuration 1 52 | if (kbd_write_i2c_u8(i2c_client, REG_CFG, REG_CFG_DEFAULT_SETTING)) { 53 | return -ENODEV; 54 | } 55 | 56 | // Read back configuration 1 setting 57 | if (kbd_read_i2c_u8(i2c_client, REG_CFG, ®_value)) { 58 | return -ENODEV; 59 | } 60 | dev_info_ld(&i2c_client->dev, 61 | "%s Configuration Register Value: 0x%02X\n", __func__, reg_value); 62 | 63 | // Write configuration 2 64 | // No USB output and disable touch. Touch settings will be updated later 65 | // based on module parameters 66 | if (kbd_write_i2c_u8(i2c_client, REG_CF2, 0)) { 67 | return -ENODEV; 68 | } 69 | 70 | // Read back configuration 2 setting 71 | if (kbd_read_i2c_u8(i2c_client, REG_CF2, ®_value)) { 72 | return rc; 73 | } 74 | dev_info_ld(&i2c_client->dev, 75 | "%s Configuration 2 Register Value: 0x%02X\n", 76 | __func__, reg_value); 77 | 78 | // Update keyboard brightness 79 | (void)kbd_write_i2c_u8(i2c_client, REG_BKL, g_brightness); 80 | 81 | // Notify firmware that driver has initialized 82 | // Clear boot indicator LED 83 | (void)kbd_write_i2c_u8(i2c_client, REG_LED, 0); 84 | (void)kbd_write_i2c_u8(i2c_client, REG_DRIVER_STATE, 1); 85 | 86 | return 0; 87 | } 88 | 89 | void input_fw_shutdown(struct i2c_client* i2c_client, struct kbd_ctx *ctx) 90 | { 91 | uint8_t reg_value; 92 | 93 | dev_info_fe(&i2c_client->dev, 94 | "%s Shutting Down Keyboard And Screen Backlight.\n", __func__); 95 | 96 | // Turn off LED and notify firmware that driver has shut down 97 | (void)kbd_write_i2c_u8(i2c_client, REG_LED, 0); 98 | (void)kbd_write_i2c_u8(i2c_client, REG_DRIVER_STATE, 0); 99 | 100 | // Turn off backlight 101 | (void)kbd_write_i2c_u8(i2c_client, REG_BKL, 0); 102 | 103 | // Reenable touch events 104 | (void)kbd_write_i2c_u8(i2c_client, REG_CF2, REG_CFG2_DEFAULT_SETTING); 105 | 106 | // Read back version 107 | (void)kbd_read_i2c_u8(i2c_client, REG_VER, ®_value); 108 | } 109 | 110 | int input_fw_consumes_keycode(struct kbd_ctx* ctx, 111 | uint8_t *remapped_keycode, uint8_t keycode, uint8_t state) 112 | { 113 | // Power key runs /sbin/poweroff if `handle_poweroff` is set 114 | if (keycode == KEY_POWER) { 115 | if ((state == KEY_STATE_PRESSED) && g_handle_poweroff) { 116 | input_fw_run_poweroff(ctx); 117 | } 118 | 119 | // Allow power key to be handled by OS 120 | } 121 | 122 | return 0; 123 | } 124 | 125 | // Brightness helpers 126 | 127 | void input_fw_decrease_brightness(struct kbd_ctx* ctx) 128 | { 129 | // Decrease by delta, min at 0x0 brightness 130 | g_brightness = (g_brightness < BBQ10_BRIGHTNESS_DELTA) 131 | ? 0x0 132 | : g_brightness - BBQ10_BRIGHTNESS_DELTA; 133 | 134 | // Set backlight using I2C 135 | (void)kbd_write_i2c_u8(ctx->i2c_client, REG_BKL, g_brightness); 136 | } 137 | 138 | void input_fw_increase_brightness(struct kbd_ctx* ctx) 139 | { 140 | // Increase by delta, max at 0xff brightness 141 | g_brightness = (g_brightness > (0xff - BBQ10_BRIGHTNESS_DELTA)) 142 | ? 0xff 143 | : g_brightness + BBQ10_BRIGHTNESS_DELTA; 144 | 145 | // Set backlight using I2C 146 | (void)kbd_write_i2c_u8(ctx->i2c_client, REG_BKL, g_brightness); 147 | } 148 | 149 | void input_fw_toggle_brightness(struct kbd_ctx* ctx) 150 | { 151 | // Toggle, save last brightness in context 152 | if (g_last_brightness) { 153 | g_brightness = g_last_brightness; 154 | g_last_brightness = 0; 155 | } else { 156 | g_last_brightness = g_brightness; 157 | g_brightness = 0; 158 | } 159 | 160 | // Set backlight using I2C 161 | (void)kbd_write_i2c_u8(ctx->i2c_client, REG_BKL, g_brightness); 162 | } 163 | 164 | // I2C helpers 165 | 166 | int input_fw_enable_touch_interrupts(struct kbd_ctx* ctx) 167 | { 168 | int rc; 169 | uint8_t reg_value; 170 | 171 | // Get old CF2 value 172 | if ((rc = kbd_read_i2c_u8(ctx->i2c_client, REG_CF2, ®_value))) { 173 | return rc; 174 | } 175 | 176 | // Set touch interrupt bit 177 | reg_value |= REG_CF2_TOUCH_INT; 178 | 179 | // Write new CF2 value 180 | if ((rc = kbd_write_i2c_u8(ctx->i2c_client, REG_CF2, reg_value))) { 181 | return rc; 182 | } 183 | 184 | // Set touch state 185 | ctx->raised_touch_event = 1; 186 | 187 | return 0; 188 | } 189 | 190 | int input_fw_disable_touch_interrupts(struct kbd_ctx* ctx) 191 | { 192 | int rc; 193 | uint8_t reg_value; 194 | 195 | // Get old CF2 value 196 | if ((rc = kbd_read_i2c_u8(ctx->i2c_client, REG_CF2, ®_value))) { 197 | return rc; 198 | } 199 | 200 | // Clear touch interrupt bit 201 | reg_value &= ~REG_CF2_TOUCH_INT; 202 | 203 | // Write new CF2 value 204 | if ((rc = kbd_write_i2c_u8(ctx->i2c_client, REG_CF2, reg_value))) { 205 | return rc; 206 | } 207 | 208 | // Clear touch state 209 | ctx->raised_touch_event = 0; 210 | 211 | return 0; 212 | } 213 | 214 | // Transfer from I2C FIFO to internal context FIFO 215 | void input_fw_read_fifo(struct kbd_ctx* ctx) 216 | { 217 | uint8_t fifo_idx; 218 | int rc; 219 | 220 | // Read number of FIFO items 221 | if (kbd_read_i2c_u8(ctx->i2c_client, REG_KEY, &ctx->key_fifo_count)) { 222 | return; 223 | } 224 | ctx->key_fifo_count &= REG_KEY_KEYCOUNT_MASK; 225 | 226 | // Read and transfer all FIFO items 227 | for (fifo_idx = 0; fifo_idx < ctx->key_fifo_count; fifo_idx++) { 228 | 229 | // Read 2 fifo items 230 | if ((rc = kbd_read_i2c_2u8(ctx->i2c_client, REG_FIF, 231 | (uint8_t*)&ctx->key_fifo_data[fifo_idx]))) { 232 | 233 | dev_err(&ctx->i2c_client->dev, 234 | "%s Could not read REG_FIF, Error: %d\n", __func__, rc); 235 | return; 236 | } 237 | 238 | // Advance FIFO position 239 | dev_info_fe(&ctx->i2c_client->dev, 240 | "%s %02d: 0x%02x%02x State %d Scancode %d\n", 241 | __func__, fifo_idx, 242 | ((uint8_t*)&ctx->key_fifo_data[fifo_idx])[0], 243 | ((uint8_t*)&ctx->key_fifo_data[fifo_idx])[1], 244 | ctx->key_fifo_data[fifo_idx].state, 245 | ctx->key_fifo_data[fifo_idx].scancode); 246 | } 247 | } 248 | 249 | // RTC helpers 250 | 251 | int input_fw_get_rtc(uint8_t* year, uint8_t* mon, uint8_t* day, 252 | uint8_t* hour, uint8_t* min, uint8_t* sec) 253 | { 254 | int rc; 255 | 256 | if (!g_ctx || !g_ctx->i2c_client) { 257 | return -EAGAIN; 258 | } 259 | 260 | if ((rc = kbd_read_i2c_u8(g_ctx->i2c_client, REG_RTC_YEAR, year))) { 261 | return rc; 262 | } 263 | if ((rc = kbd_read_i2c_u8(g_ctx->i2c_client, REG_RTC_MON, mon))) { 264 | return rc; 265 | } 266 | if ((rc = kbd_read_i2c_u8(g_ctx->i2c_client, REG_RTC_MDAY, day))) { 267 | return rc; 268 | } 269 | if ((rc = kbd_read_i2c_u8(g_ctx->i2c_client, REG_RTC_HOUR, hour))) { 270 | return rc; 271 | } 272 | if ((rc = kbd_read_i2c_u8(g_ctx->i2c_client, REG_RTC_MIN, min))) { 273 | return rc; 274 | } 275 | if ((rc = kbd_read_i2c_u8(g_ctx->i2c_client, REG_RTC_SEC, sec))) { 276 | return rc; 277 | } 278 | 279 | return 0; 280 | } 281 | 282 | int input_fw_set_rtc(uint8_t year, uint8_t mon, uint8_t day, 283 | uint8_t hour, uint8_t min, uint8_t sec) 284 | { 285 | int rc; 286 | 287 | if (!g_ctx || !g_ctx->i2c_client) { 288 | return -EAGAIN; 289 | } 290 | 291 | if ((rc = kbd_write_i2c_u8(g_ctx->i2c_client, REG_RTC_YEAR, year))) { 292 | return rc; 293 | } 294 | if ((rc = kbd_write_i2c_u8(g_ctx->i2c_client, REG_RTC_MON, mon))) { 295 | return rc; 296 | } 297 | if ((rc = kbd_write_i2c_u8(g_ctx->i2c_client, REG_RTC_MDAY, day))) { 298 | return rc; 299 | } 300 | if ((rc = kbd_write_i2c_u8(g_ctx->i2c_client, REG_RTC_HOUR, hour))) { 301 | return rc; 302 | } 303 | if ((rc = kbd_write_i2c_u8(g_ctx->i2c_client, REG_RTC_MIN, min))) { 304 | return rc; 305 | } 306 | if ((rc = kbd_write_i2c_u8(g_ctx->i2c_client, REG_RTC_SEC, sec))) { 307 | return rc; 308 | } 309 | if ((rc = kbd_write_i2c_u8(g_ctx->i2c_client, REG_RTC_COMMIT, 0x1))) { 310 | return rc; 311 | } 312 | 313 | return 0; 314 | } 315 | 316 | void input_fw_set_handle_poweroff(struct kbd_ctx* ctx, uint8_t handle_poweroff) 317 | { 318 | g_handle_poweroff = handle_poweroff; 319 | } 320 | 321 | void input_fw_set_auto_off(struct kbd_ctx* ctx, uint8_t auto_off) 322 | { 323 | uint8_t reg_value; 324 | 325 | // Get original value 326 | if (kbd_read_i2c_u8(ctx->i2c_client, REG_CF2, ®_value)) { 327 | return; 328 | } 329 | 330 | // Update auto-off bit 331 | if (auto_off) { 332 | reg_value |= REG_CF2_AUTO_OFF; 333 | } else { 334 | reg_value &= ~REG_CF2_AUTO_OFF; 335 | } 336 | 337 | // Update value 338 | if (kbd_write_i2c_u8(ctx->i2c_client, REG_CF2, reg_value)) { 339 | return; 340 | } 341 | } 342 | -------------------------------------------------------------------------------- /src/input_iface.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | /* 3 | * Keyboard Driver for Blackberry Keyboards BBQ10 from arturo182. Software written by wallComputer. 4 | * input_iface.c: Key handler implementation 5 | */ 6 | 7 | #include 8 | #include 9 | 10 | #include "config.h" 11 | #include "debug_levels.h" 12 | 13 | #include "params_iface.h" 14 | #include "input_iface.h" 15 | 16 | #include "i2c_helper.h" 17 | 18 | #include "bbq20kbd_pmod_codes.h" 19 | 20 | // Global keyboard context and sysfs data 21 | struct kbd_ctx *g_ctx = NULL; 22 | 23 | // Main key event handler 24 | static void key_report_event(struct kbd_ctx* ctx, 25 | struct key_fifo_item const* ev) 26 | { 27 | uint8_t keycode; 28 | 29 | // Only handle key pressed, held, or released events 30 | if ((ev->state != KEY_STATE_PRESSED) && (ev->state != KEY_STATE_RELEASED) 31 | && (ev->state != KEY_STATE_HOLD)) { 32 | return; 33 | } 34 | 35 | // Post key scan event 36 | input_event(ctx->input_dev, EV_MSC, MSC_SCAN, ev->scancode); 37 | 38 | // Map input scancode to Linux input keycode 39 | keycode = ctx->keycode_map[ev->scancode]; 40 | dev_info_fe(&ctx->i2c_client->dev, 41 | "%s state %d, scancode %d mapped to keycode %d\n", 42 | __func__, ev->state, ev->scancode, keycode); 43 | 44 | // Scancode mapped to ignored keycode 45 | if (keycode == 0) { 46 | return; 47 | 48 | // Scancode converted to keycode not in map 49 | } else if (keycode == KEY_UNKNOWN) { 50 | dev_warn(&ctx->i2c_client->dev, 51 | "%s Could not get Keycode for Scancode: [0x%02X]\n", 52 | __func__, ev->scancode); 53 | return; 54 | } 55 | 56 | // Update last keypress time 57 | g_ctx->last_keypress_at = ktime_get_boottime_ns(); 58 | 59 | if (keycode == KEY_STOP) { 60 | 61 | // Pressing power button sends Tmux prefix (Control + code 171 in keymap) 62 | if (ev->state == KEY_STATE_PRESSED) { 63 | input_report_key(ctx->input_dev, KEY_LEFTCTRL, TRUE); 64 | input_report_key(ctx->input_dev, 171, TRUE); 65 | input_report_key(ctx->input_dev, 171, FALSE); 66 | input_report_key(ctx->input_dev, KEY_LEFTCTRL, FALSE); 67 | 68 | // Short hold power buttion opens Tmux menu (Control + code 174 in keymap) 69 | } else if (ev->state == KEY_STATE_HOLD) { 70 | input_report_key(ctx->input_dev, KEY_LEFTCTRL, TRUE); 71 | input_report_key(ctx->input_dev, 174, TRUE); 72 | input_report_key(ctx->input_dev, 174, FALSE); 73 | input_report_key(ctx->input_dev, KEY_LEFTCTRL, FALSE); 74 | } 75 | return; 76 | } 77 | 78 | // Subsystem key handling 79 | if (input_fw_consumes_keycode(ctx, &keycode, keycode, ev->state) 80 | || input_touch_consumes_keycode(ctx, &keycode, keycode, ev->state) 81 | || input_modifiers_consumes_keycode(ctx, &keycode, keycode, ev->state) 82 | || input_meta_consumes_keycode(ctx, &keycode, keycode, ev->state)) { 83 | return; 84 | } 85 | 86 | // Ignore hold keys at this point 87 | if (ev->state == KEY_STATE_HOLD) { 88 | return; 89 | } 90 | 91 | // Apply pending sticky modifiers 92 | keycode = input_modifiers_apply_pending(ctx, keycode); 93 | 94 | // Report key to input system 95 | input_report_key(ctx->input_dev, keycode, ev->state == KEY_STATE_PRESSED); 96 | 97 | // Reset sticky modifiers 98 | input_modifiers_reset(ctx); 99 | } 100 | 101 | static irqreturn_t input_irq_handler(int irq, void *param) 102 | { 103 | struct kbd_ctx *ctx; 104 | uint8_t irq_type; 105 | int8_t reg_value; 106 | 107 | // `param` is current keyboard context as started in _probe 108 | ctx = (struct kbd_ctx *)param; 109 | 110 | dev_info_ld(&ctx->i2c_client->dev, 111 | "%s Interrupt Fired. IRQ: %d\n", __func__, irq); 112 | 113 | // Read interrupt type from client 114 | if (kbd_read_i2c_u8(ctx->i2c_client, REG_INT, &irq_type)) { 115 | return IRQ_NONE; 116 | } 117 | dev_info_ld(&ctx->i2c_client->dev, 118 | "%s Interrupt type: 0x%02x\n", __func__, irq_type); 119 | 120 | // Reported no interrupt type 121 | if (irq_type == 0x00) { 122 | return IRQ_NONE; 123 | } 124 | 125 | // Client reported a key overflow 126 | if (irq_type & REG_INT_OVERFLOW) { 127 | dev_warn(&ctx->i2c_client->dev, "%s overflow occurred.\n", __func__); 128 | } 129 | 130 | // Client reported a key event 131 | if (irq_type & REG_INT_KEY) { 132 | input_fw_read_fifo(ctx); 133 | schedule_work(&ctx->work_struct); 134 | } 135 | 136 | // Client reported a touch event 137 | if (irq_type & REG_INT_TOUCH) { 138 | 139 | // Read touch X-coordinate 140 | if (kbd_read_i2c_u8(ctx->i2c_client, REG_TOX, ®_value)) { 141 | return IRQ_NONE; 142 | } 143 | ctx->touch.dx += reg_value; 144 | 145 | // Read touch Y-coordinate 146 | if (kbd_read_i2c_u8(ctx->i2c_client, REG_TOY, ®_value)) { 147 | return IRQ_NONE; 148 | } 149 | ctx->touch.dy += reg_value; 150 | 151 | // Set touch event flag and schedule touch work 152 | ctx->raised_touch_event = 1; 153 | schedule_work(&ctx->work_struct); 154 | 155 | } else { 156 | 157 | // Clear touch event flag 158 | ctx->raised_touch_event = 0; 159 | } 160 | 161 | return IRQ_HANDLED; 162 | } 163 | 164 | static void input_workqueue_handler(struct work_struct *work_struct_ptr) 165 | { 166 | struct kbd_ctx *ctx; 167 | uint8_t fifo_idx; 168 | 169 | // Get keyboard context from work struct 170 | ctx = container_of(work_struct_ptr, struct kbd_ctx, work_struct); 171 | 172 | // Process FIFO items 173 | for (fifo_idx = 0; fifo_idx < ctx->key_fifo_count; fifo_idx++) { 174 | key_report_event(ctx, &ctx->key_fifo_data[fifo_idx]); 175 | } 176 | 177 | // Reset pending FIFO count 178 | ctx->key_fifo_count = 0; 179 | 180 | // Handle any pending touch events 181 | if (ctx->raised_touch_event) { 182 | input_touch_report_event(ctx); 183 | ctx->raised_touch_event = 0; 184 | } 185 | 186 | // Synchronize input system and clear client interrupt flag 187 | input_sync(ctx->input_dev); 188 | if (kbd_write_i2c_u8(ctx->i2c_client, REG_INT, 0)) { 189 | return; 190 | } 191 | } 192 | 193 | int input_probe(struct i2c_client* i2c_client) 194 | { 195 | int rc, i; 196 | 197 | // Allocate keyboard context (managed by device lifetime) 198 | g_ctx = devm_kzalloc(&i2c_client->dev, sizeof(*g_ctx), GFP_KERNEL); 199 | if (!g_ctx) { 200 | return -ENOMEM; 201 | } 202 | 203 | // Allocate and copy keycode array 204 | g_ctx->keycode_map = devm_kmemdup(&i2c_client->dev, keycodes, NUM_KEYCODES, 205 | GFP_KERNEL); 206 | if (!g_ctx->keycode_map) { 207 | return -ENOMEM; 208 | } 209 | 210 | // Initialize keyboard context 211 | g_ctx->i2c_client = i2c_client; 212 | g_ctx->last_keypress_at = ktime_get_boottime_ns(); 213 | 214 | // Run subsystem probes 215 | if ((rc = input_fw_probe(i2c_client, g_ctx))) { 216 | dev_err(&i2c_client->dev, "beepy-kbd: input_fw_probe failed\n"); 217 | return rc; 218 | } 219 | if ((rc = input_rtc_probe(i2c_client, g_ctx))) { 220 | dev_err(&i2c_client->dev, "beepy-kbd: input_rtc_probe failed\n"); 221 | return rc; 222 | } 223 | if ((rc = input_display_probe(i2c_client, g_ctx))) { 224 | dev_err(&i2c_client->dev, "beepy-kbd: input_display_probe failed\n"); 225 | return rc; 226 | } 227 | if ((rc = input_modifiers_probe(i2c_client, g_ctx))) { 228 | dev_err(&i2c_client->dev, "beepy-kbd: input_modifiers_probe failed\n"); 229 | return rc; 230 | } 231 | if ((rc = input_touch_probe(i2c_client, g_ctx))) { 232 | dev_err(&i2c_client->dev, "beepy-kbd: input_touch_probe failed\n"); 233 | return rc; 234 | } 235 | if ((rc = input_meta_probe(i2c_client, g_ctx))) { 236 | dev_err(&i2c_client->dev, "beepy-kbd: input_meta_probe failed\n"); 237 | return rc; 238 | } 239 | 240 | // Allocate input device 241 | if ((g_ctx->input_dev = devm_input_allocate_device(&i2c_client->dev)) == NULL) { 242 | dev_err(&i2c_client->dev, 243 | "%s Could not devm_input_allocate_device BBQX0KBD.\n", __func__); 244 | return -ENOMEM; 245 | } 246 | 247 | // Initialize input device 248 | g_ctx->input_dev->name = i2c_client->name; 249 | g_ctx->input_dev->id.bustype = BBQX0KBD_BUS_TYPE; 250 | g_ctx->input_dev->id.vendor = BBQX0KBD_VENDOR_ID; 251 | g_ctx->input_dev->id.product = BBQX0KBD_PRODUCT_ID; 252 | g_ctx->input_dev->id.version = BBQX0KBD_VERSION_ID; 253 | 254 | // Initialize input device keycodes 255 | g_ctx->input_dev->keycode = g_ctx->keycode_map; 256 | g_ctx->input_dev->keycodesize = sizeof(g_ctx->keycode_map[0]); 257 | g_ctx->input_dev->keycodemax = ARRAY_SIZE(keycodes); 258 | 259 | // Set input device keycode bits 260 | for (i = 0; i < NUM_KEYCODES; i++) { 261 | __set_bit(g_ctx->keycode_map[i], g_ctx->input_dev->keybit); 262 | } 263 | __clear_bit(KEY_RESERVED, g_ctx->input_dev->keybit); 264 | __set_bit(EV_REP, g_ctx->input_dev->evbit); 265 | __set_bit(EV_KEY, g_ctx->input_dev->evbit); 266 | 267 | // Set input device capabilities 268 | input_set_capability(g_ctx->input_dev, EV_MSC, MSC_SCAN); 269 | input_set_capability(g_ctx->input_dev, EV_REL, REL_X); 270 | input_set_capability(g_ctx->input_dev, EV_REL, REL_Y); 271 | input_set_capability(g_ctx->input_dev, EV_KEY, BTN_LEFT); 272 | input_set_capability(g_ctx->input_dev, EV_KEY, BTN_RIGHT); 273 | 274 | // Request IRQ handler for I2C client and initialize workqueue 275 | if ((rc = devm_request_threaded_irq(&i2c_client->dev, 276 | i2c_client->irq, NULL, input_irq_handler, IRQF_SHARED | IRQF_ONESHOT, 277 | i2c_client->name, g_ctx))) { 278 | 279 | dev_err(&i2c_client->dev, 280 | "Could not claim IRQ %d; error %d\n", i2c_client->irq, rc); 281 | return rc; 282 | } 283 | INIT_WORK(&g_ctx->work_struct, input_workqueue_handler); 284 | 285 | // Register input device with input subsystem 286 | dev_info(&i2c_client->dev, 287 | "%s registering input device", __func__); 288 | if ((rc = input_register_device(g_ctx->input_dev))) { 289 | dev_err(&i2c_client->dev, 290 | "Failed to register input device, error: %d\n", rc); 291 | return rc; 292 | } 293 | 294 | return 0; 295 | } 296 | 297 | void input_shutdown(struct i2c_client* i2c_client) 298 | { 299 | // Run subsystem shutdowns 300 | input_meta_shutdown(i2c_client, g_ctx); 301 | input_touch_shutdown(i2c_client, g_ctx); 302 | input_modifiers_shutdown(i2c_client, g_ctx); 303 | input_display_shutdown(i2c_client, g_ctx); 304 | input_rtc_shutdown(i2c_client, g_ctx); 305 | input_fw_shutdown(i2c_client, g_ctx); 306 | 307 | // Remove context from global state 308 | // (It is freed by the device-specific memory mananger) 309 | g_ctx = NULL; 310 | } 311 | -------------------------------------------------------------------------------- /src/input_iface.h: -------------------------------------------------------------------------------- 1 | #ifndef INPUT_IFACE_H_ 2 | #define INPUT_IFACE_H_ 3 | 4 | // SPDX-License-Identifier: GPL-2.0-only 5 | /* 6 | * Keyboard Driver for Blackberry Keyboards BBQ10 from arturo182. Software written by wallComputer. 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "registers.h" 14 | 15 | #define BBQX0KBD_BUS_TYPE BUS_I2C 16 | #define BBQX0KBD_VENDOR_ID 0x0001 17 | #define BBQX0KBD_PRODUCT_ID 0x0001 18 | #define BBQX0KBD_VERSION_ID 0x0001 19 | 20 | // From keyboard firmware source 21 | enum rp2040_key_state 22 | { 23 | KEY_STATE_IDLE = 0, 24 | KEY_STATE_PRESSED = 1, 25 | KEY_STATE_HOLD = 2, 26 | KEY_STATE_RELEASED = 3, 27 | KEY_STATE_LONG_HOLD = 4, 28 | }; 29 | struct key_fifo_item 30 | { 31 | uint8_t scancode; 32 | 33 | uint8_t _ : 4; 34 | enum rp2040_key_state state : 4; 35 | }; 36 | 37 | struct touch_ctx 38 | { 39 | enum { 40 | TOUCH_ACT_ALWAYS = 0, 41 | TOUCH_ACT_CLICK = 1 42 | } activation; 43 | 44 | enum { 45 | TOUCH_INPUT_AS_KEYS = 0, 46 | TOUCH_INPUT_AS_MOUSE = 1 47 | } input_as; 48 | 49 | uint8_t enabled; 50 | uint8_t enable_while_shift_held; 51 | uint8_t entry_while_shift_held; 52 | uint8_t threshold; 53 | int x, dx, y, dy; 54 | }; 55 | 56 | struct kbd_ctx 57 | { 58 | struct work_struct work_struct; 59 | uint8_t version_number; 60 | 61 | struct i2c_client *i2c_client; 62 | struct input_dev *input_dev; 63 | 64 | // Map from input HID scancodes to Linux keycodes 65 | uint8_t *keycode_map; 66 | 67 | // Key state and touch FIFO queue 68 | uint8_t key_fifo_count; 69 | struct key_fifo_item key_fifo_data[BBQX0KBD_FIFO_SIZE]; 70 | uint64_t last_keypress_at; 71 | 72 | uint8_t raised_touch_event; 73 | struct touch_ctx touch; 74 | }; 75 | 76 | // Shared global state for global interfaces such as sysfs 77 | extern struct kbd_ctx *g_ctx; 78 | 79 | // Public interface 80 | 81 | int input_probe(struct i2c_client* i2c_client); 82 | void input_shutdown(struct i2c_client* i2c_client); 83 | 84 | // Internal interfaces 85 | 86 | // Firmware 87 | 88 | int input_fw_probe(struct i2c_client* i2c_client, struct kbd_ctx *ctx); 89 | void input_fw_shutdown(struct i2c_client* i2c_client, struct kbd_ctx *ctx); 90 | 91 | int input_fw_consumes_keycode(struct kbd_ctx* ctx, 92 | uint8_t *remapped_keycode, uint8_t keycode, uint8_t state); 93 | 94 | void input_fw_decrease_brightness(struct kbd_ctx* ctx); 95 | void input_fw_increase_brightness(struct kbd_ctx* ctx); 96 | void input_fw_toggle_brightness(struct kbd_ctx* ctx); 97 | 98 | int input_fw_enable_touch_interrupts(struct kbd_ctx* ctx); 99 | int input_fw_disable_touch_interrupts(struct kbd_ctx* ctx); 100 | 101 | void input_fw_read_fifo(struct kbd_ctx* ctx); 102 | 103 | int input_fw_get_rtc(uint8_t* year, uint8_t* mon, uint8_t* day, 104 | uint8_t* hour, uint8_t* min, uint8_t* sec); 105 | int input_fw_set_rtc(uint8_t year, uint8_t mon, uint8_t day, 106 | uint8_t hour, uint8_t min, uint8_t sec); 107 | 108 | void input_fw_set_handle_poweroff(struct kbd_ctx* ctx, uint8_t handle_poweroff); 109 | void input_fw_set_auto_off(struct kbd_ctx* ctx, uint8_t auto_off); 110 | 111 | // RTC 112 | 113 | int input_rtc_probe(struct i2c_client* i2c_client, struct kbd_ctx *ctx); 114 | void input_rtc_shutdown(struct i2c_client* i2c_client, struct kbd_ctx *ctx); 115 | 116 | // Display 117 | 118 | int input_display_probe(struct i2c_client* i2c_client, struct kbd_ctx *ctx); 119 | void input_display_shutdown(struct i2c_client* i2c_client, struct kbd_ctx *ctx); 120 | 121 | int input_display_valid_sharp_path(char const* path); 122 | 123 | void input_display_invert(struct kbd_ctx* ctx); 124 | 125 | void input_display_set_indicator(int idx, unsigned char const* pixels); 126 | void input_display_clear_indicator(int idx); 127 | void input_display_clear_overlays(void); 128 | 129 | // Modifiers 130 | 131 | int input_modifiers_probe(struct i2c_client* i2c_client, struct kbd_ctx *ctx); 132 | void input_modifiers_shutdown(struct i2c_client* i2c_client, struct kbd_ctx *ctx); 133 | 134 | int input_modifiers_consumes_keycode(struct kbd_ctx* ctx, 135 | uint8_t *remapped_keycode, uint8_t keycode, uint8_t state); 136 | 137 | uint8_t input_modifiers_apply_pending(struct kbd_ctx* ctx, uint8_t keycode); 138 | void input_modifiers_reset(struct kbd_ctx* ctx); 139 | 140 | void input_modifiers_send_control(struct kbd_ctx* ctx); 141 | void input_modifiers_send_alt(struct kbd_ctx* ctx); 142 | 143 | void input_modifiers_reset_shift(struct kbd_ctx* ctx); 144 | 145 | // Touch 146 | 147 | int input_touch_probe(struct i2c_client* i2c_client, struct kbd_ctx *ctx); 148 | void input_touch_shutdown(struct i2c_client* i2c_client, struct kbd_ctx *ctx); 149 | 150 | void input_touch_report_event(struct kbd_ctx *ctx); 151 | 152 | int input_touch_consumes_keycode(struct kbd_ctx* ctx, 153 | uint8_t *remapped_keycode, uint8_t keycode, uint8_t state); 154 | 155 | void input_touch_enable(struct kbd_ctx *ctx); 156 | void input_touch_disable(struct kbd_ctx *ctx); 157 | 158 | void input_touch_set_activation(struct kbd_ctx *ctx, uint8_t activation); 159 | void input_touch_set_input_as(struct kbd_ctx *ctx, uint8_t input_as); 160 | 161 | void input_touch_set_threshold(struct kbd_ctx *ctx, uint8_t threshold); 162 | void input_touch_set_indicator(struct kbd_ctx *ctx); 163 | 164 | // Meta mode 165 | 166 | int input_meta_probe(struct i2c_client* i2c_client, struct kbd_ctx *ctx); 167 | void input_meta_shutdown(struct i2c_client* i2c_client, struct kbd_ctx *ctx); 168 | 169 | int input_meta_consumes_keycode(struct kbd_ctx* ctx, 170 | uint8_t *remapped_keycode, uint8_t keycode, uint8_t state); 171 | 172 | void input_meta_enable(struct kbd_ctx* ctx); 173 | void input_meta_disable(struct kbd_ctx* ctx); 174 | 175 | #endif 176 | -------------------------------------------------------------------------------- /src/input_meta.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | // Input meta mode subsystem 3 | 4 | #include 5 | 6 | #include "config.h" 7 | #include "input_iface.h" 8 | #include "params_iface.h" 9 | 10 | #include "indicators.h" 11 | 12 | #define SYMBOL_OVERLAY_PATH "/sbin/symbol-overlay" 13 | 14 | // Globals 15 | 16 | static uint8_t g_enabled; 17 | static uint8_t g_showing_indicator; 18 | // Clear the Meta menu overlay when Meta indicator cleared 19 | static uint8_t g_showing_overlay; 20 | // Store the last keycode sent to simulate a key 21 | // up event when the key is released after Meta is exited 22 | static uint8_t g_current_meta_keycode; 23 | 24 | // Call Meta menu overlay helper 25 | // Will return normally if overlay is not installed 26 | static void show_meta_menu(struct kbd_ctx* ctx) 27 | { 28 | static char const* overlay_argv[] = {SYMBOL_OVERLAY_PATH, "--meta", NULL, NULL}; 29 | 30 | if (g_showing_overlay) { 31 | return; 32 | } 33 | 34 | g_showing_overlay = 1; 35 | 36 | // Call overlay helper 37 | overlay_argv[2] = params_get_sharp_path(); 38 | call_usermodehelper(overlay_argv[0], (char**)overlay_argv, NULL, UMH_NO_WAIT); 39 | } 40 | 41 | // Called before checking "repeatable" meta mode keys, 42 | // These keys map to an internal driver function rather than another key 43 | // They will not be sent to the input system 44 | // The check is separate from the run so that key-up events can be ignored 45 | static bool is_single_function_key(struct kbd_ctx* ctx, uint8_t keycode) 46 | { 47 | switch (keycode) { 48 | case KEY_T: return TRUE; // Tab 49 | case KEY_X: return TRUE; // Control 50 | case KEY_C: return TRUE; // Alt 51 | case KEY_N: return TRUE; // Decrease brightness 52 | case KEY_M: return TRUE; // Increase brightness 53 | case KEY_MUTE: return TRUE; // Toggle brightness 54 | case KEY_0: return TRUE; // Invert display 55 | case KEY_ESC: return TRUE; // Exit meta mode 56 | case KEY_PROPS: return TRUE; // Exit meta mode 57 | } 58 | 59 | return FALSE; 60 | } 61 | 62 | // Return whether or not to exit meta mode 63 | static int run_single_function_key(struct kbd_ctx* ctx, uint8_t keycode) 64 | { 65 | switch (keycode) { 66 | 67 | case KEY_T: 68 | input_report_key(ctx->input_dev, KEY_TAB, 1); 69 | input_report_key(ctx->input_dev, KEY_TAB, 0); 70 | return 1; 71 | 72 | case KEY_X: 73 | input_modifiers_send_control(ctx); 74 | return 1; 75 | 76 | case KEY_C: 77 | input_modifiers_send_alt(ctx); 78 | return 1; 79 | 80 | case KEY_0: 81 | input_display_invert(ctx); 82 | return 1; 83 | 84 | case KEY_ESC: 85 | case KEY_PROPS: 86 | return 1; 87 | 88 | case KEY_N: input_fw_decrease_brightness(ctx); return 0; 89 | case KEY_M: input_fw_increase_brightness(ctx); return 0; 90 | case KEY_MUTE: 91 | input_fw_toggle_brightness(ctx); 92 | return 1; 93 | } 94 | 95 | return 0; 96 | } 97 | 98 | // Called after checking "single function" meta mode keys, 99 | // These keys, both press and release events, will be sent to the input system 100 | static uint8_t map_repeatable_key(struct kbd_ctx* ctx, uint8_t keycode) 101 | { 102 | switch (keycode) { 103 | 104 | case KEY_E: return KEY_UP; 105 | case KEY_S: return KEY_DOWN; 106 | case KEY_W: return KEY_LEFT; 107 | case KEY_D: return KEY_RIGHT; 108 | 109 | case KEY_R: return KEY_HOME; 110 | case KEY_F: return KEY_END; 111 | 112 | case KEY_O: return KEY_PAGEUP; 113 | case KEY_P: return KEY_PAGEDOWN; 114 | 115 | case KEY_Q: return 172; 116 | case KEY_A: return 173; 117 | } 118 | 119 | return 0; 120 | } 121 | 122 | int input_meta_probe(struct i2c_client* i2c_client, struct kbd_ctx *ctx) 123 | { 124 | g_enabled = 0; 125 | g_showing_indicator = 0; 126 | g_showing_overlay = 0; 127 | 128 | return 0; 129 | } 130 | 131 | void input_meta_shutdown(struct i2c_client* i2c_client, struct kbd_ctx *ctx) 132 | { 133 | input_meta_disable(ctx); 134 | } 135 | 136 | int input_meta_consumes_keycode(struct kbd_ctx* ctx, 137 | uint8_t *remapped_keycode, uint8_t keycode, uint8_t state) 138 | { 139 | uint8_t simulated_keycode; 140 | 141 | // Not in meta mode 142 | if (!g_enabled) { 143 | 144 | // Berry key enables meta mode 145 | if (keycode == KEY_PROPS) { 146 | 147 | if (state == KEY_STATE_RELEASED) { 148 | input_meta_enable(ctx); 149 | 150 | } else if (state == KEY_STATE_HOLD) { 151 | show_meta_menu(ctx); 152 | } 153 | return 1; 154 | } 155 | 156 | return 0; 157 | } 158 | 159 | // Ignore modifier keys in meta mode 160 | if ((keycode == KEY_LEFTSHIFT) || (keycode == KEY_RIGHTSHIFT) 161 | || (keycode == KEY_LEFTALT) || (keycode == KEY_RIGHTALT) 162 | || (keycode == KEY_LEFTCTRL) || (keycode == KEY_RIGHTCTRL)) { 163 | return 1; 164 | } 165 | 166 | // Keys in Meta mode will clear overlay 167 | if (g_showing_overlay) { 168 | input_display_clear_overlays(); 169 | g_showing_overlay = 0; 170 | // Re-display indicator after clearing 171 | if (g_showing_indicator) { 172 | input_display_set_indicator(5, ind_meta); 173 | } 174 | } 175 | 176 | // Handle function dispatch meta mode keys 177 | if (is_single_function_key(ctx, keycode)) { 178 | if (state == KEY_STATE_RELEASED) { 179 | 180 | // Function will return whether to exit meta 181 | if (run_single_function_key(ctx, keycode)) { 182 | input_meta_disable(ctx); 183 | } 184 | } 185 | return 1; 186 | } 187 | 188 | // Remap to meta mode key 189 | simulated_keycode = map_repeatable_key(ctx, keycode); 190 | 191 | // No mapped meta mode key, disable and pass through key 192 | if (simulated_keycode == 0) { 193 | input_meta_disable(ctx); 194 | return 0; 195 | } 196 | 197 | // Report key to input system 198 | input_report_key(ctx->input_dev, simulated_keycode, 199 | state == KEY_STATE_PRESSED); 200 | 201 | // Save remapped key 202 | g_current_meta_keycode = (state == KEY_STATE_PRESSED) 203 | ? simulated_keycode 204 | : 0; 205 | 206 | return 1; 207 | } 208 | 209 | void input_meta_enable(struct kbd_ctx* ctx) 210 | { 211 | g_enabled = 1; 212 | g_current_meta_keycode = 0; 213 | 214 | // Set display indicator 215 | if (!g_showing_indicator) { 216 | input_display_set_indicator(5, ind_meta); 217 | g_showing_indicator = 1; 218 | } 219 | } 220 | 221 | void input_meta_disable(struct kbd_ctx* ctx) 222 | { 223 | g_enabled = 0; 224 | 225 | // Clear display indicator 226 | if (g_showing_indicator) { 227 | input_display_clear_indicator(5); 228 | g_showing_indicator = 0; 229 | } 230 | 231 | // Simulate key up event 232 | if (g_current_meta_keycode) { 233 | input_report_key(ctx->input_dev, g_current_meta_keycode, FALSE); 234 | g_current_meta_keycode = 0; 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /src/input_modifiers.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | /* 3 | * Keyboard Driver for Blackberry Keyboards BBQ10 from arturo182. Software written by wallComputer. 4 | * input_modifiers.c: Sticky modifier keys implementation 5 | */ 6 | 7 | #include 8 | #include 9 | 10 | #include "config.h" 11 | #include "debug_levels.h" 12 | 13 | #include "input_iface.h" 14 | #include "params_iface.h" 15 | 16 | #include "bbq20kbd_pmod_codes.h" 17 | 18 | #include "indicators.h" 19 | 20 | #define SYMBOL_OVERLAY_PATH "/sbin/symbol-overlay" 21 | 22 | struct sticky_modifier 23 | { 24 | uint8_t active; 25 | uint8_t held; 26 | uint8_t pending; 27 | uint8_t sticky; 28 | uint8_t locked; 29 | 30 | // Keycode to send to the input system when applied 31 | uint8_t keycode; 32 | 33 | // Display indicator index and code 34 | uint8_t indicator_idx; 35 | unsigned char const* indicator_pixels; 36 | 37 | // When sticky modifier system has determined that 38 | // modifier should be applied, run this callback 39 | // and report the returned keycode result to the input system 40 | void (*set_callback)(struct kbd_ctx* ctx, struct sticky_modifier const* sticky_modifier); 41 | void (*unset_callback)(struct kbd_ctx* ctx, struct sticky_modifier const* sticky_modifier); 42 | void (*lock_callback)(struct kbd_ctx* ctx, struct sticky_modifier* sticky_modifier); 43 | uint8_t(*map_callback)(struct kbd_ctx* ctx, uint8_t keycode); 44 | }; 45 | 46 | // Globals 47 | 48 | // "Real" modifiers like Shift and Control are handled by simulating 49 | // input key events. Since phys. alt is hardcoded, the state is here 50 | static uint8_t g_apply_phys_alt; 51 | // Store the last keycode sent in the phys. alt map to simulate a key 52 | // up event when the key is released after phys. alt is released 53 | static uint8_t g_current_phys_alt_keycode; 54 | // Clear the symbol menu overlay when Sym indicator cleared 55 | static uint8_t g_showing_sym_menu; 56 | 57 | // Sticky modifier structs 58 | static struct sticky_modifier g_sticky_ctrl; 59 | static struct sticky_modifier g_sticky_shift; 60 | static struct sticky_modifier g_sticky_phys_alt; 61 | static struct sticky_modifier g_sticky_alt; 62 | static struct sticky_modifier g_sticky_altgr; 63 | 64 | // Sticky modifier helpers 65 | 66 | static void press_sticky_modifier(struct kbd_ctx* ctx, struct sticky_modifier const* sticky_modifier) 67 | { 68 | input_report_key(ctx->input_dev, sticky_modifier->keycode, TRUE); 69 | } 70 | 71 | static void release_sticky_modifier(struct kbd_ctx* ctx, struct sticky_modifier const* sticky_modifier) 72 | { 73 | input_report_key(ctx->input_dev, sticky_modifier->keycode, FALSE); 74 | } 75 | 76 | static void lock_sticky_modifier(struct kbd_ctx* ctx, struct sticky_modifier* sticky_modifier) 77 | { 78 | sticky_modifier->locked = 1; 79 | 80 | // Report modifier to input system as pressed 81 | sticky_modifier->set_callback(ctx, sticky_modifier); 82 | } 83 | 84 | static void enable_phys_alt(struct kbd_ctx* ctx, struct sticky_modifier const* sticky_modifier) 85 | { 86 | g_apply_phys_alt = 1; 87 | } 88 | 89 | static void disable_phys_alt(struct kbd_ctx* ctx, struct sticky_modifier const* sticky_modifier) 90 | { 91 | // Send key up event if there is a current phys. alt key being held 92 | if (g_current_phys_alt_keycode) { 93 | input_report_key(ctx->input_dev, g_current_phys_alt_keycode, FALSE); 94 | g_current_phys_alt_keycode = 0; 95 | } 96 | 97 | g_apply_phys_alt = 0; 98 | } 99 | 100 | static uint8_t map_phys_alt_keycode(struct kbd_ctx* ctx, uint8_t keycode) 101 | { 102 | if (!g_apply_phys_alt) { 103 | return keycode; 104 | } 105 | 106 | keycode += 119; // See map file for result keys 107 | g_current_phys_alt_keycode = keycode; 108 | return keycode; 109 | } 110 | 111 | // Call symbol menu overlay helper 112 | // Will return normally if overlay is not installed 113 | static void show_sym_menu(struct kbd_ctx* ctx, struct sticky_modifier* sticky_modifier) 114 | { 115 | static char const* overlay_argv[] = {SYMBOL_OVERLAY_PATH, NULL, NULL}; 116 | 117 | g_showing_sym_menu = 1; 118 | 119 | // Call overlay helper 120 | overlay_argv[1] = params_get_sharp_path(); 121 | call_usermodehelper(overlay_argv[0], (char**)overlay_argv, NULL, UMH_NO_WAIT); 122 | } 123 | 124 | // Sticky modifier keys follow BB Q10 convention 125 | // Holding modifier while typing alpha keys will apply to all alpha keys 126 | // until released. 127 | // One press and release will enter sticky mode, apply modifier key to 128 | // the next alpha key only. If the same modifier key is pressed and 129 | // released again in sticky mode, it will be canceled. 130 | static void transition_sticky_modifier(struct kbd_ctx* ctx, 131 | struct sticky_modifier* mod, enum rp2040_key_state state) 132 | { 133 | if (state == KEY_STATE_PRESSED) { 134 | 135 | // Set "held" state 136 | mod->held = 1; 137 | 138 | // If pressed again while sticky, clear sticky 139 | if (mod->sticky) { 140 | mod->sticky = 0; 141 | 142 | // Otherwise, set pending sticky to be applied on release 143 | } else { 144 | mod->pending = 1; 145 | } 146 | 147 | // In locked mode 148 | if (mod->locked) { 149 | 150 | // Clear lock mode 151 | mod->locked = 0; 152 | 153 | // Clear pending sticky so that it is not applied to next key 154 | mod->pending = 0; 155 | } 156 | 157 | // Report modifier to input system as pressed 158 | mod->set_callback(ctx, mod); 159 | 160 | // Set display indicator 161 | if (mod->indicator_pixels) { 162 | input_display_set_indicator(mod->indicator_idx, 163 | mod->indicator_pixels); 164 | } 165 | 166 | // Released 167 | } else if (state == KEY_STATE_RELEASED) { 168 | 169 | // Unset "held" state 170 | mod->held = 0; 171 | 172 | // Not in locked mode 173 | if (!mod->locked) { 174 | 175 | // If any alpha key was typed during hold, 176 | // `apply_sticky_modifiers` will clear "pending sticky" state. 177 | // If still in "pending sticky", set "sticky" state. 178 | if (mod->pending) { 179 | 180 | mod->sticky = 1; 181 | mod->pending = 0; 182 | 183 | } else { 184 | // Clear display indicator 185 | input_display_clear_indicator(mod->indicator_idx); 186 | 187 | // Clear symbol menu overlay if it was showing 188 | if (mod->keycode == KEY_RIGHTALT) { 189 | input_display_clear_overlays(); 190 | g_showing_sym_menu = 0; 191 | } 192 | } 193 | 194 | // Report modifier to input system as released 195 | mod->unset_callback(ctx, mod); 196 | } 197 | 198 | // Held 199 | } else if (state == KEY_STATE_HOLD) { 200 | 201 | // If any alpha key was typed during hold, 202 | // `apply_sticky_modifiers` will clear "pending sticky" state. 203 | // If still in "pending sticky", set locked mode 204 | if (mod->pending && mod->lock_callback) { 205 | mod->lock_callback(ctx, mod); 206 | } 207 | } 208 | } 209 | 210 | // Called before sending an alpha key to apply any pending sticky modifiers 211 | static void apply_sticky_modifier(struct kbd_ctx* ctx, 212 | struct sticky_modifier* mod) 213 | { 214 | if (mod->held) { 215 | mod->pending = 0; 216 | 217 | } else if (mod->sticky) { 218 | mod->set_callback(ctx, mod); 219 | } 220 | } 221 | 222 | // Called after sending the alpha key to reset 223 | // any sticky modifiers 224 | static void reset_sticky_modifier(struct kbd_ctx* ctx, 225 | struct sticky_modifier* mod) 226 | { 227 | if (mod->sticky) { 228 | mod->sticky = 0; 229 | 230 | mod->unset_callback(ctx, mod); 231 | 232 | // Clear display indicator 233 | input_display_clear_indicator(mod->indicator_idx); 234 | 235 | // Clear symbol menu overlay if it was showing 236 | if (mod->keycode == KEY_RIGHTALT) { 237 | input_display_clear_overlays(); 238 | g_showing_sym_menu = 0; 239 | } 240 | } 241 | } 242 | 243 | int input_modifiers_consumes_keycode(struct kbd_ctx* ctx, 244 | uint8_t *remapped_keycode, uint8_t keycode, uint8_t state) 245 | { 246 | 247 | if ((keycode == KEY_LEFTSHIFT) || (keycode == KEY_RIGHTSHIFT)) { 248 | transition_sticky_modifier(ctx, &g_sticky_shift, state); 249 | return 1; 250 | 251 | } else if (keycode == KEY_LEFTALT) { 252 | transition_sticky_modifier(ctx, &g_sticky_phys_alt, state); 253 | return 1; 254 | 255 | } else if (keycode == KEY_RIGHTALT) { 256 | transition_sticky_modifier(ctx, &g_sticky_altgr, state); 257 | return 1; 258 | 259 | } else if (keycode == KEY_OPEN) { 260 | transition_sticky_modifier(ctx, &g_sticky_ctrl, state); 261 | return 1; 262 | } 263 | 264 | return 0; 265 | } 266 | 267 | uint8_t input_modifiers_apply_pending(struct kbd_ctx* ctx, uint8_t keycode) 268 | { 269 | // Apply pending sticky modifiers 270 | apply_sticky_modifier(ctx, &g_sticky_shift); 271 | apply_sticky_modifier(ctx, &g_sticky_ctrl); 272 | apply_sticky_modifier(ctx, &g_sticky_phys_alt); 273 | apply_sticky_modifier(ctx, &g_sticky_alt); 274 | apply_sticky_modifier(ctx, &g_sticky_altgr); 275 | 276 | // Map phys. alt 277 | return g_sticky_phys_alt.map_callback(ctx, keycode); 278 | } 279 | 280 | void input_modifiers_reset(struct kbd_ctx* ctx) 281 | { 282 | // Reset sticky modifiers 283 | reset_sticky_modifier(ctx, &g_sticky_shift); 284 | reset_sticky_modifier(ctx, &g_sticky_ctrl); 285 | reset_sticky_modifier(ctx, &g_sticky_phys_alt); 286 | reset_sticky_modifier(ctx, &g_sticky_alt); 287 | reset_sticky_modifier(ctx, &g_sticky_altgr); 288 | } 289 | 290 | void input_modifiers_send_control(struct kbd_ctx* ctx) 291 | { 292 | transition_sticky_modifier(ctx, &g_sticky_ctrl, KEY_STATE_PRESSED); 293 | transition_sticky_modifier(ctx, &g_sticky_ctrl, KEY_STATE_RELEASED); 294 | } 295 | 296 | void input_modifiers_send_alt(struct kbd_ctx* ctx) 297 | { 298 | transition_sticky_modifier(ctx, &g_sticky_alt, KEY_STATE_PRESSED); 299 | transition_sticky_modifier(ctx, &g_sticky_alt, KEY_STATE_RELEASED); 300 | } 301 | 302 | static void default_init_sticky_modifier(struct sticky_modifier* mod) 303 | { 304 | mod->held = 0; 305 | mod->pending = 0; 306 | mod->sticky = 0; 307 | mod->locked = 0; 308 | 309 | mod->keycode = 0; 310 | mod->set_callback = NULL; 311 | mod->unset_callback = NULL; 312 | mod->map_callback = NULL; 313 | mod->indicator_idx = 0; 314 | mod->indicator_pixels = NULL; 315 | } 316 | 317 | int input_modifiers_probe(struct i2c_client* i2c_client, struct kbd_ctx *ctx) 318 | { 319 | g_apply_phys_alt = 0; 320 | g_current_phys_alt_keycode = 0; 321 | g_showing_sym_menu = 0; 322 | 323 | // Initialize sticky modifiers 324 | default_init_sticky_modifier(&g_sticky_ctrl); 325 | g_sticky_ctrl.keycode = KEY_LEFTCTRL; 326 | g_sticky_ctrl.set_callback = press_sticky_modifier; 327 | g_sticky_ctrl.unset_callback = release_sticky_modifier; 328 | g_sticky_ctrl.lock_callback = lock_sticky_modifier; 329 | g_sticky_ctrl.indicator_idx = 2; 330 | g_sticky_ctrl.indicator_pixels = ind_control; 331 | 332 | default_init_sticky_modifier(&g_sticky_shift); 333 | g_sticky_shift.keycode = KEY_LEFTSHIFT; 334 | g_sticky_shift.set_callback = press_sticky_modifier; 335 | g_sticky_shift.unset_callback = release_sticky_modifier; 336 | g_sticky_shift.lock_callback = lock_sticky_modifier; 337 | g_sticky_shift.indicator_idx = 0; 338 | g_sticky_shift.indicator_pixels = ind_shift; 339 | 340 | default_init_sticky_modifier(&g_sticky_phys_alt); 341 | g_sticky_phys_alt.keycode = KEY_RIGHTCTRL; 342 | g_sticky_phys_alt.set_callback = enable_phys_alt; 343 | g_sticky_phys_alt.unset_callback = disable_phys_alt; 344 | g_sticky_phys_alt.lock_callback = lock_sticky_modifier; 345 | g_sticky_phys_alt.map_callback = map_phys_alt_keycode; 346 | g_sticky_phys_alt.indicator_idx = 1; 347 | g_sticky_phys_alt.indicator_pixels = ind_phys_alt; 348 | 349 | default_init_sticky_modifier(&g_sticky_alt); 350 | g_sticky_alt.keycode = KEY_LEFTALT; 351 | g_sticky_alt.set_callback = press_sticky_modifier; 352 | g_sticky_alt.unset_callback = release_sticky_modifier; 353 | g_sticky_alt.lock_callback = lock_sticky_modifier; 354 | g_sticky_alt.indicator_idx = 3; 355 | g_sticky_alt.indicator_pixels = ind_alt; 356 | 357 | default_init_sticky_modifier(&g_sticky_altgr); 358 | g_sticky_altgr.keycode = KEY_RIGHTALT; 359 | g_sticky_altgr.set_callback = press_sticky_modifier; 360 | g_sticky_altgr.unset_callback = release_sticky_modifier; 361 | g_sticky_altgr.lock_callback = show_sym_menu; 362 | g_sticky_altgr.indicator_idx = 4; 363 | g_sticky_altgr.indicator_pixels = ind_altgr; 364 | 365 | return 0; 366 | } 367 | 368 | void input_modifiers_shutdown(struct i2c_client* i2c_client, struct kbd_ctx *ctx) 369 | {} 370 | 371 | // Clear the shift held state 372 | // Touch layer enables touch scrolling while shift is held, 373 | // so if any touch input was entered, it will clear pending shift 374 | void input_modifiers_reset_shift(struct kbd_ctx* ctx) 375 | { 376 | g_sticky_shift.pending = 0; 377 | g_sticky_shift.unset_callback(ctx, &g_sticky_shift); 378 | input_display_clear_indicator(g_sticky_shift.indicator_idx); 379 | } 380 | -------------------------------------------------------------------------------- /src/input_rtc.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | // Input RTC subsystem 3 | 4 | #include 5 | 6 | #include "config.h" 7 | #include "input_iface.h" 8 | 9 | static int i2c_set_time(struct device *dev, struct rtc_time *tm) 10 | { 11 | int rc; 12 | 13 | if ((rc = input_fw_set_rtc((uint8_t)tm->tm_year, (uint8_t)tm->tm_mon, 14 | (uint8_t)tm->tm_mday, (uint8_t)tm->tm_hour, (uint8_t)tm->tm_min, 15 | (uint8_t)tm->tm_sec))) { 16 | printk(KERN_ERR "i2c_set_time failed: %d\n", rc); 17 | return rc; 18 | } 19 | 20 | printk(KERN_INFO "beepy-kbd: updated RTC\n"); 21 | 22 | return 0; 23 | } 24 | 25 | static int i2c_read_time(struct device *dev, struct rtc_time *tm) 26 | { 27 | int rc; 28 | uint8_t year, mon, mday, hour, min, sec; 29 | 30 | if ((rc = input_fw_get_rtc(&year, &mon, &mday, &hour, &min, &sec))) { 31 | printk(KERN_ERR "i2c_read_time failed: %d\n", rc); 32 | return rc; 33 | } 34 | 35 | tm->tm_year = year; 36 | tm->tm_mon = mon; 37 | tm->tm_mday = mday; 38 | tm->tm_hour = hour; 39 | tm->tm_min = min; 40 | tm->tm_sec = sec; 41 | 42 | return 0; 43 | } 44 | 45 | static const struct rtc_class_ops beepy_rtc_ops = { 46 | .read_time = i2c_read_time, 47 | .set_time = i2c_set_time, 48 | }; 49 | 50 | int input_rtc_probe(struct i2c_client* i2c_client, struct kbd_ctx *ctx) 51 | { 52 | struct rtc_device *rtc; 53 | 54 | // Register RTC device 55 | rtc = devm_rtc_device_register(&i2c_client->dev, 56 | "beepy-rtc", &beepy_rtc_ops, THIS_MODULE); 57 | if (IS_ERR(rtc)) { 58 | dev_err(&i2c_client->dev, 59 | "Failed to register RTC device\n"); 60 | return PTR_ERR(rtc); 61 | } 62 | 63 | return 0; 64 | } 65 | 66 | void input_rtc_shutdown(struct i2c_client* i2c_client, struct kbd_ctx *ctx) 67 | {} 68 | -------------------------------------------------------------------------------- /src/input_touch.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | /* 3 | * Keyboard Driver for Blackberry Keyboards BBQ10 from arturo182. Software written by wallComputer. 4 | * input_iface.c: Key handler implementation 5 | */ 6 | 7 | #include 8 | #include 9 | 10 | #include "config.h" 11 | #include "debug_levels.h" 12 | 13 | #include "input_iface.h" 14 | #include "i2c_helper.h" 15 | 16 | #include "indicators.h" 17 | 18 | static uint8_t g_touch_indicator = 0; 19 | 20 | static void enable_scale_2x(struct kbd_ctx* ctx) 21 | { 22 | uint8_t reg; 23 | 24 | // Set touchpad scaling factor to 2x for X and Y 25 | kbd_write_i2c_u8(ctx->i2c_client, REG_TOUCHPAD_REG, REG_TOUCHPAD_REG_SPEED); 26 | kbd_read_i2c_u8(ctx->i2c_client, REG_TOUCHPAD_VAL, ®); 27 | reg |= REG_TOUCHPAD_SPEED_X_SCALE2; 28 | reg |= REG_TOUCHPAD_SPEED_Y_SCALE2; 29 | kbd_write_i2c_u8(ctx->i2c_client, REG_TOUCHPAD_VAL, reg); 30 | 31 | // Enable touchpad scaling 32 | kbd_write_i2c_u8(ctx->i2c_client, REG_TOUCHPAD_REG, REG_TOUCHPAD_REG_ENGINE); 33 | kbd_read_i2c_u8(ctx->i2c_client, REG_TOUCHPAD_VAL, ®); 34 | reg |= REG_TOUCHPAD_ENGINE_XY_SCALE; 35 | kbd_write_i2c_u8(ctx->i2c_client, REG_TOUCHPAD_VAL, reg); 36 | } 37 | 38 | static void disable_scale_2x(struct kbd_ctx* ctx) 39 | { 40 | uint8_t reg; 41 | 42 | // Clear touchpad scaling factor to 2x for X and Y 43 | kbd_write_i2c_u8(ctx->i2c_client, REG_TOUCHPAD_REG, REG_TOUCHPAD_REG_SPEED); 44 | kbd_read_i2c_u8(ctx->i2c_client, REG_TOUCHPAD_VAL, ®); 45 | reg &= ~REG_TOUCHPAD_SPEED_X_SCALE2; 46 | reg &= ~REG_TOUCHPAD_SPEED_Y_SCALE2; 47 | kbd_write_i2c_u8(ctx->i2c_client, REG_TOUCHPAD_VAL, reg); 48 | 49 | // Clear touchpad scaling 50 | kbd_write_i2c_u8(ctx->i2c_client, REG_TOUCHPAD_REG, REG_TOUCHPAD_REG_ENGINE); 51 | kbd_read_i2c_u8(ctx->i2c_client, REG_TOUCHPAD_VAL, ®); 52 | reg &= ~REG_TOUCHPAD_ENGINE_XY_SCALE; 53 | kbd_write_i2c_u8(ctx->i2c_client, REG_TOUCHPAD_VAL, reg); 54 | } 55 | 56 | int input_touch_probe(struct i2c_client* i2c_client, struct kbd_ctx *ctx) 57 | { 58 | ctx->touch.x = 0; 59 | ctx->touch.dx = 0; 60 | ctx->touch.y = 0; 61 | ctx->touch.dy = 0; 62 | 63 | ctx->touch.enable_while_shift_held = 1; 64 | ctx->touch.entry_while_shift_held = 0; 65 | ctx->touch.threshold = 8; 66 | 67 | // Default touch settings 68 | input_touch_set_activation(ctx, TOUCH_ACT_CLICK); 69 | input_touch_set_input_as(ctx, TOUCH_INPUT_AS_KEYS); 70 | 71 | return 0; 72 | } 73 | 74 | void input_touch_shutdown(struct i2c_client* i2c_client, struct kbd_ctx *ctx) 75 | {} 76 | 77 | void input_touch_report_event(struct kbd_ctx *ctx) 78 | { 79 | uint8_t x_threshold, y_threshold; 80 | #if (DEBUG_LEVEL & DEBUG_LEVEL_FE) 81 | uint8_t qual; 82 | #endif 83 | 84 | if (!ctx || !ctx->touch.enabled 85 | || ((ctx->touch.dx == 0) && (ctx->touch.dy == 0))) { 86 | return; 87 | } 88 | 89 | // Set minimum touch thresholds 90 | x_threshold = ctx->touch.threshold; 91 | y_threshold = ctx->touch.threshold; 92 | 93 | // Log touchpad surface quality 94 | #if (DEBUG_LEVEL & DEBUG_LEVEL_FE) 95 | kbd_write_i2c_u8(ctx->i2c_client, REG_TOUCHPAD_REG, 0x05); 96 | kbd_read_i2c_u8(ctx->i2c_client, REG_TOUCHPAD_VAL, &qual); 97 | 98 | dev_info_fe(&ctx->i2c_client->dev, 99 | "Touch (%d, %d) Qual %d\n", 100 | ctx->touch.dx, ctx->touch.dy, qual); 101 | #endif 102 | 103 | // Report mouse movement 104 | if (ctx->touch.input_as == TOUCH_INPUT_AS_MOUSE) { 105 | 106 | // Report mouse movement 107 | input_report_rel(ctx->input_dev, REL_X, (int8_t)ctx->touch.dx); 108 | input_report_rel(ctx->input_dev, REL_Y, (int8_t)ctx->touch.dy); 109 | ctx->touch.dx = 0; 110 | ctx->touch.dy = 0; 111 | 112 | // Reset shift sticky state if touch entry was sent while held 113 | ctx->touch.entry_while_shift_held = 1; 114 | 115 | // Report arrow key movement 116 | } else if (ctx->touch.input_as == TOUCH_INPUT_AS_KEYS) { 117 | 118 | // Accumulate X / Y 119 | ctx->touch.x += ctx->touch.dx; 120 | ctx->touch.y += ctx->touch.dy; 121 | 122 | // Snap movement to arrow keys directions 123 | if ((ctx->touch.dx == 0) && (abs(ctx->touch.x) < x_threshold)) { 124 | ctx->touch.x = 0; 125 | } 126 | if ((ctx->touch.dy == 0) && (abs(ctx->touch.y) < y_threshold)) { 127 | ctx->touch.y = 0; 128 | } 129 | ctx->touch.dx = 0; 130 | ctx->touch.dy = 0; 131 | 132 | // Reset shift sticky state if touch entry was sent while held 133 | ctx->touch.entry_while_shift_held = 1; 134 | 135 | // Negative X: left arrow key 136 | if (ctx->touch.x <= -x_threshold) { 137 | 138 | do { 139 | input_report_key(ctx->input_dev, KEY_LEFT, TRUE); 140 | input_report_key(ctx->input_dev, KEY_LEFT, FALSE); 141 | ctx->touch.x += x_threshold; 142 | } while (ctx->touch.x <= -x_threshold); 143 | 144 | // Positive X: right arrow key 145 | } else if (ctx->touch.x > x_threshold) { 146 | 147 | do { 148 | input_report_key(ctx->input_dev, KEY_RIGHT, TRUE); 149 | input_report_key(ctx->input_dev, KEY_RIGHT, FALSE); 150 | ctx->touch.x -= x_threshold; 151 | } while (ctx->touch.x > x_threshold); 152 | } 153 | 154 | // Negative Y: up arrow key 155 | if (ctx->touch.y <= -y_threshold) { 156 | 157 | do { 158 | input_report_key(ctx->input_dev, KEY_UP, TRUE); 159 | input_report_key(ctx->input_dev, KEY_UP, FALSE); 160 | ctx->touch.y += y_threshold; 161 | } while (ctx->touch.y <= -y_threshold); 162 | 163 | // Positive Y: down arrow key 164 | } else if (ctx->touch.y > y_threshold) { 165 | 166 | do { 167 | input_report_key(ctx->input_dev, KEY_DOWN, TRUE); 168 | input_report_key(ctx->input_dev, KEY_DOWN, FALSE); 169 | ctx->touch.y -= y_threshold; 170 | } while (ctx->touch.y > y_threshold); 171 | } 172 | } 173 | } 174 | 175 | // Touch enabled: touchpad click sends enter / mouse click 176 | // Touch disabled: touchpad click enables touch mode 177 | int input_touch_consumes_keycode(struct kbd_ctx* ctx, 178 | uint8_t *remapped_keycode, uint8_t keycode, uint8_t state) 179 | { 180 | // Touchpad click 181 | // Touch off: enable touch 182 | // Touch on: enter or mouse click 183 | if (keycode == KEY_COMPOSE) { 184 | 185 | if (ctx->touch.enabled) { 186 | 187 | // Keys mode, send enter 188 | if ((ctx->touch.input_as == TOUCH_INPUT_AS_KEYS) 189 | && (state == KEY_STATE_RELEASED)) { 190 | input_report_key(ctx->input_dev, KEY_ENTER, TRUE); 191 | input_report_key(ctx->input_dev, KEY_ENTER, FALSE); 192 | 193 | // Mouse mode, send mouse click 194 | } else if (ctx->touch.input_as == TOUCH_INPUT_AS_MOUSE) { 195 | input_report_key(ctx->input_dev, BTN_LEFT, 196 | (state == KEY_STATE_PRESSED)); 197 | } 198 | 199 | return 1; 200 | 201 | // If touch off, touchpad click will turn touch on 202 | } else if (state == KEY_STATE_RELEASED) { 203 | input_touch_enable(ctx); 204 | 205 | // Don't show indicator in mouse mode 206 | if (ctx->touch.input_as == TOUCH_INPUT_AS_KEYS) { 207 | input_touch_set_indicator(ctx); 208 | } 209 | } 210 | 211 | // Back key disables touch mode if touch enabled 212 | } else if (ctx->touch.enabled && (keycode == KEY_ESC)) { 213 | 214 | if (state == KEY_STATE_RELEASED) { 215 | input_touch_disable(ctx); 216 | } 217 | 218 | return 1; 219 | 220 | // Enable touch while shift is held 221 | } else if ((keycode == KEY_LEFTSHIFT) || (keycode == KEY_RIGHTSHIFT)) { 222 | if ((ctx->touch.activation == TOUCH_ACT_CLICK) 223 | && ctx->touch.enable_while_shift_held) { 224 | 225 | if (!ctx->touch.enabled && (state == KEY_STATE_PRESSED)) { 226 | ctx->touch.entry_while_shift_held = 0; 227 | input_touch_enable(ctx); 228 | 229 | } else if (ctx->touch.enabled && (state == KEY_STATE_RELEASED)) { 230 | input_touch_disable(ctx); 231 | if (ctx->touch.entry_while_shift_held) { 232 | ctx->touch.entry_while_shift_held = 0; 233 | input_modifiers_reset_shift(ctx); 234 | } 235 | } 236 | } 237 | } 238 | 239 | return 0; 240 | } 241 | 242 | void input_touch_enable(struct kbd_ctx *ctx) 243 | { 244 | ctx->touch.enabled = 1; 245 | input_fw_enable_touch_interrupts(ctx); 246 | } 247 | 248 | void input_touch_disable(struct kbd_ctx *ctx) 249 | { 250 | ctx->touch.enabled = 0; 251 | input_fw_disable_touch_interrupts(ctx); 252 | 253 | if (g_touch_indicator) { 254 | g_touch_indicator = 0; 255 | input_display_clear_indicator(6); 256 | } 257 | } 258 | 259 | void input_touch_set_activation(struct kbd_ctx *ctx, uint8_t activation) 260 | { 261 | if (activation == TOUCH_ACT_ALWAYS) { 262 | ctx->touch.activation = TOUCH_ACT_ALWAYS; 263 | input_touch_enable(ctx); 264 | 265 | } else if (activation == TOUCH_ACT_CLICK) { 266 | ctx->touch.activation = TOUCH_ACT_CLICK; 267 | input_touch_disable(ctx); 268 | } 269 | } 270 | 271 | void input_touch_set_input_as(struct kbd_ctx *ctx, uint8_t input_as) 272 | { 273 | ctx->touch.input_as = input_as; 274 | 275 | // Scale setting for touch input as keys 276 | if (input_as == TOUCH_INPUT_AS_KEYS) { 277 | enable_scale_2x(ctx); 278 | 279 | // Mouse does not apply scaling 280 | } else if (input_as == TOUCH_INPUT_AS_MOUSE) { 281 | disable_scale_2x(ctx); 282 | } 283 | } 284 | 285 | void input_touch_set_threshold(struct kbd_ctx *ctx, uint8_t threshold) 286 | { 287 | ctx->touch.threshold = threshold; 288 | } 289 | 290 | void input_touch_set_indicator(struct kbd_ctx *ctx) 291 | { 292 | g_touch_indicator = 1; 293 | input_display_set_indicator(6, ind_touch); 294 | } 295 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | /* 3 | * Keyboard Driver for Blackberry Keyboards BBQ10 from arturo182. Software written by wallComputer. 4 | * main.c: Main C File. 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "config.h" 13 | #include "debug_levels.h" 14 | 15 | #include "input_iface.h" 16 | #include "params_iface.h" 17 | #include "sysfs_iface.h" 18 | 19 | #if (BBQX0KBD_INT != BBQX0KBD_USE_INT) 20 | #error "Only supporting interrupts mode right now" 21 | #endif 22 | 23 | #if (BBQX0KBD_TYPE != BBQ20KBD_PMOD) 24 | #error "Only supporting BBQ20 keyboard right now" 25 | #endif 26 | 27 | static int beepy_kbd_probe 28 | #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0) 29 | (struct i2c_client* i2c_client, struct i2c_device_id const* i2c_id) 30 | #else 31 | (struct i2c_client* i2c_client) 32 | #endif 33 | { 34 | int rc; 35 | 36 | // Initialize key handler system 37 | if ((rc = input_probe(i2c_client))) { 38 | return rc; 39 | } 40 | 41 | // Initialize module parameters 42 | if ((rc = params_probe())) { 43 | return rc; 44 | } 45 | 46 | // Initialize sysfs interface 47 | if ((rc = sysfs_probe(i2c_client))) { 48 | return rc; 49 | } 50 | 51 | return 0; 52 | } 53 | 54 | static void beepy_kbd_shutdown(struct i2c_client* i2c_client) 55 | { 56 | sysfs_shutdown(i2c_client); 57 | params_shutdown(); 58 | input_shutdown(i2c_client); 59 | } 60 | 61 | static void beepy_kbd_remove(struct i2c_client* i2c_client) 62 | { 63 | dev_info_fe(&i2c_client->dev, 64 | "%s Removing beepy-kbd.\n", __func__); 65 | 66 | beepy_kbd_shutdown(i2c_client); 67 | } 68 | 69 | // Driver definitions 70 | 71 | // Device IDs 72 | static const struct i2c_device_id beepy_kbd_i2c_device_id[] = { 73 | { "beepy-kbd", 0, }, 74 | { } 75 | }; 76 | MODULE_DEVICE_TABLE(i2c, beepy_kbd_i2c_device_id); 77 | static const struct of_device_id beepy_kbd_of_device_id[] = { 78 | { .compatible = "beepy-kbd", }, 79 | { } 80 | }; 81 | MODULE_DEVICE_TABLE(of, beepy_kbd_of_device_id); 82 | 83 | // Callbacks 84 | static struct i2c_driver beepy_kbd_driver = { 85 | .driver = { 86 | .name = "beepy-kbd", 87 | .of_match_table = beepy_kbd_of_device_id, 88 | }, 89 | .probe = beepy_kbd_probe, 90 | .shutdown = beepy_kbd_shutdown, 91 | .remove = beepy_kbd_remove, 92 | .id_table = beepy_kbd_i2c_device_id, 93 | }; 94 | 95 | // Module constructor 96 | static int __init beepy_kbd_init(void) 97 | { 98 | int rc; 99 | 100 | // Adding the I2C driver will call the _probe function to continue setup 101 | if ((rc = i2c_add_driver(&beepy_kbd_driver))) { 102 | pr_err("%s Could not initialise beepy-kbd! Error: %d\n", 103 | __func__, rc); 104 | return rc; 105 | } 106 | pr_info("%s Initalised beepy-kbd.\n", __func__); 107 | 108 | return rc; 109 | } 110 | module_init(beepy_kbd_init); 111 | 112 | // Module destructor 113 | static void __exit beepy_kbd_exit(void) 114 | { 115 | pr_info("%s Exiting beepy-kbd.\n", __func__); 116 | i2c_del_driver(&beepy_kbd_driver); 117 | } 118 | module_exit(beepy_kbd_exit); 119 | 120 | MODULE_LICENSE("GPL"); 121 | MODULE_AUTHOR("wallComputer and Andrew D'Angelo "); 122 | MODULE_DESCRIPTION("BB Classic keyboard driver for Beepy"); 123 | MODULE_VERSION("2.11"); 124 | -------------------------------------------------------------------------------- /src/params_iface.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | /* 3 | * Keyboard Driver for Blackberry Keyboards BBQ10 from arturo182. Software written by wallComputer. 4 | * params_iface.c: Module parameters 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "config.h" 12 | 13 | #include "i2c_helper.h" 14 | #include "input_iface.h" 15 | #include "params_iface.h" 16 | 17 | // Kernel module parameters 18 | static char *touch_act_setting = "click"; // "click" or "always" 19 | static char *touch_shift_setting = "1"; // Hold Shift to temporarily enable touch 20 | static char *touch_as_setting = "keys"; // "keys" or "mouse" 21 | static char *touch_min_squal_setting = "16"; // Minimum surface quality to accept touch event 22 | static char *touch_led_setting = "high"; // "low", "med", "high" 23 | static uint32_t touch_threshold_setting = 8; // Touchpad move offset 24 | static char *handle_poweroff_setting = "0"; // Enable to have module invoke poweroff 25 | static char *shutdown_grace_setting = "30"; // 30 seconds between shutdown signal and poweroff 26 | static char *sharp_path_setting = "/dev/dri/card0"; // Path to Sharp display device 27 | static uint32_t sysfs_gid_setting = 0; // GID of files in /sys/firmware/beepy 28 | static char *auto_off_setting = // Enable to trigger a 30 second poweroff timer on driver unload 29 | #ifdef DEBUG 30 | "0"; 31 | #else 32 | "1"; 33 | #endif 34 | 35 | // Update touchpad activation setting in global context, if available 36 | static int set_touch_act_setting(struct kbd_ctx* ctx, char const* val) 37 | { 38 | // If no state was passed, just update the local setting without 39 | // changing I2C touch interrupts 40 | if (!ctx) { 41 | return 0; 42 | } 43 | 44 | // Touchpad only active when clicked 45 | if (strcmp(val, "click") == 0) { 46 | input_touch_set_activation(ctx, TOUCH_ACT_CLICK); 47 | return 0; 48 | 49 | // Touchpad always active 50 | } else if (strcmp(val, "always") == 0) { 51 | input_touch_set_activation(ctx, TOUCH_ACT_ALWAYS); 52 | return 0; 53 | } 54 | 55 | // Invalid parameter value 56 | return -1; 57 | } 58 | 59 | // Copy provided value to buffer and strip it of newlines 60 | static char* copy_and_strip(char* buf, size_t buf_len, const char* val) 61 | { 62 | strncpy(buf, val, buf_len - 1); 63 | buf[buf_len - 1] = '\0'; 64 | return strstrip(buf); 65 | } 66 | 67 | // Activate touch when clicked, or always enabled 68 | static int touch_act_setting_param_set(const char *val, const struct kernel_param* kp) 69 | { 70 | char buf[8]; 71 | char *stripped_val; 72 | 73 | stripped_val = copy_and_strip(buf, sizeof(buf), val); 74 | 75 | return (set_touch_act_setting(g_ctx, stripped_val) < 0) 76 | ? -EINVAL 77 | : param_set_charp(stripped_val, kp); 78 | } 79 | 80 | static const struct kernel_param_ops touch_act_setting_param_ops = { 81 | .set = touch_act_setting_param_set, 82 | .get = param_get_charp, 83 | }; 84 | module_param_cb(touch_act, &touch_act_setting_param_ops, &touch_act_setting, 0664); 85 | MODULE_PARM_DESC(touch_act_setting, "Touchpad enabled after clicking (\"click\") or always enabled (\"always\")"); 86 | 87 | // Update touch shift enable in global context 88 | static int set_touch_shift_setting(struct kbd_ctx *ctx, char const* val) 89 | { 90 | // If no state was passed, exit 91 | if (!ctx) { 92 | return 0; 93 | } 94 | 95 | ctx->touch.enable_while_shift_held = val[0] != '0'; 96 | return 0; 97 | } 98 | 99 | // Hold shift to temporarily enable touch 100 | static int touch_shift_setting_param_set(const char *val, const struct kernel_param *kp) 101 | { 102 | char *stripped_val; 103 | char stripped_val_buf[2]; 104 | 105 | // Copy provided value to buffer and strip it of newlines 106 | strncpy(stripped_val_buf, val, 2); 107 | stripped_val_buf[1] = '\0'; 108 | stripped_val = strstrip(stripped_val_buf); 109 | 110 | return (set_touch_shift_setting(g_ctx, stripped_val) < 0) 111 | ? -EINVAL 112 | : param_set_charp(stripped_val, kp); 113 | } 114 | 115 | static const struct kernel_param_ops touch_shift_setting_param_ops = { 116 | .set = touch_shift_setting_param_set, 117 | .get = param_get_charp, 118 | }; 119 | 120 | module_param_cb(touch_shift, &touch_shift_setting_param_ops, &touch_shift_setting, 0664); 121 | MODULE_PARM_DESC(touch_shift_setting, "Set to 1 to enable touch while Shift key is held"); 122 | 123 | // Update touchpad mode setting in global context, if available 124 | static int set_touch_as_setting(struct kbd_ctx* ctx, char const* val) 125 | { 126 | // If no state was passed, just update the local setting without 127 | // changing I2C touch interrupts 128 | if (!ctx) { 129 | return 0; 130 | } 131 | 132 | // Touchpad sends arrow keys 133 | if (strcmp(val, "keys") == 0) { 134 | input_touch_set_input_as(ctx, TOUCH_INPUT_AS_KEYS); 135 | return 0; 136 | 137 | // Touchpad sends mouse 138 | } else if (strcmp(val, "mouse") == 0) { 139 | input_touch_set_input_as(ctx, TOUCH_INPUT_AS_MOUSE); 140 | return 0; 141 | } 142 | 143 | // Invalid parameter value 144 | return -1; 145 | } 146 | 147 | // Touchpad sends arrow keys or mouse 148 | static int touch_as_setting_param_set(const char *val, const struct kernel_param* kp) 149 | { 150 | char buf[8]; 151 | char *stripped_val; 152 | 153 | stripped_val = copy_and_strip(buf, sizeof(buf), val); 154 | 155 | return (set_touch_as_setting(g_ctx, stripped_val) < 0) 156 | ? -EINVAL 157 | : param_set_charp(stripped_val, kp); 158 | } 159 | 160 | static const struct kernel_param_ops touch_as_setting_param_ops = { 161 | .set = touch_as_setting_param_set, 162 | .get = param_get_charp, 163 | }; 164 | module_param_cb(touch_as, &touch_as_setting_param_ops, &touch_as_setting, 0664); 165 | MODULE_PARM_DESC(touch_as_setting, "Touchpad sends arrow keys (\"keys\") or mouse (\"mouse\")"); 166 | 167 | // Set touchpad minimum surface quality level 168 | static int set_touch_min_squal_setting(struct kbd_ctx *ctx, char const* val) 169 | { 170 | int parsed_val; 171 | 172 | // If no state was passed, exit 173 | if (!ctx) { 174 | return 0; 175 | } 176 | 177 | // Parse setting 178 | if ((parsed_val = parse_u8(val)) < 0) { 179 | return parsed_val; 180 | } 181 | 182 | // Write setting to firmware 183 | (void)kbd_write_i2c_u8(ctx->i2c_client, REG_TOUCHPAD_MIN_SQUAL, (uint8_t)parsed_val); 184 | 185 | return 0; 186 | } 187 | 188 | // Touchpad minimum surface quality level 189 | static int touch_min_squal_param_set(const char *val, const struct kernel_param *kp) 190 | { 191 | char *stripped_val; 192 | char stripped_val_buf[4]; 193 | 194 | // Copy provided value to buffer and strip it of newlines 195 | strncpy(stripped_val_buf, val, 4); 196 | stripped_val_buf[3] = '\0'; 197 | stripped_val = strstrip(stripped_val_buf); 198 | 199 | return (set_touch_min_squal_setting(g_ctx, stripped_val) < 0) 200 | ? -EINVAL 201 | : param_set_charp(stripped_val, kp); 202 | } 203 | 204 | static const struct kernel_param_ops touch_min_squal_param_ops = { 205 | .set = touch_min_squal_param_set, 206 | .get = param_get_charp, 207 | }; 208 | 209 | module_param_cb(touch_min_squal, &touch_min_squal_param_ops, &touch_min_squal_setting, 0664); 210 | MODULE_PARM_DESC(touch_min_squal_setting, "Minimum surface quality to accept touch event"); 211 | 212 | // Set touchpad move threshold 213 | static int set_touch_threshold_setting(struct kbd_ctx *ctx, char const* val) 214 | { 215 | int parsed_val; 216 | 217 | // If no state was passed, exit 218 | if (!ctx) { 219 | return 0; 220 | } 221 | 222 | // Parse setting 223 | if ((parsed_val = parse_u8(val)) < 0) { 224 | return parsed_val; 225 | } 226 | 227 | // Check setting 228 | if ((parsed_val < 4) || (parsed_val > 255)) { 229 | return -1; 230 | } 231 | 232 | // Store setting 233 | input_touch_set_threshold(ctx, (uint8_t)parsed_val); 234 | 235 | return 0; 236 | } 237 | 238 | // Touchpad move threshold 239 | static int touch_threshold_param_set(const char *val, const struct kernel_param *kp) 240 | { 241 | char *stripped_val; 242 | char stripped_val_buf[4]; 243 | 244 | // Copy provided value to buffer and strip it of newlines 245 | strncpy(stripped_val_buf, val, 4); 246 | stripped_val_buf[3] = '\0'; 247 | stripped_val = strstrip(stripped_val_buf); 248 | 249 | return (set_touch_threshold_setting(g_ctx, stripped_val) < 0) 250 | ? -EINVAL 251 | : param_set_uint(stripped_val, kp); 252 | } 253 | 254 | static const struct kernel_param_ops touch_threshold_param_ops = { 255 | .set = touch_threshold_param_set, 256 | .get = param_get_uint, 257 | }; 258 | 259 | module_param_cb(touch_threshold, &touch_threshold_param_ops, &touch_threshold_setting, 0664); 260 | MODULE_PARM_DESC(touch_threshold_setting, "Send touch event above this threshold (minimum 4, default 8)"); 261 | 262 | // Set touchpad LED power level 263 | static int set_touch_led_setting(struct kbd_ctx* ctx, char const* val) 264 | { 265 | if (strcmp(val, "low") == 0) { 266 | (void)kbd_write_i2c_u8(ctx->i2c_client, REG_TOUCHPAD_LED, TOUCHPAD_LED_LOW); 267 | return 0; 268 | 269 | } else if (strcmp(val, "med") == 0) { 270 | (void)kbd_write_i2c_u8(ctx->i2c_client, REG_TOUCHPAD_LED, TOUCHPAD_LED_MED); 271 | return 0; 272 | 273 | } else if (strcmp(val, "high") == 0) { 274 | (void)kbd_write_i2c_u8(ctx->i2c_client, REG_TOUCHPAD_LED, TOUCHPAD_LED_HIGH); 275 | return 0; 276 | } 277 | 278 | // Invalid parameter value 279 | return -1; 280 | } 281 | 282 | // Touchpad LED level 283 | static int touch_led_setting_param_set(const char *val, const struct kernel_param* kp) 284 | { 285 | char buf[8]; 286 | char *stripped_val; 287 | 288 | stripped_val = copy_and_strip(buf, sizeof(buf), val); 289 | 290 | return (set_touch_led_setting(g_ctx, stripped_val) < 0) 291 | ? -EINVAL 292 | : param_set_charp(stripped_val, kp); 293 | } 294 | 295 | static const struct kernel_param_ops touch_led_setting_param_ops = { 296 | .set = touch_led_setting_param_set, 297 | .get = param_get_charp, 298 | }; 299 | module_param_cb(touch_led, &touch_led_setting_param_ops, &touch_led_setting, 0664); 300 | MODULE_PARM_DESC(touch_led_setting, "Touchpad LED power level (\"low\", \"med\", \"high\")"); 301 | 302 | // Update poweroff setting in global context, if available 303 | static int set_handle_poweroff_setting(struct kbd_ctx *ctx, char const* val) 304 | { 305 | // If no state was passed, exit 306 | if (!ctx) { 307 | return 0; 308 | } 309 | 310 | input_fw_set_handle_poweroff(ctx, val[0] != '0'); 311 | return 0; 312 | } 313 | 314 | // Whether or not to handle poweroff in driver (for minimal system without ACPI) 315 | static int handle_poweroff_setting_param_set(const char *val, const struct kernel_param *kp) 316 | { 317 | char *stripped_val; 318 | char stripped_val_buf[2]; 319 | 320 | // Copy provided value to buffer and strip it of newlines 321 | strncpy(stripped_val_buf, val, 2); 322 | stripped_val_buf[1] = '\0'; 323 | stripped_val = strstrip(stripped_val_buf); 324 | 325 | return (set_handle_poweroff_setting(g_ctx, stripped_val) < 0) 326 | ? -EINVAL 327 | : param_set_charp(stripped_val, kp); 328 | } 329 | 330 | static const struct kernel_param_ops handle_poweroff_setting_param_ops = { 331 | .set = handle_poweroff_setting_param_set, 332 | .get = param_get_charp, 333 | }; 334 | 335 | module_param_cb(handle_poweroff, &handle_poweroff_setting_param_ops, &handle_poweroff_setting, 0664); 336 | MODULE_PARM_DESC(handle_poweroff_setting, "Set to 1 to invoke /sbin/poweroff when power key is held"); 337 | 338 | // Update shutdown grace time in firmware 339 | static int set_shutdown_grace_setting(struct kbd_ctx *ctx, char const* val) 340 | { 341 | int parsed_val; 342 | 343 | // If no state was passed, exit 344 | if (!ctx) { 345 | return 0; 346 | } 347 | 348 | // Parse setting 349 | if ((parsed_val = parse_u8(val)) < 0) { 350 | return parsed_val; 351 | } 352 | 353 | // Check setting (minimum 5 seconds) 354 | if ((parsed_val < 5) || (parsed_val > 255)) { 355 | return -EINVAL; 356 | } 357 | 358 | // Write setting to firmware 359 | (void)kbd_write_i2c_u8(ctx->i2c_client, REG_SHUTDOWN_GRACE, (uint8_t)parsed_val); 360 | 361 | return 0; 362 | } 363 | 364 | // Time between shutdown signal and power off in seconds 365 | static int shutdown_grace_param_set(const char *val, const struct kernel_param *kp) 366 | { 367 | char *stripped_val; 368 | char stripped_val_buf[4]; 369 | 370 | // Copy provided value to buffer and strip it of newlines 371 | strncpy(stripped_val_buf, val, 4); 372 | stripped_val_buf[3] = '\0'; 373 | stripped_val = strstrip(stripped_val_buf); 374 | 375 | return (set_shutdown_grace_setting(g_ctx, stripped_val) < 0) 376 | ? -EINVAL 377 | : param_set_charp(stripped_val, kp); 378 | } 379 | 380 | static const struct kernel_param_ops shutdown_grace_param_ops = { 381 | .set = shutdown_grace_param_set, 382 | .get = param_get_charp, 383 | }; 384 | 385 | module_param_cb(shutdown_grace, &shutdown_grace_param_ops, &shutdown_grace_setting, 0664); 386 | MODULE_PARM_DESC(shutdown_grace_setting, "Set delay in seconds from shutdown signal to poweroff"); 387 | 388 | // Path to Sharp DRM device 389 | static int sharp_path_param_set(const char *val, const struct kernel_param *kp) 390 | { 391 | char *stripped_val; 392 | char stripped_val_buf[64]; 393 | 394 | // Copy provided value to buffer and strip it of newlines 395 | strncpy(stripped_val_buf, val, sizeof(stripped_val_buf)); 396 | stripped_val_buf[sizeof(stripped_val_buf) - 1] = '\0'; 397 | stripped_val = strstrip(stripped_val_buf); 398 | 399 | return (input_display_valid_sharp_path(stripped_val)) 400 | ? param_set_charp(stripped_val, kp) 401 | : -EINVAL; 402 | } 403 | 404 | static const struct kernel_param_ops sharp_path_param_ops = { 405 | .set = param_set_charp, 406 | .get = param_get_charp, 407 | }; 408 | 409 | module_param_cb(sharp_path, &sharp_path_param_ops, &sharp_path_setting, 0664); 410 | MODULE_PARM_DESC(sharp_path_setting, "Set path to Sharp display DRM device"); 411 | 412 | // Time between shutdown signal and power off in seconds 413 | static int sysfs_gid_param_set(const char *val, const struct kernel_param *kp) 414 | { 415 | char *stripped_val; 416 | char stripped_val_buf[11]; 417 | 418 | // Copy provided value to buffer and strip it of newlines 419 | strncpy(stripped_val_buf, val, 11); 420 | stripped_val_buf[10] = '\0'; 421 | stripped_val = strstrip(stripped_val_buf); 422 | 423 | return param_set_uint(stripped_val, kp); 424 | } 425 | 426 | static const struct kernel_param_ops sysfs_gid_param_ops = { 427 | .set = sysfs_gid_param_set, 428 | .get = param_get_uint, 429 | }; 430 | 431 | module_param_cb(sysfs_gid, &sysfs_gid_param_ops, &sysfs_gid_setting, 0664); 432 | MODULE_PARM_DESC(sysfs_gid_setting, "Set group ID for entries in /sys/firmware/beepy"); 433 | 434 | // Trigger shutdown on driver unload 435 | static int set_auto_off_setting(struct kbd_ctx *ctx, char const* val) 436 | { 437 | // If no state was passed, exit 438 | if (!ctx) { 439 | return 0; 440 | } 441 | 442 | input_fw_set_auto_off(ctx, val[0] != '0'); 443 | return 0; 444 | } 445 | 446 | static int auto_off_setting_param_set(const char *val, const struct kernel_param *kp) 447 | { 448 | char *stripped_val; 449 | char stripped_val_buf[2]; 450 | 451 | // Copy provided value to buffer and strip it of newlines 452 | strncpy(stripped_val_buf, val, 2); 453 | stripped_val_buf[1] = '\0'; 454 | stripped_val = strstrip(stripped_val_buf); 455 | 456 | return (set_auto_off_setting(g_ctx, stripped_val) < 0) 457 | ? -EINVAL 458 | : param_set_charp(stripped_val, kp); 459 | } 460 | 461 | static const struct kernel_param_ops auto_off_setting_param_ops = { 462 | .set = auto_off_setting_param_set, 463 | .get = param_get_charp, 464 | }; 465 | 466 | module_param_cb(auto_off, &auto_off_setting_param_ops, &auto_off_setting, 0664); 467 | MODULE_PARM_DESC(auto_off_setting, "Automatically shut down and enter deep sleep when driver is unloaded"); 468 | 469 | // No setup 470 | int params_probe(void) 471 | { 472 | int rc; 473 | 474 | // Set initial touchpad settings based on module's parameter 475 | if ((rc = set_touch_act_setting(g_ctx, touch_act_setting)) < 0) { 476 | return rc; 477 | } 478 | if ((rc = set_touch_as_setting(g_ctx, touch_as_setting)) < 0) { 479 | return rc; 480 | } 481 | if ((rc = set_touch_min_squal_setting(g_ctx, touch_min_squal_setting)) < 0) { 482 | return rc; 483 | } 484 | if ((rc = set_handle_poweroff_setting(g_ctx, handle_poweroff_setting)) < 0) { 485 | return rc; 486 | } 487 | if ((rc = set_auto_off_setting(g_ctx, auto_off_setting)) < 0) { 488 | return rc; 489 | } 490 | 491 | return 0; 492 | } 493 | 494 | // No cleanup 495 | void params_shutdown(void) 496 | { 497 | return; 498 | } 499 | 500 | char const* params_get_sharp_path(void) 501 | { 502 | return sharp_path_setting; 503 | } 504 | 505 | uint32_t params_get_sysfs_gid(void) 506 | { 507 | return sysfs_gid_setting; 508 | } 509 | -------------------------------------------------------------------------------- /src/params_iface.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef PARAMS_IFACE_H_ 3 | #define PARAMS_IFACE_H_ 4 | 5 | // SPDX-License-Identifier: GPL-2.0-only 6 | /* 7 | * Keyboard Driver for Blackberry Keyboards BBQ10 from arturo182. Software written by wallComputer. 8 | */ 9 | 10 | #include "input_iface.h" 11 | 12 | int params_probe(void); 13 | void params_shutdown(void); 14 | 15 | char const* params_get_sharp_path(void); 16 | uint32_t params_get_sysfs_gid(void); 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /src/registers.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 */ 2 | /* 3 | * Keyboard Driver for Blackberry Keyboards BBQ10 from arturo182. Software written by wallComputer. 4 | * bbq10kbd_registers.h: Registers in BBQ10 Keyboard Software. 5 | */ 6 | 7 | #ifndef BBQX0KBD_REGISTERS_H_ 8 | #define BBQX0KBD_REGISTERS_H_ 9 | 10 | #include "config.h" 11 | 12 | 13 | #define BBQX0KBD_I2C_ADDRESS BBQX0KBD_ASSIGNED_I2C_ADDRESS 14 | 15 | #define BBQX0KBD_WRITE_MASK 0x80 16 | #define BBQX0KBD_FIFO_SIZE 31 17 | 18 | #define REG_VER 0x01 19 | #if (BBQX0KBD_TYPE == BBQ10KBD_FEATHERWING) 20 | #define BBQX0KBD_I2C_SW_VERSION 0x04 // Unused for now since the version numbering has reset. 21 | #endif 22 | #if (BBQX0KBD_TYPE == BBQ20KBD_PMOD) 23 | #define BBQX0KBD_I2C_SW_VERSION 0x10 24 | #endif 25 | #define REG_CFG 0x02 26 | #define REG_CFG_USE_MODS BIT(7) 27 | #define REG_CFG_REPORT_MODS BIT(6) 28 | #define REG_CFG_PANIC_INT BIT(5) 29 | #define REG_CFG_KEY_INT BIT(4) 30 | #define REG_CFG_NUMLOCK_INT BIT(3) 31 | #define REG_CFG_CAPSLOCK_INT BIT(2) 32 | #define REG_CFG_OVERFLOW_INT BIT(1) 33 | #define REG_CFG_OVERFLOW_ON BIT(0) 34 | #define REG_CFG_DEFAULT_SETTING (REG_CFG_REPORT_MODS | REG_CFG_KEY_INT | REG_CFG_OVERFLOW_ON | REG_CFG_OVERFLOW_INT) 35 | // Configurations Used By Arturo182's Code. 36 | // #define REG_CFG_DEFAULT_SETTING (REG_CFG_OVERFLOW_ON | REG_CFG_OVERFLOW_INT | REG_CFG_CAPSLOCK_INT | REG_CFG_NUMLOCK_INT | REG_CFG_KEY_INT | REG_CFG_REPORT_MODS ) 37 | 38 | #define REG_INT 0x03 39 | #if (BBQX0KBD_TYPE == BBQ20KBD_PMOD) 40 | #define REG_INT_TOUCH BIT(6) 41 | #endif 42 | #define REG_INT_GPIO BIT(5) 43 | #define REG_INT_PANIC BIT(4) 44 | #define REG_INT_KEY BIT(3) 45 | #define REG_INT_NUMLOCK BIT(2) 46 | #define REG_INT_CAPSLOCK BIT(1) 47 | #define REG_INT_OVERFLOW BIT(0) 48 | #define REG_INT_RESET_VALUE 0x00 49 | 50 | #define REG_KEY 0x04 51 | #define REG_KEY_NUMLOCK BIT(6) 52 | #define REG_KEY_CAPSLOCK BIT(5) 53 | #define REG_KEY_KEYCOUNT_MASK 0x1F 54 | 55 | #define REG_BKL 0x05 56 | 57 | #define REG_DEB 0x06 58 | 59 | #define REG_FRQ 0x07 60 | 61 | #define REG_RST 0x08 62 | #define REG_FIF 0x09 63 | #define KEY_IDLE_STATE 0 64 | #define KEY_PRESSED_STATE 1 65 | #define KEY_PRESSED_AND_HELD_STATE 2 66 | #define KEY_RELEASED_STATE 3 67 | 68 | #define REG_BK2 0x0A 69 | 70 | #define REG_DIR 0x0B 71 | #define REG_DIR_INPUT 1 72 | #define REG_DIR_OUTPUT 0 73 | 74 | #define REG_PUE 0x0C 75 | #define REG_PUE_ENABLE 1 76 | #define REG_PUE_DISABLE 0 77 | 78 | 79 | #define REG_PUD 0x0D 80 | #define REG_PUD_PULL_UP 1 81 | #define REG_PUD_PULL_DOWN 0 82 | 83 | 84 | #define REG_GIO 0x0E 85 | 86 | #define REG_GIC 0x0F 87 | #define REG_GIC_INTERRUPT_TRIGGER 1 88 | #define REG_GIC_NO_INTERRUPT_TRIGGER 0 89 | 90 | #define REG_GIN 0x10 91 | #define REG_GIN_RESET_VALUE 0x00 92 | 93 | #define BBQ10_BRIGHTNESS_DELTA 64 94 | 95 | #if (BBQX0KBD_TYPE == BBQ20KBD_PMOD) 96 | #define REG_CF2 0x14 97 | #define REG_CF2_AUTO_OFF BIT(3) 98 | #define REG_CF2_USB_MOUSE_ON BIT(2) 99 | #define REG_CF2_USB_KEYB_ON BIT(1) 100 | #define REG_CF2_TOUCH_INT BIT(0) 101 | #ifdef DEBUG 102 | #define REG_CFG2_DEFAULT_SETTING (REG_CF2_TOUCH_INT) 103 | #else 104 | #define REG_CFG2_DEFAULT_SETTING (REG_CF2_TOUCH_INT | REG_CF2_AUTO_OFF) 105 | #endif 106 | 107 | #define REG_TOX 0x15 108 | #define REG_TOY 0x16 109 | 110 | #define REG_ADC 0x17 111 | #define REG_LED 0x20 112 | #define REG_LED_R 0x21 113 | #define REG_LED_G 0x22 114 | #define REG_LED_B 0x23 115 | 116 | #define REG_REWAKE_MINS 0x24 117 | #define REG_SHUTDOWN_GRACE 0x25 118 | 119 | #define REG_RTC_SEC 0x26 120 | #define REG_RTC_MIN 0x27 121 | #define REG_RTC_HOUR 0x28 122 | #define REG_RTC_MDAY 0x29 123 | #define REG_RTC_MON 0x2A 124 | #define REG_RTC_YEAR 0x2B 125 | #define REG_RTC_COMMIT 0x2C 126 | 127 | #define REG_DRIVER_STATE 0x2D 128 | 129 | #define REG_STARTUP_REASON 0x2E 130 | #define STARTUP_REASON_FW_INIT 0x1 131 | #define STARTUP_REASON_BUTTON 0x2 132 | #define STARTUP_REASON_REWAKE 0x3 133 | #define STARTUP_REASON_REWAKE_CANCELED 0x4 134 | 135 | #define REG_UPDATE_DATA 0x30 136 | #define UPDATE_OFF 0 137 | #define UPDATE_RECV 1 138 | #define UPDATE_FAILED 2 139 | #define UPDATE_FAILED_LINE_OVERFLOW 3 140 | #define UPDATE_FAILED_FLASH_EMPTY 4 141 | #define UPDATE_FAILED_FLASH_OVERFLOW 5 142 | #define UPDATE_FAILED_BAD_LINE 6 143 | #define UPDATE_FAILED_BAD_CHECKSUM 7 144 | 145 | #define REG_TOUCHPAD_REG 0x40 146 | #define REG_TOUCHPAD_VAL 0x41 147 | 148 | #define REG_TOUCHPAD_MIN_SQUAL 0x42 149 | #define REG_TOUCHPAD_LED 0x43 150 | #define TOUCHPAD_LED_MED 0x0 151 | #define TOUCHPAD_LED_HIGH 0x3 152 | #define TOUCHPAD_LED_LOW 0x5 153 | 154 | #define REG_TOUCHPAD_REG_ENGINE 0x60 155 | #define REG_TOUCHPAD_ENGINE_XY_SCALE (1 << 1) 156 | #define REG_TOUCHPAD_REG_SPEED 0x63 157 | #define REG_TOUCHPAD_SPEED_X_SCALE2 (1 << 6) 158 | #define REG_TOUCHPAD_SPEED_Y_SCALE2 (1 << 7) 159 | 160 | #endif 161 | 162 | 163 | #endif 164 | -------------------------------------------------------------------------------- /src/sysfs_iface.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | /* 3 | * Kernel driver for Q20 keyboard by ardangelo 4 | * References work by arturo182, wallComputer 5 | * sysfs.c: /sys/firmware/beepy interface 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "config.h" 16 | 17 | #include "i2c_helper.h" 18 | #include "input_iface.h" 19 | #include "params_iface.h" 20 | #include "sysfs_iface.h" 21 | 22 | // Read battery level over I2C 23 | static int read_raw_battery_level(void) 24 | { 25 | int rc; 26 | uint8_t battery_level[2]; 27 | 28 | // Make sure I2C client was initialized 29 | if ((g_ctx == NULL) || (g_ctx->i2c_client == NULL)) { 30 | return -EINVAL; 31 | } 32 | 33 | // Read battery level 34 | if ((rc = kbd_read_i2c_2u8(g_ctx->i2c_client, REG_ADC, battery_level)) < 0) { 35 | return rc; 36 | } 37 | 38 | // Calculate raw battery level 39 | return (battery_level[1] << 8) | battery_level[0]; 40 | } 41 | 42 | static int parse_and_write_i2c_u8(char const* buf, size_t count, uint8_t reg) 43 | { 44 | int parsed; 45 | 46 | // Parse string entry 47 | if ((parsed = parse_u8(buf)) < 0) { 48 | return -EINVAL; 49 | } 50 | 51 | // Write value to LED register if available 52 | if (g_ctx && g_ctx->i2c_client) { 53 | kbd_write_i2c_u8(g_ctx->i2c_client, reg, (uint8_t)parsed); 54 | } 55 | 56 | return count; 57 | } 58 | 59 | // Sysfs entries 60 | 61 | // Raw battery level 62 | static ssize_t battery_raw_show(struct kobject *kobj, struct kobj_attribute *attr, 63 | char *buf) 64 | { 65 | int total_level; 66 | 67 | // Read raw level 68 | if ((total_level = read_raw_battery_level()) < 0) { 69 | return total_level; 70 | } 71 | 72 | // Format into buffer 73 | return sprintf(buf, "%d\n", total_level); 74 | } 75 | struct kobj_attribute battery_raw_attr 76 | = __ATTR(battery_raw, 0444, battery_raw_show, NULL); 77 | 78 | // Battery volts level 79 | static ssize_t battery_volts_show(struct kobject *kobj, struct kobj_attribute *attr, 80 | char *buf) 81 | { 82 | int volts_fp; 83 | 84 | // Read raw level 85 | if ((volts_fp = read_raw_battery_level()) < 0) { 86 | return volts_fp; 87 | } 88 | 89 | // Calculate voltage in fixed point 90 | volts_fp *= 330 * 21; 91 | volts_fp /= 4095; 92 | 93 | // Format into buffer 94 | return sprintf(buf, "%d.%03d\n", volts_fp / 1000, volts_fp % 1000); 95 | } 96 | struct kobj_attribute battery_volts_attr 97 | = __ATTR(battery_volts, 0444, battery_volts_show, NULL); 98 | 99 | // Battery percent approximate 100 | static ssize_t battery_percent_show(struct kobject *kobj, struct kobj_attribute *attr, 101 | char *buf) 102 | { 103 | int percent; 104 | 105 | // Read raw level 106 | if ((percent = read_raw_battery_level()) < 0) { 107 | return percent; 108 | } 109 | 110 | // Calculate voltage in fixed point 111 | percent *= 330 * 21; 112 | percent /= 4095; 113 | 114 | // Range from 3.2V min to 4.2V max 115 | // `percent` currently contains the fp voltage value (v * 1000; e.g. in the range from 3200 to 4200) 116 | // To convert to a percentage in the range from 0 - 1 we subtract the value at 0% (3200) 117 | // and divide by the difference between lowest value and higest value (4200 - 3200). Then 118 | // multiply by 100 to get 0 - 100. This can all be simplified to: 119 | percent -= 3200; 120 | percent /= 10; 121 | 122 | // If the voltage goes above 4.2v the percentage will go above 100. This can happen while plugged in. 123 | if (percent > 100) { 124 | percent = 100; 125 | // If the voltage drops below 3.2v the percentage will go below 0, so cap at 0. 126 | } else if (percent < 0) { 127 | percent = 0; 128 | } 129 | 130 | // Format into buffer 131 | return sprintf(buf, "%d\n", percent); 132 | } 133 | struct kobj_attribute battery_percent_attr 134 | = __ATTR(battery_percent, 0444, battery_percent_show, NULL); 135 | 136 | // LED on or off 137 | static ssize_t led_store(struct kobject *kobj, struct kobj_attribute *attr, 138 | char const *buf, size_t count) 139 | { 140 | return parse_and_write_i2c_u8(buf, count, REG_LED); 141 | } 142 | struct kobj_attribute led_attr = __ATTR(led, 0220, NULL, led_store); 143 | 144 | // LED red value 145 | static ssize_t led_red_store(struct kobject *kobj, struct kobj_attribute *attr, 146 | char const *buf, size_t count) 147 | { 148 | return parse_and_write_i2c_u8(buf, count, REG_LED_R); 149 | } 150 | struct kobj_attribute led_red_attr = __ATTR(led_red, 0220, NULL, led_red_store); 151 | 152 | // LED green value 153 | static ssize_t led_green_store(struct kobject *kobj, struct kobj_attribute *attr, 154 | char const *buf, size_t count) 155 | { 156 | return parse_and_write_i2c_u8(buf, count, REG_LED_G); 157 | } 158 | struct kobj_attribute led_green_attr = __ATTR(led_green, 0220, NULL, led_green_store); 159 | 160 | // LED blue value 161 | static ssize_t __used led_blue_store(struct kobject *kobj, struct kobj_attribute *attr, 162 | char const *buf, size_t count) 163 | { 164 | return parse_and_write_i2c_u8(buf, count, REG_LED_B); 165 | } 166 | struct kobj_attribute led_blue_attr = __ATTR(led_blue, 0220, NULL, led_blue_store); 167 | 168 | // Keyboard backlight value 169 | static ssize_t __used keyboard_backlight_store(struct kobject *kobj, 170 | struct kobj_attribute *attr, char const *buf, size_t count) 171 | { 172 | return parse_and_write_i2c_u8(buf, count, REG_BKL); 173 | } 174 | struct kobj_attribute keyboard_backlight_attr 175 | = __ATTR(keyboard_backlight, 0220, NULL, keyboard_backlight_store); 176 | 177 | // Shutdown and rewake timer in minutes 178 | static ssize_t __used rewake_timer_store(struct kobject *kobj, 179 | struct kobj_attribute *attr, char const *buf, size_t count) 180 | { 181 | return parse_and_write_i2c_u8(buf, count, REG_REWAKE_MINS); 182 | } 183 | struct kobj_attribute rewake_timer_attr 184 | = __ATTR(rewake_timer, 0220, NULL, rewake_timer_store); 185 | 186 | // Firmware version 187 | static ssize_t fw_version_show(struct kobject *kobj, struct kobj_attribute *attr, 188 | char *buf) 189 | { 190 | int rc; 191 | uint8_t version; 192 | 193 | // Make sure I2C client was initialized 194 | if ((g_ctx == NULL) || (g_ctx->i2c_client == NULL)) { 195 | return -EINVAL; 196 | } 197 | 198 | // Read firmware version 199 | if ((rc = kbd_read_i2c_u8(g_ctx->i2c_client, REG_VER, &version)) < 0) { 200 | return rc; 201 | } 202 | 203 | return sprintf(buf, "%d.%d\n", version >> 4, version & 0xf); 204 | } 205 | struct kobj_attribute fw_version_attr 206 | = __ATTR(fw_version, 0444, fw_version_show, NULL); 207 | 208 | // Why the Pi was powered on 209 | static ssize_t startup_reason_show(struct kobject *kobj, struct kobj_attribute *attr, 210 | char *buf) 211 | { 212 | int rc; 213 | uint8_t reason; 214 | 215 | // Make sure I2C client was initialized 216 | if ((g_ctx == NULL) || (g_ctx->i2c_client == NULL)) { 217 | return -EINVAL; 218 | } 219 | 220 | // Read startup reason 221 | if ((rc = kbd_read_i2c_u8(g_ctx->i2c_client, REG_STARTUP_REASON, &reason)) < 0) { 222 | return rc; 223 | } 224 | 225 | switch (reason) { 226 | 227 | case STARTUP_REASON_FW_INIT: return sprintf(buf, "fw_init\n"); 228 | case STARTUP_REASON_BUTTON: return sprintf(buf, "power_button\n"); 229 | case STARTUP_REASON_REWAKE: return sprintf(buf, "rewake\n"); 230 | case STARTUP_REASON_REWAKE_CANCELED: return sprintf(buf, "rewake_canceled\n"); 231 | } 232 | 233 | return sprintf(buf, "unknown: %d\n", reason); 234 | } 235 | struct kobj_attribute startup_reason_attr 236 | = __ATTR(startup_reason, 0444, startup_reason_show, NULL); 237 | 238 | // Firmware update 239 | static ssize_t __used fw_update_store(struct kobject *kobj, 240 | struct kobj_attribute *attr, char const *buf, size_t count) 241 | { 242 | int rc; 243 | size_t i; 244 | uint8_t update_state; 245 | char const* update_error = NULL; 246 | 247 | // Write value to update 248 | if (g_ctx && g_ctx->i2c_client) { 249 | 250 | // Read update status 251 | if ((rc = kbd_read_i2c_u8(g_ctx->i2c_client, REG_UPDATE_DATA, &update_state)) < 0) { 252 | return rc; 253 | } 254 | 255 | // Start a new update 256 | if ((update_state == UPDATE_OFF) || (update_state >= UPDATE_FAILED)) { 257 | dev_info(&g_ctx->i2c_client->dev, 258 | "fw_update: starting new update, writing %zu bytes\n", count); 259 | 260 | // In-progress update 261 | } else if (update_state == UPDATE_RECV) { 262 | dev_info(&g_ctx->i2c_client->dev, 263 | "fw_update: writing %zu bytes\n", count); 264 | } 265 | 266 | for (i = 0; i < count; i++) { 267 | kbd_write_i2c_u8(g_ctx->i2c_client, REG_UPDATE_DATA, (uint8_t)buf[i]); 268 | } 269 | 270 | // Read update status 271 | if ((rc = kbd_read_i2c_u8(g_ctx->i2c_client, REG_UPDATE_DATA, &update_state)) < 0) { 272 | return rc; 273 | } 274 | 275 | // Successful update 276 | if (update_state == UPDATE_OFF) { 277 | dev_info(&g_ctx->i2c_client->dev, 278 | "fw_update: wrote %zu bytes, update completed\n", count); 279 | 280 | // Update still in-progress 281 | } else if (update_state == UPDATE_RECV) { 282 | dev_info(&g_ctx->i2c_client->dev, 283 | "fw_update: wrote %zu bytes\n", count); 284 | 285 | // Update failed 286 | } else if (update_state >= UPDATE_FAILED) { 287 | 288 | update_error = "update failed"; 289 | 290 | switch (update_state) { 291 | case UPDATE_FAILED_LINE_OVERFLOW: 292 | update_error = "hex line too long"; break; 293 | case UPDATE_FAILED_FLASH_EMPTY: 294 | update_error = "flash image empty"; break; 295 | case UPDATE_FAILED_FLASH_OVERFLOW: 296 | update_error = "flash image > 64k"; break; 297 | case UPDATE_FAILED_BAD_LINE: 298 | update_error = "could not parse hex line"; break; 299 | case UPDATE_FAILED_BAD_CHECKSUM: 300 | update_error = "bad checksum"; break; 301 | } 302 | 303 | dev_info(&g_ctx->i2c_client->dev, 304 | "fw_update: failed: %s\n", update_error); 305 | return -EINVAL; 306 | } 307 | } 308 | 309 | return count; 310 | } 311 | struct kobj_attribute fw_update_attr 312 | = __ATTR(fw_update, 0220, NULL, fw_update_store); 313 | 314 | // Time since last keypress in milliseconds 315 | static ssize_t last_keypress_show(struct kobject *kobj, struct kobj_attribute *attr, 316 | char *buf) 317 | { 318 | uint64_t last_keypress_ms; 319 | 320 | if (g_ctx) { 321 | 322 | // Get time in ns 323 | last_keypress_ms = ktime_get_boottime_ns(); 324 | if (g_ctx->last_keypress_at < last_keypress_ms) { 325 | last_keypress_ms -= g_ctx->last_keypress_at; 326 | 327 | // Calculate time in milliseconds 328 | last_keypress_ms = div_u64(last_keypress_ms, 1000000); 329 | 330 | // Format into buffer 331 | return sprintf(buf, "%lld\n", last_keypress_ms); 332 | } 333 | } 334 | 335 | return sprintf(buf, "-1\n"); 336 | } 337 | struct kobj_attribute last_keypress_attr 338 | = __ATTR(last_keypress, 0444, last_keypress_show, NULL); 339 | 340 | // Sysfs attributes (entries) 341 | struct kobject *beepy_kobj = NULL; 342 | static struct attribute *beepy_attrs[] = { 343 | &battery_raw_attr.attr, 344 | &battery_volts_attr.attr, 345 | &battery_percent_attr.attr, 346 | &led_attr.attr, 347 | &led_red_attr.attr, 348 | &led_green_attr.attr, 349 | &led_blue_attr.attr, 350 | &keyboard_backlight_attr.attr, 351 | &rewake_timer_attr.attr, 352 | &startup_reason_attr.attr, 353 | &fw_version_attr.attr, 354 | &fw_update_attr.attr, 355 | &last_keypress_attr.attr, 356 | NULL, 357 | }; 358 | static struct attribute_group beepy_attr_group = { 359 | .attrs = beepy_attrs 360 | }; 361 | 362 | static void beepy_get_ownership 363 | #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0) 364 | (struct kobject *kobj, kuid_t *uid, kgid_t *gid) 365 | #else 366 | (struct kobject const *kobj, kuid_t *uid, kgid_t *gid) 367 | #endif 368 | { 369 | if (gid != NULL) { 370 | gid->val = params_get_sysfs_gid(); 371 | } 372 | } 373 | 374 | static struct kobj_type beepy_ktype = { 375 | .get_ownership = beepy_get_ownership, 376 | .sysfs_ops = &kobj_sysfs_ops 377 | }; 378 | 379 | int sysfs_probe(struct i2c_client* i2c_client) 380 | { 381 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) 382 | int rc; 383 | #endif 384 | 385 | // Allocate custom sysfs type 386 | if ((beepy_kobj = devm_kzalloc(&i2c_client->dev, sizeof(*beepy_kobj), GFP_KERNEL)) == NULL) { 387 | return -ENOMEM; 388 | } 389 | 390 | // Create sysfs entries for beepy with custom type 391 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) 392 | rc = 393 | #endif 394 | kobject_init_and_add(beepy_kobj, &beepy_ktype, firmware_kobj, "beepy"); 395 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) 396 | if (rc < 0) { 397 | kobject_put(beepy_kobj); 398 | return rc; 399 | } 400 | #endif 401 | 402 | // Create sysfs attributes 403 | if (sysfs_create_group(beepy_kobj, &beepy_attr_group)) { 404 | kobject_put(beepy_kobj); 405 | return -ENOMEM; 406 | } 407 | 408 | return 0; 409 | } 410 | 411 | void sysfs_shutdown(struct i2c_client* i2c_client) 412 | { 413 | // Remove sysfs entry 414 | if (beepy_kobj) { 415 | kobject_put(beepy_kobj); 416 | beepy_kobj = NULL; 417 | } 418 | } 419 | -------------------------------------------------------------------------------- /src/sysfs_iface.h: -------------------------------------------------------------------------------- 1 | #ifndef SYSFS_IFACE_H_ 2 | #define SYSFS_IFACE_H_ 3 | 4 | // SPDX-License-Identifier: GPL-2.0-only 5 | /* 6 | * Keyboard Driver for Blackberry Keyboards BBQ10 from arturo182. Software written by wallComputer. 7 | */ 8 | 9 | int sysfs_probe(struct i2c_client* i2c_client); 10 | void sysfs_shutdown(struct i2c_client* i2c_client); 11 | 12 | #endif 13 | --------------------------------------------------------------------------------