From 6a5fe581b9d9a78ee76582eaca00b051ba25592c Mon Sep 17 00:00:00 2001 From: Ondrej Jirman Date: Sun, 27 Jun 2021 18:45:36 +0200 Subject: [PATCH] Implement flashing over I2C and user/stock firmware support I2C register layout changed a bit to make various operations easier to implement in FW and for the user. Flashing/debugging tools now share more code. Firmware is now more configurable (it's now possible to compile-out various features). Self-testing for column-shorts is implemented. Firmware is optimized for low power consumption. --- .gitignore | 1 + Makefile | 45 + README | 27 +- README.flashing | 92 +- README.i2c-intf | 135 +- TODO | 41 +- builds/fw.bin | Bin 32768 -> 0 bytes charger/build.sh | 3 - charger/main.c | 385 ------ common.c | 387 ++++++ firmware/build.sh | 45 +- firmware/em85f684a.h | 13 +- firmware/main.c | 1522 +++++++++++++++++----- firmware/registers.h | 71 + firmware/stock-ivt.asm | 101 ++ i2c-charger-ctl.c | 478 +++++++ i2c-debugger.c | 60 + i2c-flasher.c | 509 ++++++++ inputd/main.c => i2c-inputd.c | 215 +-- i2c-selftest.c | 114 ++ inputd/build.sh | 4 - inputd/kmap.h | 284 ---- keymaps/factory-keymap-megi.txt | 63 + {inputd => keymaps}/factory-keymap.jpg | Bin {inputd => keymaps}/factory-keymap.txt | 0 {inputd => keymaps}/map-to-c.php | 4 +- {inputd => keymaps}/physical-map.jpg | Bin {inputd => keymaps}/physical-map.txt | 0 usb-flasher/debugger.c => usb-debugger.c | 29 +- usb-flasher/flasher.c => usb-flasher.c | 13 +- usb-flasher/build.sh | 6 - usb-flasher/common.c | 205 --- 32 files changed, 3325 insertions(+), 1527 deletions(-) create mode 100644 .gitignore create mode 100644 Makefile delete mode 100644 builds/fw.bin delete mode 100755 charger/build.sh delete mode 100644 charger/main.c create mode 100644 common.c create mode 100644 firmware/registers.h create mode 100644 firmware/stock-ivt.asm create mode 100644 i2c-charger-ctl.c create mode 100644 i2c-debugger.c create mode 100644 i2c-flasher.c rename inputd/main.c => i2c-inputd.c (65%) create mode 100644 i2c-selftest.c delete mode 100755 inputd/build.sh delete mode 100644 inputd/kmap.h create mode 100644 keymaps/factory-keymap-megi.txt rename {inputd => keymaps}/factory-keymap.jpg (100%) rename {inputd => keymaps}/factory-keymap.txt (100%) rename {inputd => keymaps}/map-to-c.php (96%) rename {inputd => keymaps}/physical-map.jpg (100%) rename {inputd => keymaps}/physical-map.txt (100%) rename usb-flasher/debugger.c => usb-debugger.c (92%) rename usb-flasher/flasher.c => usb-flasher.c (98%) delete mode 100755 usb-flasher/build.sh delete mode 100644 usb-flasher/common.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..714d340 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/firmware/build/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..821d7a3 --- /dev/null +++ b/Makefile @@ -0,0 +1,45 @@ +VERSION := $(shell git describe) $(shell git log -1 --format=%cd --date=iso) + +OUT ?= build/ +CFLAGS ?= -O2 -g0 +CFLAGS += -DVERSION="\"$(VERSION)\"" -I. -I$(OUT) + +all: $(OUT)ppkb-i2c-inputd $(OUT)ppkb-usb-flasher $(OUT)ppkb-usb-debugger $(OUT)fw-stock.bin $(OUT)ppkb-i2c-debugger $(OUT)ppkb-i2c-charger-ctl $(OUT)ppkb-i2c-flasher $(OUT)ppkb-i2c-selftest + +$(OUT)ppkb-usb-flasher: usb-flasher.c common.c + @mkdir -p $(OUT) + $(CC) $(CFLAGS) -o $@ $< + +$(OUT)ppkb-usb-debugger: usb-debugger.c common.c + @mkdir -p $(OUT) + $(CC) $(CFLAGS) -o $@ $< + +$(OUT)kmap.h: keymaps/physical-map.txt keymaps/factory-keymap.txt + @mkdir -p $(OUT) + php keymaps/map-to-c.php $^ > $@ + +$(OUT)ppkb-i2c-inputd: i2c-inputd.c $(OUT)kmap.h common.c + @mkdir -p $(OUT) + $(CC) $(CFLAGS) -o $@ $< + +$(OUT)ppkb-i2c-debugger: i2c-debugger.c common.c + @mkdir -p $(OUT) + $(CC) $(CFLAGS) -o $@ $< + +$(OUT)ppkb-i2c-charger-ctl: i2c-charger-ctl.c common.c + @mkdir -p $(OUT) + $(CC) $(CFLAGS) -o $@ $< + +$(OUT)ppkb-i2c-flasher: i2c-flasher.c common.c + @mkdir -p $(OUT) + $(CC) $(CFLAGS) -o $@ $< + +$(OUT)ppkb-i2c-selftest: i2c-selftest.c common.c + @mkdir -p $(OUT) + $(CC) $(CFLAGS) -o $@ $< + +$(OUT)fw-stock.bin $(OUT)fw-user.bin: firmware/em85f684a.h firmware/main.c firmware/build.sh firmware/bootloader.bin + @mkdir -p $(OUT) + cd firmware && ./build.sh + cp -f firmware/build/fw-stock.bin $(OUT)fw-stock.bin + cp -f firmware/build/fw-user.bin $(OUT)fw-user.bin diff --git a/README b/README index 76ef804..9fdf1f8 100644 --- a/README +++ b/README @@ -1,4 +1,29 @@ -This is work in progress free software firmware for pinephone keyboard. +FOSS firmware for pinephone keyboard +==================================== + +Features: + +- Dual firmware architecture: stock firmware + optional user firmware. +- Stock firmware implements the full functionality of the keyboard. + - Stock firmware is layout independent, it reports the raw status + of the whole keyboard matrix. + - Key maps and combinations can be arbitrarily changed in the keyboard + driver without re-flashing the firmware. + - Stock firmware should be sufficient for most users who will not want + to do HW modifications to their keyboard to add more peripherals to + the keyboard MCU. + - Power efficient implementation using power-down feature of the MCU + to save power as much as possible. (currently: 9 mW when idle, 20mW + in active scanning mode - with at least one key pressed) +- Stock firmware is flashed in factory and allows flashing user firmware + from the pinephone itself over I2C interface. +- User firmware can be used either for updates or for customizations + (SW support for HW modifications of the keyboard) +- USB stack and tools for stock firmware flashing using ELAN's original + bootloader to ease development of the stock firmware. +- Self-testing features to quickly test for issues with the keyboard matrix. +- Fully based on FOSS software, with no dependencies. You only need + sdcc 4.1+ to build the firmware. See demo video https://megous.com/dl/tmp/kb.mp4 and some technical overview https://xnux.eu/log/ diff --git a/README.flashing b/README.flashing index 29ca8ca..4fa9197 100644 --- a/README.flashing +++ b/README.flashing @@ -1,13 +1,19 @@ -Flashing tool from usb-flasher/ directory can be used to flash the -firmware over USB. +Flashing over USB +================= + +This method allows updating the stock firmware on the keyboard. + +Flashing tool ppkb-usb-flasher can be used to flash the firmware over USB. +You'll have to solder USB cable to the keyboard controller board to be able +to user this method. For example to build and flash your own firmware you could run these commands: # save the original firmware in case you want to restore it - ./usb-flasher/ppkb-flasher info > saved-info.txt - ./usb-flasher/ppkb-flasher --rom-out saved-rom.bin read + ./ppkb-usb-flasher info > saved-info.txt + ./ppkb-usb-flasher --rom-out saved-rom.bin read # build the new firmware (you may need sdcc 4.1, older versions may # miscompile the firmware) @@ -16,20 +22,21 @@ commands: # flash the new firmware - ./usb-flasher/ppkb-flasher --rom-in firmware/build/fw.bin write reset + ./ppkb-usb-flasher --rom-in firmware/build/fw-stock.bin write reset + Full help output ---------------- -Usage: ppkb-flasher [--rom-in ] [--rom-out ] [--verbose] - [--help] [...] +Usage: ppkb-usb-flasher [--rom-in ] [--rom-out ] [--verbose] + [--help] [...] Options: -i, --rom-in Specify path to binary file you want to flash. -o, --rom-out Specify path where you want to store the contents of code ROM read from the device. - -s, --rom-size Specify how many bytes of code rom to flash + -s, --size Specify how many bytes of code rom to flash starting from offset 0x2000 in the rom file. -v, --verbose Show details of what's going on. -h, --help This help. @@ -43,7 +50,68 @@ Commands: Format of the ROM files is a flat binary. Only the part of it starting from 0x2000 will be flashed. Use -s to specify how many bytes to write. -Pinephone keyboard flashing tool 1.0 -Written by Ondrej Jirman , 2021 -Licensed under GPLv3, see https://xff.cz/git/pinephone-keyboard/ for -more information. + +Flashing over I2C +================= + +This method users stock stock firmware's flashing interface to allow writing +user's own firmware to the keyboard, while keeping the stock firmware intact. + +Stock firmware will remain present and available for future updates of the +users fiwmare. + +You can use this method without having to disassemble the keyboard, or +solder anything. + +You'll need to build the user firmware in a special way, so that it's placed +at address 0x4000 in code ROM. Interrupt vectors are forwarded with 0x4000 +offset. + + +For example to build and flash your own firmware you could run these +commands: + + # build the new firmware (you may need sdcc 4.1, older versions may + # miscompile the firmware) + + (cd firmware && ./build.sh) + + # flash the new firmware + + ./ppkb-i2c-flasher --rom-in firmware/build/fw-user.bin write reset + +Depending on the user firmware, you may need to change stock firmware entry +method using -e option. + + +Full help output +---------------- + +Usage: ppkb-i2c-flasher [--rom-in ] [--rom-out ] [--verbose] + [--help] [...] + +Options: + -i, --rom-in Specify path to binary file you want to flash. + -o, --rom-out Specify path where you want to store the contents + of code ROM read from the device. + -s, --size Specify how many bytes of code rom to flash + starting from offset 0x4000 in the rom file. + -e, --entry + Specify how to enter the stock firmware: + - manual: Ask the user to power-cycle the keyboard + - i2c: Send I2C command to make supporting user + - none: Assume stock firmware is already running + -v, --verbose Show details of what's going on. + -h, --help This help. + +Commands: + info Display information about the current firmware. + read Read ROM from the device to --rom-out file. + write Flash ROM file to the device from --rom-in. + erase Erase the user firmware. + reset Perform software reset of the MCU. + +Format of the ROM files is a flat binary. Only the part of it starting +from 0x4000 will be flashed. Use -s to specify how many bytes to write. +The stock firmware between 0x2000 and 0x4000 will be preserved. + diff --git a/README.i2c-intf b/README.i2c-intf index 6c2d456..4c4da03 100644 --- a/README.i2c-intf +++ b/README.i2c-intf @@ -20,6 +20,13 @@ Device address is 0x15. Registers --------- +General ranges: + 0x00 - 0x1f - Read-only status range + 0x20 - 0x2f - Writable keyboard control range + 0x70 - 0xf4 - Flashing interface + 0xff - Debug log + + 0x00: Device ID1 (0x4b) 0x01: Device ID2 (0x42) 0x02: Firmware revision @@ -27,8 +34,15 @@ Registers bit 0: USB debugger bit 1: Flashing mode bit 2: Self-test features + bit 3: Stock firmware flag (only stock firmware should have this set) -0x04: System configuration +0x06: bits 3-0: number of rows, bits 7-4 number of cols +0x07: CRC8 of keyboard data from 0x08-0x13 +0x08: Keyboard data for column 1 +... +0x13: Keyboard data for column 12 (up to number of cols, in this case 12) + +0x20: System configuration bit 0: disable KB scanning (1: scanning disabled, 0: scanning enabled) bit 1: poll mode 1: don't rely on row change detection, poll the matrix periodically @@ -39,7 +53,7 @@ Registers 1: enabled 0: disabled -0x06: System command +0x21: System command Writing values into this register causes the firmware to perform certain one-shot actions: @@ -49,23 +63,34 @@ Registers (results for both tests will be stored in test-result registers) -0x10: Keyboard data for column 1 -... -0x1b: Keyboard data for column 12 -0x1c: CRC8 of keyboard data from 0x10-0x0b + The register is set to 0x00 or 0xff after the operation completes. + 0xff means error. -0x20: Writing value 0x53 ('S') to this register stops the main app from +0x22: Writing value 0x53 ('S') to this register stops the main app from jumping to the user app. -0x50: Self-test results (32 bytes) -... -0x6f: - -0x70: Flashing mode unlock key - (writing 0x46 to this register unlocks the flashing mode.) +0x70: 128B block of EEPROM data (either read from code memory or to be +... written) +0xef -0x71: Flashing control +0xf0: target address low byte +0xf1: target address high byte + +0xf2: CRC8 calculated for the 128B block of data from 0x70-0xef + - this must be written by the user when preparing data for write + operation, MCU checks the CRC8 of the data and compares it against + the value in this register before starting the execution of + 0x57 command + - this is updated by the MCU after reading the data from flash memory + so the user can check the checksum against the data read from + 0x70-0xef + +0xf3: Flashing mode unlock key + - writing 0x46 to this register unlocks the flashing mode. + - this register is cleared after command completion + +0xf4: Flashing control Writing various commands to this register makes the MCU execute them, if the MCU is not executing the previous command. Available commands: @@ -79,36 +104,30 @@ Registers This register will ignore further commands as long as the last operation is still in progress. This register will contain the code of the currently executed operation, and will be cleared after the operation - finishes. Completion is also signalled by pulsing the INT pin shortly. + finishes. If the operation fails, this register will contain value 0xff. If it succeeds it will contain value 0x00. - -0x7d: target address low byte -0x7e: target address high byte - -0x7f: CRC8 calculated for the 128B block of data from 0x80-0xff - - this must be written by the user when preparing data for write - operation, MCU checks the CRC8 of the data and compares it against - the value in this register before starting the execution of - 0x57 command - - - this is updated by the MCU after reading the data from flash memory - -0x80: 128B block of EEPROM data (either read from code memory or to be -... written) -0xff +0xff: Debug log + - reading from this register returns next character of the debug log + or 0 + - register address is not auto-advanced for the 0xff address, so you + can read any number of bytes from 0xff to get the debug log text + - debug log stores results of self-tests Usage ----- -User can modify register 0x03 to choose how the firmware should operate. +User can modify register 0x20 to choose how the firmware should operate. The settings are not persistent across resets. To read the keyboard matrix status, the user can perform a 13B read transaction -from address 0x10 and calculate CRC8 on the first 12 bytes and compare it with -the 13th byte. +from address 0x07. Data for each column start at 0x08, 0x07 contains CRC8 checksum +of the 12 bytes starting from 0x08. + +You can verify the data by calculating CRC8 on the last 12 bytes and compare it +with the 1st byte. Bit 0 corresponds to row 1, bit 5 to row 6, bits 6 and 7 are always 0. @@ -122,23 +141,43 @@ The firmware is split into 3 parts: 0x2000 - 0x4000: Stock FOSS firmware (flashable from stock bootloader) 0x4000 - 0x8000: User app (optional, flashable over I2C from stock FOSS firmware) -When the stock FOSS firmware runs after MCU powerup or reset, it will wait for 200ms -and listen on I2C. If 0x53 is not written to register 0x20 during that time and -the user's app is flashed and commited, it will redirect interrupt vectors to -0x4000+offset and jump to 0x4000, which will start executing user's app. +It is necessary to execute stock firmware to flash the user firmware. Stock +firmware will normally re-direct execution to user firmware if user firmware +is flashed. + +After MCU powerup or reset, stock firmware will listen for 1s on I2C interface, +before passing control to the user firmware. If 0x53 is not written to register +0x22 during that time, this will prevent execution of the user's firmware. + +If the user firmware execution is not prevented in this way, stock firmware +will redirect interrupt vectors from 0x0000+offset to 0x4000+offset and jump +to 0x4000, which will start execution of user's firmware. User firmware must +not change last byte of IRAM, because it is used by the stock firmware's +interrupt redirection mechanism. + +User's firmware should always implement some way to reset the MCU via I2C +interface. This will make development and flashing easier. Failing that, +I2C flashing tool also allows for manual entry to stock firmware which +is done by holding the power key for > 12s to force power cycle on the MCU. -User app should always return value 0x00 when reading register 0x20, to make it -easy to distinguish that the "stay in stock app" command succeeded. Flashing steps: -1) Unlock by writing 0x46 to register 0x70 -2) Write address to 0x7d/0x7e -3) Write data to 0x80-0xff and CRC8 to 0x7f -4) Write command 0x57 to 0x71 -5) Poll 0x71 for result (either 0x00 or 0xff) - ... repeat 2-5 for all memory locations to be flashed -6) Write command 0x43 to reg 0x71 -7) Wait for success +1) Write 128 B of data to 0x70-0xef and data CRC8 to 0xf2 +2) Write address to 0xf0 (low) and 0xf1 (high byte) + (only range from 0x4000 to 0x7fff) is writeable +3) Unlock by writing 0x46 to register 0xf3 +4) Write command 0x57 to 0xf4 +5) Poll 0xf4 for result (either 0x00 or 0xff) + ... repeat 1-5 for all memory locations to be flashed +6) Write command 0x43 to reg 0xf4 +7) Poll 0xf4 for result (either 0x00 or 0xff) +8) Reset the MCU by writing command 0x52 to reg 0x21 -Reset the MCU. + +Self-tests +---------- + +1) Write command 'r' or 'c' to reg 0x21 +2) Read repeatedly from register 0xff to get the self-test results + in human readable text form diff --git a/TODO b/TODO index 759e0cc..385c3b1 100644 --- a/TODO +++ b/TODO @@ -1,29 +1,34 @@ Firmware -------- -- mask R5:C4 and R4:C2 because these are always on on my prototype - and prevent development of idle functionality - (Z key is stuck) +- implement I2C configuration options + - disable keyboard matrix scanning - C1 2 3 4 5 6 7 8 9 10 11 12 -R1 . . . . . . . . . . . . -R2 . . . . . . . . . . . . -R3 . . . . . . . . . . . . -R4 . X . . . . . . . . . . -R5 . . . X . . . . . . . . -R6 . . . . . . . . . . . . +- only enable USB in stock FW upon request over I2C (idling USB stack takes + about 0.6mA on top of 1.8mA baseline) -- sleep when no key is pressed and wakeup using pin change interrupt - on P6 port +- first I2C TX after switch to USER firmware after flasging over I2C fails, + subsequent ones work + - also power consumption indicates power consumption of the sotck firmware + so maybe we just sleep in stock instead of switching after flashing? -- wakeup on I2C activity only happens on device address match, so - we can only sleep again after the entire transfer completes (how - to detect that though?) +- power measurement / optimization + - currently with no USB stack and in constant PD mode: 1.8mA + - theoretical lower limit 0.5mA (no idea how they achieve it) -USB Flashing Tool ------------------ -- investigate reason for URB errors/empty (in command status INT transfer) +Charger behavior +---------------- + +- does the charger turn off VOUT after some period of low load? +- does it enable it again to see if there's a need for it? +- this auto-off behavior will kill the power to the keyboard + controller, so we need a way to detect it and power the controller + from the pinephone side by enabling VBUS. + - tricky! + - or make the user keep pressing the kb power button to use the + keyboard for a while, like in a train conductor has to in a train :) + - not good! Userspace input device daemon diff --git a/builds/fw.bin b/builds/fw.bin deleted file mode 100644 index a560bf37ca82ae76692cca1cd1ebd5168e85ad7c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32768 zcmeHN3v?6LnZ7fUY{|TA+42LDW*BU?C7~Jo;B71;L?GB%5}7=BH0st4o9$*R(z!4K zj0Y()uYm0s*lY=e)QuNRFmWKHkbqrLn$zq_;wYOy+z@2YSOF3cG_nC3YyUeV8ygI1 zclVs#o-@dr|G)SC_kVo%_0JXgLnAb!iI9IZGblY3N_FqQpS462hN;gDlwM=^+G*i>V_MWBuxHEzr!_TdQuC>EVM0 z_)GglM4MJrti;Q;N9W(y7`$gmt7oChv%t;Qg-k9!0uT7phAtnbDQo^Z?>>YOUVR#u zBg-WbUwp)v_RhGY<2uKAm$Vvikt~&_TtFd(Eg%Fs99E zxc9Ohh}gUo3BOv3mW(>^dLVyYypdvgy;0)^ozAVxtcGO;hGqGNWsg{zL>5f3r9%W} zDbkjubwP@D_x(B76wp!wA1iv_);s1CIjA*o9V6Wi-toF6Ca_ z6@h-18(KcosM%4P$)^Ef-qf9H=Ml?CSf#VcMLz!UPip)nKRGTx9n)+42kM4MD-O(` zD6KpY%mVoX)L1ShslQ6*O%1d-UkM)AZE$W69w;M4K!Xteb+((o4hH)?u0oO4Z2Kz6 zHud*Yj>SzAImsT%+zv@fd`CAm0RYO&A9eW+jtU_R-RJ5$oS0ZFv)1jEXXiwy?&8wI} zAFuFCJ?F_i2SVb5fofC(f+17`f~PJIRXXvjGF}M35XkX_J^bv@`I--?_$15xqF?cG%xrp^9;{^yWjBtq>ys*rh zbZ}>aFUbd$i}9jYxkLsp`d9Yg)**O-1~1Cv665irWh`D0ix-uV9;8r?q$x$3N`3Cg z2n#FR1yEApf5>v4=4oP8v4p1}f{ZlDPO%LY-WXr3Pk&6x-zQoeOMw15=R z_{n|12ru#C=yqpyFdKFBktFSY=<>XOH0_PFziPmGBc{Q%Z_*9Lcuw8hsN-Xd+`&USjwhPU|TAt1OypDV|B zF~fY^9LUK#o~PRij*#x9B8;UnZoQs7;mLcPc+xYhiYQ+IPT6Fq6o-kJzoyC6&^a9O`U6xyV>3B0(*fwjAQR0t~YP!jo=-84w1ids;|ZxP}x@_gy-)Qo-#i?TOZYJ z?HgOKfoO=!BP#%N2-Dvsdn+U5@6r3uuu3EBsj~qX8j4GNfH7qpMx%ut2M3K5UOl*T zh_p!ib4Rkz1RIVxNG=%ULfyhW3%~#ypFpufRryo+NyNubFMwH7;L!67aF-q{@H}Dm zJe~)NJ&$f}sKArZDZ{^Q3AgsR%7SlyGnrsPR`d|#+(TEcXEEepk`X$rPUSmGkSa2$V&@Hb{w_aogHU4iZw$ecgjGQ&FaA!g*L z^o*Ua{krBiuh;rp&zueDtM!}phxLAg)-b|QX!woc!?>8Z#fEK$mBY3Sq6e)X)H!(V zU}?zwAt#289{T*yreO)gN?6HiooRi@I?FoS`mkL?$A2KKTS^P-mm2hNsx3v{7_Z(J zJD?N=QuOyqAz{6~cFtWi*$TVqgweOTLBcPjh0V2Z@afM8>)OE>@u!cX{%%W!@vY## z&sKOt*vvm!VtvLQLsZB6EQQoS2Bd-_N)Fg(B+XEAknrKiCUHUBv0gnWuoZf70TQa< zFrR{=JJ2*ldi1vsb9x0MS?z{Biv}H_G?ff8riCto?fo5(A}$-}DOkzGu4ndC z&%?RW)Z*QwSTE%km&pvCLs<%_%R7kmX(W{(2}M4_(-BNzJQ+4ONHwZqptQ!c9me5f z`h?nRKn{$jO6JHlQX%6XBo!7tQ2YRt6XNe|8QBWDg;#243)^cS2JNKdcw@rofzFQ_ zNY-GQW3!0!kflItO#H&AyVn?N_iAkgo$Te8jl<)?H3F?MVQMehz^sy;iaJrq`Srra za2^pemKpyPo}t=lc@*5cp1P( zqONx*?p-X(8W~_g^vo0?I{y{Fbe4j6Jmt>wJpQg82`@QeKY__Sha$U^Ljzl)t>Dt# zJsS{LG_ZRymICH(?lFIvd*sE6>hI)fa1xHt+YXcH`sR8S6XtWp2Ri;MzRsw9hz;-& zLg3(81v!c`^T?Lglh9v;>_AScdvY-`vtKUQn_1s^x{CkalLY*A$u-|i-i-pRG+)?s zZ0lTFyqxfoH2>H(;DWG_ipuP?_$R(>KAah}=o3HZsi@o^6JFveS+221ZhwzlomkHb z70!*p(bt7a=ceHJ&hrjx#Bi3U#7FsIVj(|NoXy9HRz6nD=VQcNUMo)Ghln}+U~wEz zi(`3>_#<8?X7GA(BySM!m6k*5Kn_oxu%TVAyvX_6qX|UcrrxX~TB#K_^gY<4wVGRM zH`ZE(O|>(HirR;S%Gz0M(n8axDRWZoreNyFDYH@%likVL$=j0;!gqV}hsplrVDjaZ zSwP{Fx-jMaln+yUAoo7`q>fJ=o4SyElBr~U^04G&BNb1I((l)KSj?Y9?d}|$cg>|{X#AmlT=@7 z@U?{-qz10F6JcK-{xlE&XCD464_?F$SRTD{+an_F=h`|gEoS^S!Uqw42jTaI_1*$l zKDR?h$jCt|*eUI2g5*_}k`8f_hE7XS*j9Q8yvb71Va`ui)hb*o-B7J?XXqtig*!_x zu_;`TUh+KQ4smT7W7>FPpzeb>!BYD>eM_fw7+$ju^S>*x*lDWP2YIVEmW6|AG8fb? zxhR&v`Oeu1w}6~w4#GXU_oE?F{;}m}k@VpWuBf|Mbo_8-M4D)) zUz{L_wG*Ul2Q4N`;~;qxV|ciyqFG)8uN^F}W8qZ-1~3qA2!w5L>E%7s4#xfA!_pyd zYxocwwl(>ktAoBX-atnnTqKBXUCsVaU$GCJJ6N*S+KOTw+MqAc5tSylW@jM0BU~#y zk2C%_h{_L9!56yPi+Fu_UX#C9i_WZR3(u3dO4EZzDiUs#inPQ=vNSI#JkJpXIY*N0 z6c?aGil0tgf(wTl-du9$L2#EhRc#hPZD=9-=`*-g)y943$HMbk3V zDySVL6;&o|t7Zw=OGvu0hq*t>R2qlN?M6-^GZnH>AH6?v zWPZtx5tk)`V=IiG__YuDD8AI`>M->Az>b_*)K#pv)2*|B(^Mur1>-B_q<5KTNZG|B z?PH2Hj>%FEX{jdfbexV)7Dr<)2FQJwGXP1&TpW-DDTndLVon2FsSa~m-cTj5RSQ`u z+dAGl!3xs=Ok|~Z&m}Y`O0yV$ij)KE$4il$CV3&t&bN-0t9_)}*RT4fGVoGSR+;cq z;&m1=(Hp*$4Y4}i9pW;u+GQp$&d~wOs!Vv6$Y(oW=x};EoaG(PH66~i9b%zeCC&sQ zSBq1DQ<^fREWk;0SbKC3M3%@}iYfbpfE8`cvgSaj6CFbUc^@FJ%w1^GIEG4D#k4&L z&-Y*voF=f0hb%I6p^OA!Oqo)qgKoVbO(>?U+180&QYM7HH|v{1S&lT@|4x1q=>Ui5 z85Bp+Gw2&DDH*Dj^f!y_G*8i~g=8d#031Zp=?an>0zs+-kvqX>=L@9H=?RJ}oNI#O zQYAKJfJj`cNn8`{8ax3>6OteR${ts&u}=d!Lwtg0H%VEJM*`u-z8~?&V5QSMp*T{R zunsivns6hCh$*}l7%^S~d>m=gV`TA%8+jTQnzDctoQz2eS-vYH+gxJGma^@-&$81+ zBlH<>&4HX?F7)Aeqc}sUahRb$$3G`#LzqcYPO-u9V`+S`*6~ASiir%wnz|pnNPQ=@ zk&gvgt*D1}&+)8?j7b7Z%)uQ@X1Zk7!OS0a+x`!C+#I{%B3!2IDpZEZr5hX? zf$9e%7qd}!jpQz79GA((TW@opwx2t$C{2;F2+Jfu@ z-oKF}m+sJb^9L1`QxQ-RP!UiO_?tk0X3k|s9le#(Bb{=~?e$L%SbwYB|Cch&?x9DG z$E)*=84pM0z8v^yN$e#@-yOfH-PE?7gK&a+q*B95Z9$^E*admjmJUj+@k(UAsP5!3Xre@rX_Q6k; zNUDd#0+QZ(rlP#LxjDAkpXsCU281{M3~y-iR%C(<7Lo1EZrj)Q;cF2~mz;fE^Ly*P z@E%q9U4+kjI_zS-^>-3v_qyuc`$8*8V!cAQA*2hzoe7=c|18w`-DB7AdgR@RUyR`O z*SU>1KwnYb_37G;u1)TW(0OC}(>0`bzfRYuUmJ8G{p#Ge!Qcwiuj#*~FRJdKt*d(g zXF7YFxd6^U@GnBBUy~f?TDQVju2Jw~I6xZHq-&y@P#k?50UU#DCoaju zgPN&kU2}{)Oa$*Q5N;WUze4zw37=pr`{n;S;oTPq?+Q1B-wN;N1^9hCHe7)>A!!rb zR^W9Bck?vOQPKX?a$I3xRSH-}&%%m*+`kE&88(8j5oax12JCggc0nmv>9cTL_s zWvynNHubj=D(ymnJ;Rgd@|fM8{E#stzN^OL%JaC*$~^j}XC8?kd8WENxo*$25X*EV zaP5g}-Md^&*wDVq4L6sp*nLF|hggqms>hw{2~CqGTcFt;wz`~^rYp1TuT|bSdGV*Q&a?IWP@;6S{G_him zxNq;&`rLif6dys)jWV=n!W$FoCv9-O=-xy$cdkO;9@`dnUD^?LgTqN&dy?XCY@6cn z8L=Z!&H7pR{a;OV|0Yf~)kzfr6#*3i6#*3i6#*3i6#*3i6#*3i6#*3i6#*3i6#*3i z6#*3i6#*3i6#*3i6#*3i6#*3i6#*3i6#*3i6#*3i6#*3i6#*3i6#*3i6#*3i6#*3i z6#*3i6#*3i6#*3i6#*3i6#*3i6#*3i6#*3i6#*3i6#*3i6#*3i6#*3i6#*3i6#*3i s6#*3i6#*3i6#*3i6#*3i6#*3i6#*3i6#*3i6#*3i6#*52|2qW!3+{dzPyhe` diff --git a/charger/build.sh b/charger/build.sh deleted file mode 100755 index be4a5f2..0000000 --- a/charger/build.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -gcc -o kbpower main.c || exit 1 diff --git a/charger/main.c b/charger/main.c deleted file mode 100644 index 454aea8..0000000 --- a/charger/main.c +++ /dev/null @@ -1,385 +0,0 @@ -/* - * Pinephone keyboard power management daemon/tool. - * - * Copyright (C) 2021 Ondřej Jirman - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -// {{{ includes - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -//#include - -#define DEBUG 1 - -#if DEBUG -#define debug(args...) printf(args) -#else -#define debug(args...) -#endif - -// }}} -// {{{ utils - -static void syscall_error(int is_err, const char* fmt, ...) -{ - va_list ap; - - if (!is_err) - return; - - fprintf(stderr, "ERROR: "); - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - fprintf(stderr, ": %s\n", strerror(errno)); - - exit(1); -} - -static void error(const char* fmt, ...) -{ - va_list ap; - - fprintf(stderr, "ERROR: "); - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - fprintf(stderr, "\n"); - - exit(1); -} - -bool read_file(const char* path, char* buf, size_t size) -{ - int fd; - ssize_t ret; - - fd = open(path, O_RDONLY); - if (fd < 0) - return false; - - ret = read(fd, buf, size); - close(fd); - if (ret < 0) - return false; - - if (ret < size) { - buf[ret] = 0; - return true; - } else { - buf[size - 1] = 0; - return false; - } -} - -#define BIT(n) (1u << (n)) - -#define KB_ADDR 0x15 -#define POWER_ADDR 0x75 - -static int pogo_i2c_open(void) -{ - int ret; - char path[256], buf[1024]; - int fd = -1; - - for (int i = 0; i < 8; i++) { - snprintf(path, sizeof path, "/sys/class/i2c-adapter/i2c-%d/uevent", i); - if (!read_file(path, buf, sizeof buf)) - continue; - - if (!strstr(buf, "OF_FULLNAME=/soc/i2c@1c2b400")) - continue; - - snprintf(path, sizeof path, "/dev/i2c-%d", i); - - int fd = open(path, O_RDWR); - syscall_error(fd < 0, "open(%s) failed"); - - //ret = ioctl(fd, I2C_SLAVE, addr); - //syscall_error(ret < 0, "I2C_SLAVE failed"); - - return fd; - } - - error("Can't find POGO I2C adapter"); - return -1; -} - -uint8_t read_power(int fd, uint8_t reg) -{ - int ret; - uint8_t val; - struct i2c_msg msgs[] = { - { POWER_ADDR, 0, 1, ® }, // address - { POWER_ADDR, I2C_M_RD, 1, &val }, - }; - - struct i2c_rdwr_ioctl_data msg = { - .msgs = msgs, - .nmsgs = sizeof(msgs) / sizeof(msgs[0]) - }; - - ret = ioctl(fd, I2C_RDWR, &msg); - syscall_error(ret < 0, "I2C_RDWR failed"); - - return val; -} - -void write_power(int fd, uint8_t reg, uint8_t val) -{ - int ret; - uint8_t buf[] = {reg, val}; - - struct i2c_msg msgs[] = { - { POWER_ADDR, 0, 2, buf }, - }; - -// printf("wr 0x%02hhx: %02hhx\n", reg, val); - - struct i2c_rdwr_ioctl_data msg = { - .msgs = msgs, - .nmsgs = sizeof(msgs) / sizeof(msgs[0]) - }; - - ret = ioctl(fd, I2C_RDWR, &msg); - syscall_error(ret < 0, "I2C_RDWR failed"); -} - -void update_power(int fd, uint8_t reg, uint8_t mask, uint8_t val) -{ - uint8_t tmp; - - tmp = read_power(fd, reg); - tmp &= ~mask; - tmp |= val & mask; - write_power(fd, reg, tmp); -} - -// bits 4-0 are mapped to gpio4 - gpio0 -//#define MFP_CTL0 0x51 -//#define MFP_CTL1 0x52 -//#define GPIO_INEN 0x53 -//#define GPIO_OUTEN 0x54 -//#define GPIO_DATA 0x55 - -#define BATVADC_DAT_L 0xa2 -#define BATVADC_DAT_H 0xa3 -#define BATIADC_DAT_L 0xa4 -#define BATIADC_DAT_H 0xa5 -#define BATOCV_DAT_L 0xa8 -#define BATOCV_DAT_H 0xa9 - -// in mV -unsigned get_bat_voltage(int fd) -{ - unsigned l = read_power(fd, BATVADC_DAT_L); - unsigned h = read_power(fd, BATVADC_DAT_H); - - if (h & 0x20) - return 2600 - ((~l & 0xff) + ((~h & 0x1f) << 8) + 1) * 1000 / 3724; - - return 2600 + (l + (h << 8)) * 1000 / 3724; -} - -int get_bat_current(int fd) -{ - unsigned l = read_power(fd, BATIADC_DAT_L); - unsigned h = read_power(fd, BATIADC_DAT_H); - - if (h & 0x20) - return - (int)((~l & 0xff) + ((~h & 0x1f) << 8) + 1) * 1000 / 1341; - - return (l + (h << 8)) * 1000 / 1341; -} - -unsigned get_bat_oc_voltage(int fd) -{ - unsigned l = read_power(fd, BATOCV_DAT_L); - unsigned h = read_power(fd, BATOCV_DAT_H); - - if (h & 0x20) - return 2600 - ((~l & 0xff) + ((~h & 0x1f) << 8) + 1) * 1000 / 3724; - - return 2600 + (l + (h << 8)) * 1000 / 3724; -} - -enum { - POWER_CHARGER_ENABLED, - POWER_VOUT_ENABLED, - POWER_VOUT_AUTO, -}; - -#define SYS_CTL0 0x01 -#define SYS_CTL1 0x02 -#define SYS_CTL2 0x0c -#define SYS_CTL3 0x03 -#define SYS_CTL4 0x04 -#define SYS_CTL5 0x07 - -#define Charger_CTL1 0x22 -#define Charger_CTL2 0x24 -#define CHG_DIG_CTL4 0x25 - -void power_setup(int fd, unsigned flags) -{ - update_power(fd, SYS_CTL1, 0x03, 0x00); // disable automatic control based on load detection - update_power(fd, SYS_CTL0, 0x1e, BIT(1) | BIT(2)); // 2=boost 1=charger enable - update_power(fd, SYS_CTL3, BIT(5), 0); // disable "2x key press = shutdown" function - update_power(fd, SYS_CTL4, BIT(5), 0); // disable "VIN pull out -> VOUT auto-enable" function - - update_power(fd, CHG_DIG_CTL4, 0x1f, 15); // set charging current (in 100mA steps) -} - -#define READ0 0x71 -#define READ1 0x72 -#define READ2 0x77 - -const char* get_chg_status_text(uint8_t s) -{ - switch (s) { - case 0: return "Idle"; - case 1: return "Trickle charge"; - case 2: return "Constant current phase"; - case 3: return "Constant voltage phase"; - case 4: return "Constant voltage stop"; - case 5: return "Full"; - case 6: return "Timeout"; - default: return "Unknown"; - } -} - -void power_status(int fd) -{ - uint8_t r0 = read_power(fd, READ0); - uint8_t r1 = read_power(fd, READ1); - uint8_t r2 = read_power(fd, READ2); - - printf("Charger: %s (%s%s%s%s%s%s%s)\n", get_chg_status_text((r0 >> 5) & 0x7), - r0 & BIT(4) ? " chg_op" : "", - r0 & BIT(3) ? " chg_end" : "", - r0 & BIT(2) ? " cv_timeout" : "", - r0 & BIT(1) ? " chg_timeout" : "", - r0 & BIT(0) ? " trickle_timeout" : "", - r1 & BIT(6) ? " VIN overvoltage" : "", - r1 & BIT(5) ? " <= 75mA load" : "" - ); - - printf("Button: %02hhx (%s%s%s%s)\n", r2, - r2 & BIT(3) ? " btn_press" : " btn_not_press", - r2 & BIT(2) ? " double_press" : "", - r2 & BIT(1) ? " long_press" : "", - r2 & BIT(0) ? " short_press" : "" - ); - - printf("0x70: %02hhx\n", read_power(fd, 0x70)); - - update_power(fd, READ2, 0x7, 0x7); -} - -/* -- Independent control of - - Boost (5V VOUT to power Pinephone) - - Battery Charger - -- Optional automatic power on when load is inserted -- Optional auto enable of VOUT when disconnecting VIN (reg 0x04) - -- Optiobal automatic shutdown when VOUT has light load (customizable via reg - 0x0c, min. is 100mA, shutdown lastsa 8-64s (see reg 0x04)) - -- Charger_CTL1 0x22 - - Control of charging current based on VOUT undervoltage (it tries - to keep VOUT in a certain range by reducing load on VIN by - decreasing charging current?) - -- Battery type selection 4.2/4.3/4.35V - - + extra margin 0-42mV during constant voltage phase? - - External (via VSET pin) or internal setting (via reg 0x24) - -- Charging current selection (100mA - 2.3A ?) - -- Charging status register - - charging state - idle, trickle, constant voltage/current phase, full, - timeout - - LED heavey load indication - - VIN overvoltage indication (> 5.6V) - -- Button press status - - current state: UP/DOWN - - long press - - short press - -GPIO: - -- KEY input - - Long press button time selection 1-4s - - Enable/disable 2x short press shutdown function -- L3/L4 function selection: - - GPIO0/1 - - normal function -- LIGHT pin function selection: - - GPIO2 - - VREF - - WLED -- VSET - - VSET (normal function to select battery voltage via PIN setting) - - GPIO4 -- RSET - - GPIO3 - - battery internal resistance selection via resistor on the RSET pin - -- separate input/output enable register for all 5 GPIOs -- GPIO data register to read/write values to pins - -ADC: - -- 14 bit two register VBAT, IBAT, VBAT_OCV readings -*/ - -int main(int ac, char* av[]) -{ - int fd, ret; - - fd = pogo_i2c_open(); - - printf("V=%u mV (OCV %u mV) I=%d mA\n", get_bat_voltage(fd), get_bat_oc_voltage(fd), get_bat_current(fd)); - -// uint8_t v = read_power(fd, 2); -// printf("%02hhx\n", v); - - return 0; -} diff --git a/common.c b/common.c new file mode 100644 index 0000000..fb1e962 --- /dev/null +++ b/common.c @@ -0,0 +1,387 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define BIT(n) (1u << (n)) + +#define KB_ADDR 0x15 +#define POWER_ADDR 0x75 + +static bool verbose; +#define debug(args...) { if (verbose) printf(args); } + +static void syscall_error(int is_err, const char* fmt, ...) +{ + va_list ap; + + if (!is_err) + return; + + fprintf(stderr, "ERROR: "); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, ": %s\n", strerror(errno)); + + exit(1); +} + +static void error(const char* fmt, ...) +{ + va_list ap; + + fprintf(stderr, "ERROR: "); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "\n"); + + exit(1); +} + +static bool read_file(const char* path, char* buf, size_t size) +{ + int fd; + ssize_t ret; + + fd = open(path, O_RDONLY); + if (fd < 0) + return false; + + ret = read(fd, buf, size); + close(fd); + if (ret < 0) + return false; + + if (ret < size) { + buf[ret] = 0; + return true; + } else { + buf[size - 1] = 0; + return false; + } +} + +static int open_usb_dev(uint16_t vid, uint16_t pid) +{ + char path[256], buf[256]; + struct dirent *e; + unsigned e_vid, e_pid, bus, dev; + int fd = -1, ret; + DIR* d; + + d = opendir("/sys/bus/usb/devices"); + syscall_error(d == NULL, "opendir(/sys/bus/usb/devices) failed"); + + while (true) { + errno = 0; + e = readdir(d); + syscall_error(e == NULL && errno, "readdir(/sys/bus/usb/devices) failed"); + if (!e) + break; + + if (!strcmp(e->d_name, ".") || !strcmp(e->d_name, "..")) + continue; + + snprintf(path, sizeof path, + "/sys/bus/usb/devices/%s/idVendor", e->d_name); + if (!read_file(path, buf, sizeof buf)) + continue; + + ret = sscanf(buf, "%x", &e_vid); + if (ret != 1) + error("Failed to parse %s", path); + + snprintf(path, sizeof path, + "/sys/bus/usb/devices/%s/idProduct", e->d_name); + if (!read_file(path, buf, sizeof buf)) + continue; + + ret = sscanf(buf, "%x", &e_pid); + if (ret != 1) + error("Failed to parse %s", path); + + if (e_vid == vid && e_pid == pid) { + snprintf(path, sizeof path, + "/sys/bus/usb/devices/%s/busnum", e->d_name); + if (!read_file(path, buf, sizeof buf)) + error("Failed to read %s", path); + + ret = sscanf(buf, "%u", &bus); + if (ret != 1) + error("Failed to parse %s", path); + + snprintf(path, sizeof path, + "/sys/bus/usb/devices/%s/devnum", e->d_name); + if (!read_file(path, buf, sizeof buf)) + error("Failed to read %s", path); + + ret = sscanf(buf, "%u", &dev); + if (ret != 1) + error("Failed to parse %s", path); + + snprintf(path, sizeof path, + "/dev/bus/usb/%03u/%03u", bus, dev); + + debug("Found %04x:%04x at %s\n", e_vid, e_pid, path); + + fd = open(path, O_RDWR); + syscall_error(fd < 0, "open(%s) failed", path); + break; + } + } + + errno = ENOENT; + closedir(d); + return fd; +} + +static int handle_urb(int usb_fd, struct usbdevfs_urb* urb, int timeout) +{ + int ret; + struct usbdevfs_urb* reaped_urb; + int retries = 0; + +retry: + ret = ioctl(usb_fd, USBDEVFS_SUBMITURB, urb); + if (ret < 0) + return ret; + + struct pollfd fd = { + .fd = usb_fd, + .events = POLLOUT, + }; + + ret = poll(&fd, 1, timeout); + if (ret <= 0) { + if (ret == 0) + errno = ETIMEDOUT; + + int save_errno = errno; + + // on timeout or other poll error, we need to discard and reap the submitted URB + ret = ioctl(usb_fd, USBDEVFS_DISCARDURB, urb); + + // even if discard fails, URB may still be reapable, we need to try reaping anyway + ret = ioctl(usb_fd, USBDEVFS_REAPURBNDELAY, &reaped_urb); + + // reap must immediately succeed, otherwise this is fatal + syscall_error(ret < 0, "USBDEVFS_REAPURBNDELAY failed"); + + errno = save_errno; + return -1; + } + + // hopefully POLLERR means we get some error immediately on reap + + ret = ioctl(usb_fd, USBDEVFS_REAPURB, &reaped_urb); + if (ret < 0) + return ret; + + // EPROTO errors are recoverable + if (urb->status == -71 && retries < 3) { + retries++; + goto retry; + } + + if (urb->status != 0) { + errno = -urb->status; + return -1; + } + + return 0; +} + +static int pogo_i2c_open(void) +{ + int ret; + char path[256], buf[1024]; + int fd = -1; + + for (int i = 0; i < 8; i++) { + snprintf(path, sizeof path, "/sys/class/i2c-adapter/i2c-%d/uevent", i); + if (!read_file(path, buf, sizeof buf)) + continue; + + if (!strstr(buf, "OF_FULLNAME=/soc/i2c@1c2b400")) + continue; + + snprintf(path, sizeof path, "/dev/i2c-%d", i); + + int fd = open(path, O_RDWR); + syscall_error(fd < 0, "open(%s) failed"); + + return fd; + } + + error("Can't find POGO I2C adapter"); + return -1; +} + +ssize_t xwrite(int fd, uint8_t* buf, size_t len) +{ + size_t off = 0; + + while (off < len) { + ssize_t ret = write(fd, buf + off, len - off); + if (ret < 0) + return ret; + + off += ret; + } + + return off; +} + +static const uint8_t crc8_0x7_table[] = { + 0x00, 0x07, 0x0e, 0x09, 0x1c, 0x1b, 0x12, 0x15, + 0x38, 0x3f, 0x36, 0x31, 0x24, 0x23, 0x2a, 0x2d, + 0x70, 0x77, 0x7e, 0x79, 0x6c, 0x6b, 0x62, 0x65, + 0x48, 0x4f, 0x46, 0x41, 0x54, 0x53, 0x5a, 0x5d, + 0xe0, 0xe7, 0xee, 0xe9, 0xfc, 0xfb, 0xf2, 0xf5, + 0xd8, 0xdf, 0xd6, 0xd1, 0xc4, 0xc3, 0xca, 0xcd, + 0x90, 0x97, 0x9e, 0x99, 0x8c, 0x8b, 0x82, 0x85, + 0xa8, 0xaf, 0xa6, 0xa1, 0xb4, 0xb3, 0xba, 0xbd, + 0xc7, 0xc0, 0xc9, 0xce, 0xdb, 0xdc, 0xd5, 0xd2, + 0xff, 0xf8, 0xf1, 0xf6, 0xe3, 0xe4, 0xed, 0xea, + 0xb7, 0xb0, 0xb9, 0xbe, 0xab, 0xac, 0xa5, 0xa2, + 0x8f, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9d, 0x9a, + 0x27, 0x20, 0x29, 0x2e, 0x3b, 0x3c, 0x35, 0x32, + 0x1f, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0d, 0x0a, + 0x57, 0x50, 0x59, 0x5e, 0x4b, 0x4c, 0x45, 0x42, + 0x6f, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7d, 0x7a, + 0x89, 0x8e, 0x87, 0x80, 0x95, 0x92, 0x9b, 0x9c, + 0xb1, 0xb6, 0xbf, 0xb8, 0xad, 0xaa, 0xa3, 0xa4, + 0xf9, 0xfe, 0xf7, 0xf0, 0xe5, 0xe2, 0xeb, 0xec, + 0xc1, 0xc6, 0xcf, 0xc8, 0xdd, 0xda, 0xd3, 0xd4, + 0x69, 0x6e, 0x67, 0x60, 0x75, 0x72, 0x7b, 0x7c, + 0x51, 0x56, 0x5f, 0x58, 0x4d, 0x4a, 0x43, 0x44, + 0x19, 0x1e, 0x17, 0x10, 0x05, 0x02, 0x0b, 0x0c, + 0x21, 0x26, 0x2f, 0x28, 0x3d, 0x3a, 0x33, 0x34, + 0x4e, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5c, 0x5b, + 0x76, 0x71, 0x78, 0x7f, 0x6a, 0x6d, 0x64, 0x63, + 0x3e, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2c, 0x2b, + 0x06, 0x01, 0x08, 0x0f, 0x1a, 0x1d, 0x14, 0x13, + 0xae, 0xa9, 0xa0, 0xa7, 0xb2, 0xb5, 0xbc, 0xbb, + 0x96, 0x91, 0x98, 0x9f, 0x8a, 0x8d, 0x84, 0x83, + 0xde, 0xd9, 0xd0, 0xd7, 0xc2, 0xc5, 0xcc, 0xcb, + 0xe6, 0xe1, 0xe8, 0xef, 0xfa, 0xfd, 0xf4, 0xf3 +}; + +static uint8_t crc8(const uint8_t *pdata, size_t nbytes) +{ + unsigned int idx; + uint8_t crc = 0; + + while (nbytes--) { + idx = (crc ^ *pdata); + crc = (crc8_0x7_table[idx]) & 0xff; + pdata++; + } + + return crc; +} + +uint64_t time_abs(void) +{ + struct timespec tmp; + int ret; + + ret = clock_gettime(CLOCK_MONOTONIC, &tmp); + if (ret < 0) + return 0; + + return tmp.tv_sec * 1000000000ull + tmp.tv_nsec; +} + +static int gpiochip_open(const char* match) +{ + int ret; + char path[256], buf[1024]; + int fd = -1; + + for (int i = 0; i < 8; i++) { + snprintf(path, sizeof path, "/sys/bus/gpio/devices/gpiochip%d/uevent", i); + if (!read_file(path, buf, sizeof buf)) + continue; + + if (!strstr(buf, match)) + continue; + + snprintf(path, sizeof path, "/dev/gpiochip%d", i); + + int fd = open(path, O_RDWR); + syscall_error(fd < 0, "open(%s) failed"); + + return fd; + } + + error("Can't find %s gpiochip", match); + return -1; +} + +static int gpio_setup_pl12(unsigned flags) +{ + int ret; + struct gpio_v2_line_request req = { + .num_lines = 1, + .offsets[0] = 12, + .config.flags = flags, + .consumer = "ppkbd", + }; + + int fd = gpiochip_open("OF_FULLNAME=/soc/pinctrl@1f02c00"); + + ret = ioctl(fd, GPIO_V2_GET_LINE_IOCTL, &req); + syscall_error(ret < 0, "GPIO_V2_GET_LINE_IOCTL failed"); + + close(fd); + + return req.fd; +} + +static int gpio_get_value(int lfd) +{ + int ret; + struct gpio_v2_line_values vals = { + .mask = 1, + }; + + ret = ioctl(lfd, GPIO_V2_LINE_GET_VALUES_IOCTL, &vals); + syscall_error(ret < 0, "GPIO_V2_GET_LINE_IOCTL failed"); + + return vals.bits & 0x1; +} + +static int gpio_set_value(int lfd, int val) +{ + int ret; + struct gpio_v2_line_values vals = { + .mask = 1, + }; + + ret = ioctl(lfd, GPIO_V2_LINE_GET_VALUES_IOCTL, &vals); + syscall_error(ret < 0, "GPIO_V2_GET_LINE_IOCTL failed"); + + return vals.bits & 0x1; +} diff --git a/firmware/build.sh b/firmware/build.sh index f087369..29d60b8 100755 --- a/firmware/build.sh +++ b/firmware/build.sh @@ -5,6 +5,45 @@ set -e rm -rf build mkdir -p build -sdcc -mmcs51 --iram-size 256 --xram-size 2048 --code-size 0x6000 --code-loc 0x2000 --opt-code-size -I. main.c -o build/fw.ihx -makebin build/fw.ihx build/fw.bin -dd if=bootloader.bin of=build/fw.bin conv=notrunc &>/dev/null \ No newline at end of file +hex2bin() +{ + local name="$1" + + makebin build/$name.ihx build/$name.bin + dd if=bootloader.bin of=build/$name.bin conv=notrunc &>/dev/null +} + +# build stock FW + +cpp -P -nostdinc -I. -D__ASM_ONLY__ stock-ivt.asm build/stock-ivt.asm +sdas8051 -plosgff build/stock-ivt.rel build/stock-ivt.asm + +echo Stock FW +sdcc \ + -mmcs51 --iram-size 256 --xram-size 2048 \ + --code-size 0x2000 --code-loc 0x2130 \ + -Wl-bIVECT=0x2000 \ + -I. \ + -DFW_REVISION_STR="\"$(git describe) $(git log -1 --format=%cd --date=iso)\"" \ + -DCONFIG_STOCK_FW=1 \ + build/stock-ivt.rel main.c \ + -o build/fw-stock.ihx + +hex2bin fw-stock + +# build user FW + +echo User FW +sdcc \ + -mmcs51 --iram-size 255 --xram-size 2048 \ + --code-size 0x4000 --code-loc 0x4000 \ + -I. \ + -DFW_REVISION_STR="\"$(git describe) $(git log -1 --format=%cd --date=iso)\"" \ + -DCONFIG_STOCK_FW=0 \ + -DCONFIG_USB_STACK=0 \ + -DCONFIG_DEBUG_LOG=1 \ + -DCONFIG_SELFTEST=0 \ + main.c \ + -o build/fw-user.ihx + +hex2bin fw-user diff --git a/firmware/em85f684a.h b/firmware/em85f684a.h index 5cadd4c..b99463e 100644 --- a/firmware/em85f684a.h +++ b/firmware/em85f684a.h @@ -20,6 +20,8 @@ #ifndef __EM85F684A_H__ #define __EM85F684A_H__ +#ifndef __ASM_ONLY__ + __sfr __at(0x87) PCON; // Power Control __sfr __at(0xc0) RSTSC; // Reset Source __sfr __at(0xbf) P0_PRST; // Peripheral Reset @@ -105,11 +107,9 @@ __sfr __at(0xc4) P0_I2CADB; // I2CA Data Buffer Register __sfr __at(0xc5) P0_I2CADAL; // I2CA Device Address Register L __sfr __at(0xc6) P0_I2CADAH; // I2CA Device Address Register H __sfr __at(0xc7) P0_I2CASF; // I2CA status flag - __sfr __at(0xcd) P0_DEVPD1; // Peripheral power down __sfr __at(0xce) P0_DEVPD2; // Peripheral power down __sfr __at(0xcf) P0_DEVPD3; // Peripheral power down - __sfr __at(0xd1) P0_SMBTO1; // SMbus Time Out 1 Register __sfr __at(0xd2) P0_SMBTR1; // SMbus Timer reload 1 Register __sfr __at(0xd3) P0_SMBTO2; // SMbus Time Out 2 Register @@ -273,19 +273,24 @@ __sbit __at(0xea) P92; __sbit __at(0xe9) P91; __sbit __at(0xe8) P90; +#endif + #define IRQ_EINT0 0 // External Interrupt 0 #define IRQ_TIMER0 1 // Timer0 Overflow #define IRQ_EINT1 2 // External Interrupt 1 #define IRQ_TIMER1 3 // Timer1 Overflow #define IRQ_UART0 4 // Serial Port 0 #define IRQ_PINCHANGE 6 // PIN CHANGE Interrupt 0 +#define IRQ_LVD 7 // Low voltage detect Interrupt #define IRQ_SYSTEMHOLD 8 // System Hold Interrupt #define IRQ_INT2_3 10 // External Interrupt 2~3 #define IRQ_SPI 11 // SPI Interrupt -#define IRQ_ADC 13 // ADC Conversion Complete -#define IRQ_TIMER2 14 // Timer2 Overflow +#define IRQ_PWMD 12 // PWMD Interrupt +#define IRQ_TIMER3 14 // Timer3 Overflow #define IRQ_PWMA 15 // PWMA Interrupt +#define IRQ_PWME 16 // PWME Interrupt #define IRQ_USB 17 // USB Interrupt +#define IRQ_PWMF 18 // PWMF Interrupt #define IRQ_I2CA 20 // I2CA Interrupt #define IRQ_PWMB 23 // PWMB Interrupt #define IRQ_PWMC 24 // PWMC Interrupt diff --git a/firmware/main.c b/firmware/main.c index 621275a..6b4b962 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -21,75 +21,45 @@ #include #include -// configuration (we can make this runtime configurable via i2c) -// polled input mode is necessary if some rows are always on -#define POLL_INPUT 1 +#ifndef CONFIG_FLASH_ENABLE +#define CONFIG_FLASH_ENABLE 1 +#endif +#ifndef CONFIG_DEBUG_LOG +#define CONFIG_DEBUG_LOG 1 +#endif +#ifndef CONFIG_USB_STACK +#define CONFIG_USB_STACK 1 +#endif +#ifndef CONFIG_SELFTEST +#define CONFIG_SELFTEST 1 +#endif +#ifndef CONFIG_STOCK_FW +#define CONFIG_STOCK_FW 1 +#endif + +#define USB_DEBUG 0 #define BIT(n) (1u << (n)) -// timers clock is 2 MHz so we need to wait for 2000 ticks to get delay of 1ms -#define T0_SET_TIMEOUT(n) { \ - TL0 = 0x00; \ - TH0 = (0x10000u - n) >> 8; \ - TL0 = (0x10000u - n) & 0xff; \ - } - -#define T1_SET_TIMEOUT(n) { \ - TL1 = 0x00; \ - TH1 = (0x10000u - n) >> 8; \ - TL1 = (0x10000u - n) & 0xff; \ - } - -#define delay_us(n) { \ - TL0 = 0x00; \ - TF0 = 0; \ - TH0 = (0x10000u - 2 * n) >> 8; \ - TL0 = (0x10000u - 2 * n) & 0xff; \ - while (!TF0); \ -} - -static __sbit p6_changed = 0; -static __sbit run_tasks = 0; - -// we use this interrupt for wakeup from sleep on input change - -void pinchange_interupt(void) __interrupt(IRQ_PINCHANGE) -{ - uint8_t saved_page = PAGESW; - - PAGESW = 0; - - if (P0_ICEN & BIT(1)) - p6_changed = 1; - - // clear input change flags - P0_ICEN = BIT(5); - - PAGESW = saved_page; -} - -// we use this interrupt as a scheduling tick (wakeup from sleep) - -void timer1_interupt(void) __interrupt(IRQ_TIMER1) -{ - run_tasks = 1; - - // 20 ms - T1_SET_TIMEOUT(40000); - - TF1 = 0; -} - // {{{ Debug logging -static uint8_t __xdata log_buffer[1024]; +#if CONFIG_DEBUG_LOG + +// debug logging needs to have all variables volatile, since they +// can be accessed from interrupts +// +// any access to these variables has to happen with interrupts disabled + +static volatile uint8_t __xdata log_buffer[1024]; // end = start => empty buffer // end can never equal start on a filled buffer // end points to the last char if end != start -static uint16_t log_start = 0; -static uint16_t log_end = 0; +static volatile uint16_t log_start = 0; +static volatile uint16_t log_end = 0; -static void putc(char c) +// putc needs to disable interrupts (thus it's marked __critical) +// it's possible to call putc in any context +static void putc(char c) __critical { log_end = (log_end + 1) % 1024; @@ -151,9 +121,222 @@ static void put_hex_w(uint16_t hex) put_hex_b(hex); } +#else + +#define putc(a) +#define puts(a) +#define put_hex_b(a) +#define put_hex_w(a) +#define put_hex_n(a) +#define put_uint(a) + +#endif + +// }}} +// {{{ Global flags + +static __bit jump_to_usb_bootloader = 0; + +// }}} +// {{{ Interrupt forwarding + +#if CONFIG_STOCK_FW + +// Stock firmware translates all interrupts to inerrupt vectors at 0x4000 +// if IRAM location 0xff contains 0. Otherwise, it call interrupt handlers +// in stock FW. + +#pragma noiv + +uint8_t __idata __at(0xff) stock_flag = 1; + +//XXX: check if we want to jump to user FW + +// cleanup after stock firmware and jump to user firmware +static void jmp_to_user_fw(void) __naked +{ + PAGESW = 0; + + // disable all interrupts + IE = 0; + P0_EIE1 = 0; + P0_EIE2 = 0; + P0_EIE3 = 0; + + // disable timers + TCON = 0; + TMOD = 0; + PSW1 = 0; + + // disable I2C B + P0_I2CBCR1 = 0x00; + P0_I2CBCR2 = 0x20; + P0_I2CBINT = 0; + + // disable watchdog + P0_WDTCR = 0x07; // disable watchdog ~1s + P0_WDTKEY = 0xb1; // disable watchdog + + // reset powerdown/reset registers + P0_DEVPD1 = 0; + P0_DEVPD2 = 0; + P0_DEVPD3 = 0; + P0_PRST = 0; + + // disable USB and clear irq flags + PAGESW = 1; + P1_PHYTEST0 &= ~BIT(6); // phy disable + P1_UDCCTRL &= ~BIT(6); // udc disable + P1_UDCINT0STA = 0; + P1_UDCINT1STA = 0; + P1_UDCINT2STA = 0; + P1_UDCINT0EN = 0; + P1_UDCINT1EN = 0; + P1_UDCINT2EN = 0; + + // disable pullups, set all pins to input + P1_PHCON2 = 0; + P1_P9M0 = 0xffu; + PAGESW = 0; + P0_PHCON0 = 0; + P0_PHCON1 = 0; + P0_P6M0 = 0xffu; + P0_P8M0 = 0xffu; + P0_ICEN = 0; + + stock_flag = 0; + __asm__ ("ljmp 0x4000"); +} + +#endif + +// }}} +// {{{ Original USB bootloader integration + +static void usb_bootloader_jump(void) __naked +{ + PAGESW = 0; + + // disable all interrupts + IE = 0; + P0_EIE1 = 0; + P0_EIE2 = 0; + P0_EIE3 = 0; + + // disable timers + TCON = 0; + TMOD = 0; + PSW1 = 0; + + // disable I2C B + P0_I2CBCR1 = 0x00; + P0_I2CBCR2 = 0x20; + P0_I2CBINT = 0; + + // disable watchdog + P0_WDTCR = 0x07; // disable watchdog ~1s + P0_WDTKEY = 0xb1; // disable watchdog + + // reset powerdown/reset registers + P0_DEVPD1 = 0; + P0_DEVPD2 = 0; + P0_DEVPD3 = 0; + P0_PRST = 0; + + // disable USB and clear irq flags + PAGESW = 1; + P1_PHYTEST0 &= ~BIT(6); // phy disable + P1_UDCCTRL &= ~BIT(6); // udc disable + P1_UDCINT0STA = 0; + P1_UDCINT1STA = 0; + P1_UDCINT2STA = 0; + P1_UDCINT0EN = 0; + P1_UDCINT1EN = 0; + P1_UDCINT2EN = 0; + + // disable pullups, set all pins to input + P1_PHCON2 = 0; + P1_P9M0 = 0xffu; + PAGESW = 0; + P0_PHCON0 = 0; + P0_PHCON1 = 0; + P0_P6M0 = 0xffu; + P0_P8M0 = 0xffu; + P0_ICEN = 0; + + __asm__("mov r6,#0x5a"); + __asm__("mov r7,#0xe7"); + __asm__("ljmp 0x0118"); +} + +// }}} +// {{{ Timers/delays + +// timers clock is 2 MHz so we need to wait for 2000 ticks to get delay of 1ms +#define T0_SET_TIMEOUT(n) { \ + TL0 = 0x00; \ + TH0 = (0x10000u - n) >> 8; \ + TL0 = (0x10000u - n) & 0xff; \ + } + +#define T1_SET_TIMEOUT(n) { \ + TL1 = 0x00; \ + TH1 = (0x10000u - n) >> 8; \ + TL1 = (0x10000u - n) & 0xff; \ + } + +#define delay_us(n) { \ + TL0 = 0x00; \ + TF0 = 0; \ + TH0 = (0x10000u - 2 * n) >> 8; \ + TL0 = (0x10000u - 2 * n) & 0xff; \ + while (!TF0); \ +} + +static volatile __bit run_timed_tasks = 0; + +// we use this interrupt as a scheduling tick (wakeup from sleep) + +void timer1_interrupt(void) __interrupt(IRQ_TIMER1) __using(1) +{ + run_timed_tasks = 1; + + // 20 ms + T1_SET_TIMEOUT(40000); + + TF1 = 0; +} + +// }}} +// {{{ GPIO change interrupt + +// we use this interrupt for wakeup from sleep on input change on port 6 + +static volatile __bit p6_changed = 0; + +void pinchange_interrupt(void) __interrupt(IRQ_PINCHANGE) __using(1) +{ + uint8_t saved_page = PAGESW; + + PAGESW = 0; + + // change flag + if (P0_ICEN & BIT(1)) { + p6_changed = 1; + } + + // disable port 6 change detection + P0_ICEN = 0; + ICIE = 0; + + PAGESW = saved_page; +} + // }}} // {{{ Key scanning +static __bit scan_active = 0; + // Keyboard has 12 columns and 6 rows directly connected to GPIOs. // // C1 P95 @@ -212,7 +395,7 @@ static void put_hex_w(uint16_t hex) // In this state we can use keyscan_idle_is_pressed() to detect whether // any key is pressed, and switch to active mode via keyscan_active(). // -void keyscan_idle(void) +static void keyscan_idle(void) { // enable output low on all columns (P9[7:5] P5[7:0] P8[0]) @@ -222,32 +405,19 @@ void keyscan_idle(void) P8 &= 0xfe; P9 &= 0x1f; -#if POLL_INPUT - // make all columns an input, hi-Z (saves power) - P0_P5M0 = ~0x00u; - P0_P8M0 |= ~0xfeu; - PAGESW = 1; - P1_P9M0 |= ~0x1fu; - ICIE = 0; - p6_changed = 0; -#else P0_P5M0 = 0x00; - P0_P8M0 &= 0xfe; + P0_P8M0 &= 0xfeu; PAGESW = 1; - P1_P9M0 &= 0x1f; + P1_P9M0 &= 0x1fu; - // enable input change interrupt on port6 and clear the interrupt flag after - // things stabilize + // delay a bit for things to stabilize delay_us(10); - PAGESW = 0; p6_changed = 0; - P0_ICEN = BIT(5); - ICIE = 1; -#endif + scan_active = 0; } -uint8_t keyscan_idle_is_pressed(void) +static uint8_t keyscan_idle_is_pressed(void) { return ~P6 & 0x3f; } @@ -257,13 +427,10 @@ uint8_t keyscan_idle_is_pressed(void) // // In this state, we can call keyscan_scan() to perform a scan. // -void keyscan_active(void) +static void keyscan_active(void) { // put all columns to hi-Z (P9[7:5] P5[7:0] P8[0]) - // disable input change interrupt - ICIE = 0; - PAGESW = 0; P5 = 0; @@ -276,13 +443,12 @@ void keyscan_active(void) P0_P8M0 |= ~0xfeu; PAGESW = 1; P1_P9M0 |= ~0x1fu; + + scan_active = 1; } -// XXX: do we need to debounce in the scan function? -// XXX: it looks like that there should be no bouncing going on mechanically - // 12 byte storage required -uint8_t keyscan_scan(uint8_t* res) +static uint8_t keyscan_scan(uint8_t* res) { uint8_t pin, mask = 0, row; @@ -318,18 +484,21 @@ uint8_t keyscan_scan(uint8_t* res) mask |= row; *res++ = row; P0_P8M0 |= BIT(0); - + return mask; } -void ext_int_assert(void) +// }}} +// {{{ Enternal interrupt control + +static void ext_int_assert(void) { P90 = 0; PAGESW = 1; P1_P9M0 &= ~BIT(0); } -void ext_int_deassert(void) +static void ext_int_deassert(void) { P90 = 0; PAGESW = 1; @@ -337,14 +506,560 @@ void ext_int_deassert(void) } // }}} -// {{{ I2C +// {{{ CRC-8 -#define I2C_N_REGS 16 +static const uint8_t crc8_0x7_table[] = { + 0x00, 0x07, 0x0e, 0x09, 0x1c, 0x1b, 0x12, 0x15, + 0x38, 0x3f, 0x36, 0x31, 0x24, 0x23, 0x2a, 0x2d, + 0x70, 0x77, 0x7e, 0x79, 0x6c, 0x6b, 0x62, 0x65, + 0x48, 0x4f, 0x46, 0x41, 0x54, 0x53, 0x5a, 0x5d, + 0xe0, 0xe7, 0xee, 0xe9, 0xfc, 0xfb, 0xf2, 0xf5, + 0xd8, 0xdf, 0xd6, 0xd1, 0xc4, 0xc3, 0xca, 0xcd, + 0x90, 0x97, 0x9e, 0x99, 0x8c, 0x8b, 0x82, 0x85, + 0xa8, 0xaf, 0xa6, 0xa1, 0xb4, 0xb3, 0xba, 0xbd, + 0xc7, 0xc0, 0xc9, 0xce, 0xdb, 0xdc, 0xd5, 0xd2, + 0xff, 0xf8, 0xf1, 0xf6, 0xe3, 0xe4, 0xed, 0xea, + 0xb7, 0xb0, 0xb9, 0xbe, 0xab, 0xac, 0xa5, 0xa2, + 0x8f, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9d, 0x9a, + 0x27, 0x20, 0x29, 0x2e, 0x3b, 0x3c, 0x35, 0x32, + 0x1f, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0d, 0x0a, + 0x57, 0x50, 0x59, 0x5e, 0x4b, 0x4c, 0x45, 0x42, + 0x6f, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7d, 0x7a, + 0x89, 0x8e, 0x87, 0x80, 0x95, 0x92, 0x9b, 0x9c, + 0xb1, 0xb6, 0xbf, 0xb8, 0xad, 0xaa, 0xa3, 0xa4, + 0xf9, 0xfe, 0xf7, 0xf0, 0xe5, 0xe2, 0xeb, 0xec, + 0xc1, 0xc6, 0xcf, 0xc8, 0xdd, 0xda, 0xd3, 0xd4, + 0x69, 0x6e, 0x67, 0x60, 0x75, 0x72, 0x7b, 0x7c, + 0x51, 0x56, 0x5f, 0x58, 0x4d, 0x4a, 0x43, 0x44, + 0x19, 0x1e, 0x17, 0x10, 0x05, 0x02, 0x0b, 0x0c, + 0x21, 0x26, 0x2f, 0x28, 0x3d, 0x3a, 0x33, 0x34, + 0x4e, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5c, 0x5b, + 0x76, 0x71, 0x78, 0x7f, 0x6a, 0x6d, 0x64, 0x63, + 0x3e, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2c, 0x2b, + 0x06, 0x01, 0x08, 0x0f, 0x1a, 0x1d, 0x14, 0x13, + 0xae, 0xa9, 0xa0, 0xa7, 0xb2, 0xb5, 0xbc, 0xbb, + 0x96, 0x91, 0x98, 0x9f, 0x8a, 0x8d, 0x84, 0x83, + 0xde, 0xd9, 0xd0, 0xd7, 0xc2, 0xc5, 0xcc, 0xcb, + 0xe6, 0xe1, 0xe8, 0xef, 0xfa, 0xfd, 0xf4, 0xf3 +}; -static uint8_t i2c_rx_cnt = 0; -static uint8_t i2c_tx_cnt = 0; -static uint8_t i2c_tx_buf[I2C_N_REGS] = {0xaa, 0x55}; -static uint8_t i2c_rx_buf[I2C_N_REGS]; +static uint8_t crc8(const uint8_t *pdata, size_t nbytes) +{ + unsigned int idx; + uint8_t crc = 0; + + while (nbytes--) { + idx = (crc ^ *pdata); + crc = (crc8_0x7_table[idx]) & 0xff; + pdata++; + } + + return crc; +} + +// }}} +// {{{ Public I2C register interface + +#include "registers.h" + +// all the variables are volatile because they can be accessed +// from interrupt context of I2C interrupt handler + +static volatile uint8_t __idata ro_regs[REG_KEYMATRIX_STATE_END + 1] = { + [REG_DEVID_K] = 'K', + [REG_DEVID_B] = 'B', + [REG_FW_REVISION] = FW_REVISION, + [REG_FW_FEATURES] = +#if CONFIG_USB_STACK && CONFIG_DEBUG_LOG + REG_FW_FEATURES_USB_DEBUGGER | +#endif +#if CONFIG_FLASH_ENABLE + REG_FW_FEATURES_FLASHING_MODE | +#endif +#if CONFIG_SELFTEST + REG_FW_FEATURES_SELF_TEST | +#endif +#if CONFIG_STOCK_FW + REG_FW_FEATURES_STOCK_FW | +#endif + 0, + [REG_KEYMATRIX_SIZE] = 0xc6, // 12 x 6 +}; + +static volatile uint8_t ctl_regs[3] = {0, 0, 0}; +static volatile __bit sys_cmd_run = 0; + +static volatile uint8_t reg_addr = 0; + +// }}} +// {{{ Flashing + +static volatile uint8_t __code __at(0x3fff) app_flag; + +#if CONFIG_FLASH_ENABLE + +// all the variables are volatile because they can be accessed +// from interrupt context of I2C interrupt handler + +static volatile uint8_t flash_regs[5] = {0, 0, 0, 0, 0}; + +// this is where the HW expects the data for a code ROM page +static volatile uint8_t __xdata __at(0x780) flash_content[128]; +static volatile uint8_t __xdata __at(0x700) flash_content2[128]; + +// used to signal that flashing command should be executed and +// to block further writes to flashing registers via I2C +static volatile __bit flash_cmd_run = 0; + +#define REG_FLASH(n) flash_regs[REG_FLASH_##n - REG_FLASH_ADDR_L] + +static void user_app_flag_set(uint8_t flag) __critical +{ + if (app_flag == flag) + return; + + for (uint8_t i = 0; i < 128; i++) + flash_content2[i] = 0xff; + flash_content2[0x7fu] = flag; + + PAGESW = 0; + + uint8_t ckcon_saved = CKCON1; + CKCON1 = (CKCON1 & ~0x06u) | (1 << 1); // set HS pre-divider to /4 + + // unlock + P0_FLCR &= ~BIT(2); + P0_FLKEY = 0xA9; + P0_FLKEY = 0x7F; + + __asm__ ( + // dptr0 = source ptr, dptr1 = dest ptr + "push psw\n" + "push _DPL\n" + "push _DPH\n" + "push ar7\n" + "push A\n" + + "mov _DPL1,#0x80\n" + "mov _DPH1,#0x3f\n" + "mov A,#_flash_content2\n" + "mov _DPL,A\n" + "mov A,#(_flash_content2 >> 8)\n" + "mov _DPH,A\n" + "mov r7,#0\n" // counter + + "00002$: movx a,@dptr\n" + "inc dptr\n" + "orl _PCON,#0x08\n" // select dptr1 + "movx @dptr,a\n" + "inc dptr\n" + "anl _PCON,#0xf7\n" // select dptr0 + "inc r7\n" + "cjne r7,#0x80,00002$\n" + + "pop A\n" + "pop ar7\n" + "pop _DPH\n" + "pop _DPL\n" + "pop psw\n" + ); + + P0_FLCR |= BIT(0) | BIT(1); + __asm__("nop"); + while (P0_FLCR & (BIT(0) | BIT(1))); // wait until programming is done + + CKCON1 = ckcon_saved; +} + +static void exec_flashing_command(void) +{ + if (!flash_cmd_run) + return; + + // skip normal result codes + if (REG_FLASH(CMD) == 0 || REG_FLASH(CMD) == 0xff) + goto out; + + // return error if the unlock magic is incorrect + if (REG_FLASH(UNLOCK) != REG_FLASH_UNLOCK_MAGIC) + goto err; + + if (REG_FLASH(CMD) == REG_FLASH_CMD_READ_ROM) { + // does not need to be in critical section, because I2C access + // is prevented via flash_cmd_run + __asm__ ( + // dptr0 = CODE ptr, dptr1 = XRAM ptr + "push psw\n" + "push _DPL\n" + "push _DPH\n" + "push ar7\n" + "push A\n" + + "mov _DPL,_flash_regs\n" + "mov _DPH,(_flash_regs + 1)\n" + "mov A,#_flash_content\n" + "mov _DPL1,A\n" + "mov A,#(_flash_content >> 8)\n" + "mov _DPH1,A\n" + + "mov r7,#0\n" // counter + "00001$: mov A,r7\n" + "movc a,@a+dptr\n" + "orl _PCON,#0x08\n" // select dptr1 + "movx @dptr,a\n" + "inc dptr\n" + "anl _PCON,#0xf7\n" // select dptr0 + "inc r7\n" + "cjne r7,#0x80,00001$\n" + + "pop A\n" + "pop ar7\n" + "pop _DPH\n" + "pop _DPL\n" + "pop psw\n" + ); + + REG_FLASH(CRC8) = crc8(flash_content, 128); + } else if (REG_FLASH(CMD) == REG_FLASH_CMD_WRITE_ROM) { + // does not need to be in critical section, because I2C access + // is prevented via flash_cmd_run + if (REG_FLASH(CRC8) != crc8(flash_content, 128)) + goto err; + if (REG_FLASH(ADDR_H) < 0x40 || REG_FLASH(ADDR_H) >= 0x80) + goto err; + if ((REG_FLASH(ADDR_L) % 128) != 0) + goto err; + + user_app_flag_set(0xff); + + // Burn the code, we need to disable interrupts during write + // + // 1) unlock by writing 0xa9 0x7f to FLKEY + // 2) set HS pre-divider to /4 + // 3) FLCR |= BIT(2) (if writing options) + // 4) MOVX data to area starting from the ROM address we want to + // write + // 5) FLCR |= BIT(0) | BIT(1); + // 6) nop and wait for FLCR bits to clear + // 7) FLCR &= ~BIT(2), restore pre-divider (cleanup) + // + // FLCR: + // bit 0 = WE + // bit 1 = EE + // bit 2 = MEMSP + // bit 7 = EPEN (protection) + // + __critical { + PAGESW = 0; + + uint8_t ckcon_saved = CKCON1; + CKCON1 = (CKCON1 & ~0x06u) | (1 << 1); // set HS pre-divider to /4 + + // unlock + P0_FLCR &= ~BIT(2); + P0_FLKEY = 0xA9; + P0_FLKEY = 0x7F; + + __asm__ ( + // dptr0 = source ptr, dptr1 = dest ptr + "push psw\n" + "push _DPL\n" + "push _DPH\n" + "push ar7\n" + "push A\n" + + "mov _DPL1,_flash_regs\n" + "mov _DPH1,(_flash_regs + 1)\n" + "mov A,#_flash_content\n" + "mov _DPL,A\n" + "mov A,#(_flash_content >> 8)\n" + "mov _DPH,A\n" + "mov r7,#0\n" // counter + + "00002$: movx a,@dptr\n" + "inc dptr\n" + "orl _PCON,#0x08\n" // select dptr1 + "movx @dptr,a\n" + "inc dptr\n" + "anl _PCON,#0xf7\n" // select dptr0 + "inc r7\n" + "cjne r7,#0x80,00002$\n" + + "pop A\n" + "pop ar7\n" + "pop _DPH\n" + "pop _DPL\n" + "pop psw\n" + ); + + P0_FLCR |= BIT(0) | BIT(1); + __asm__("nop"); + while (P0_FLCR & (BIT(0) | BIT(1))); // wait until programming is done + + CKCON1 = ckcon_saved; + } + } else if (REG_FLASH(CMD) == REG_FLASH_CMD_COMMIT) { + user_app_flag_set(1); + } else if (REG_FLASH(CMD) == REG_FLASH_CMD_ERASE_ROM) { + user_app_flag_set(0xff); + } else { + goto err; + } + + REG_FLASH(CMD) = 0; +out: + REG_FLASH(UNLOCK) = 0; + flash_cmd_run = 0; + return; + +err: + REG_FLASH(CMD) = 0xff; + goto out; +} + +#endif + +// }}} +// {{{ Self-tests + +#if CONFIG_SELFTEST + +static void set_column_input(uint8_t col) +{ + if (col <= 2) + PAGESW = 1; + else + PAGESW = 0; + + switch (col) { + case 0: P1_P9M0 |= BIT(5); break; + case 1: P1_P9M0 |= BIT(6); break; + case 2: P1_P9M0 |= BIT(7); break; + case 3: P0_P5M0 |= BIT(0); break; + case 4: P0_P5M0 |= BIT(1); break; + case 5: P0_P5M0 |= BIT(2); break; + case 6: P0_P5M0 |= BIT(3); break; + case 7: P0_P5M0 |= BIT(4); break; + case 8: P0_P5M0 |= BIT(5); break; + case 9: P0_P5M0 |= BIT(6); break; + case 10: P0_P5M0 |= BIT(7); break; + case 11: P0_P8M0 |= BIT(0); break; + } +} + +static void set_column_output(uint8_t col) +{ + if (col <= 2) + PAGESW = 1; + else + PAGESW = 0; + + switch (col) { + case 0: P1_P9M0 &= ~BIT(5); break; + case 1: P1_P9M0 &= ~BIT(6); break; + case 2: P1_P9M0 &= ~BIT(7); break; + case 3: P0_P5M0 &= ~BIT(0); break; + case 4: P0_P5M0 &= ~BIT(1); break; + case 5: P0_P5M0 &= ~BIT(2); break; + case 6: P0_P5M0 &= ~BIT(3); break; + case 7: P0_P5M0 &= ~BIT(4); break; + case 8: P0_P5M0 &= ~BIT(5); break; + case 9: P0_P5M0 &= ~BIT(6); break; + case 10: P0_P5M0 &= ~BIT(7); break; + case 11: P0_P8M0 &= ~BIT(0); break; + } +} + +static __bit get_column_value(uint8_t col) +{ + switch (col) { + case 0: return P95; + case 1: return P96; + case 2: return P97; + case 3: return P50; + case 4: return P51; + case 5: return P52; + case 6: return P53; + case 7: return P54; + case 8: return P55; + case 9: return P56; + case 10: return P57; + case 11: return P80; + default: return 0; + } +} + +static void set_column_value(uint8_t col, __bit val) +{ + switch (col) { + case 0: P95 = val; break; + case 1: P96 = val; break; + case 2: P97 = val; break; + case 3: P50 = val; break; + case 4: P51 = val; break; + case 5: P52 = val; break; + case 6: P53 = val; break; + case 7: P54 = val; break; + case 8: P55 = val; break; + case 9: P56 = val; break; + case 10: P57 = val; break; + case 11: P80 = val; break; + } +} + +static void self_test_run(void) +{ + PAGESW = 0; + + // all rows pull-up already as a defauklt config, so set all columns + // to hi-Z first + P0_P5M0 = ~0x00u; + P0_P8M0 |= ~0xfeu; + + PAGESW = 1; + P1_P9M0 |= ~0x1fu; + + // for each column: + // - output low + // - read other columns + // - turn column back to hi-Z + // data output: + // - list of columns shorted together in 2 byte sequences as a bitmask + // - first byte: cols 12-9 aligned to LSB + // - second byte: cols 8-1 + // - terminated by two-byte 00 00 sequence + + puts("column self-test:\n"); + + for (uint8_t c1 = 0; c1 < 12; c1++) { + set_column_output(c1); + + for (uint8_t c2 = c1 + 1; c2 < 12; c2++) { + set_column_value(c1, 0); + __bit a = get_column_value(c2); + set_column_value(c1, 1); + __bit b = get_column_value(c2); + + if (!a && b) { + // column-column short found + puts("c-c short: "); + put_uint(c1); + puts(" "); + put_uint(c2); + puts("\n"); + } + } + + set_column_input(c1); + } + + puts("done\n"); +} + +#endif + +// }}} +// {{{ System commands + +#define REG_SYS(n) ctl_regs[REG_SYS_##n - REG_SYS_CONFIG] + +static void exec_system_command(void) +{ + if (!sys_cmd_run) + return; + + if (REG_SYS(COMMAND) == 0 || REG_SYS(COMMAND) == 0xff) + goto out_done; + + if (REG_SYS(COMMAND) == REG_SYS_COMMAND_MCU_RESET) { + RSTSC &= ~BIT(7); + RSTSC |= BIT(7); +#if CONFIG_SELFTEST + } else if (REG_SYS(COMMAND) == REG_SYS_COMMAND_SELFTEST) { + self_test_run(); +#endif + } else if (REG_SYS(COMMAND) == REG_SYS_COMMAND_USB_IAP) { + jump_to_usb_bootloader = 1; + } else { + REG_SYS(COMMAND) = 0xff; + goto out_done; + } + + REG_SYS(COMMAND) = 0x00; +out_done: + sys_cmd_run = 0; +} + +// }}} +// {{{ I2C register access + +// only call this in interrupt context from regbank 1! +static uint8_t reg_get_value(void) __using(1) +{ +#if CONFIG_DEBUG_LOG + // read from this register reads the next byte of log buffer + // (register address is not advanced!) + if (reg_addr == 0xff) { + if (log_start != log_end) { + log_start = (log_start + 1) % 1024; + return log_buffer[log_start]; // push data to fifo + } + + goto none; + } +#endif + + if (reg_addr <= REG_KEYMATRIX_STATE_END) { + return ro_regs[reg_addr]; + } else if (reg_addr < REG_SYS_CONFIG) { + goto none; + } else if (reg_addr <= REG_SYS_USER_APP_BLOCK) { + return ctl_regs[reg_addr - REG_SYS_CONFIG]; +#if CONFIG_FLASH_ENABLE + } else if (reg_addr < REG_FLASH_DATA_START) { + goto none; + } else if (reg_addr <= REG_FLASH_DATA_END) { + return flash_content[reg_addr - REG_FLASH_DATA_START]; + } else if (reg_addr <= REG_FLASH_CMD) { + return flash_regs[reg_addr - REG_FLASH_ADDR_L]; +#endif + } + +none: + return 0; +} + +// only call this in interrupt context from regbank 1! +static void reg_set_value(uint8_t val) __using(1) +{ + if (reg_addr < REG_SYS_CONFIG) { + return; + } else if (reg_addr <= REG_SYS_USER_APP_BLOCK) { + if (reg_addr == REG_SYS_COMMAND) { + if (sys_cmd_run) + return; + sys_cmd_run = 1; + } + + ctl_regs[reg_addr - REG_SYS_CONFIG] = val; +#if CONFIG_FLASH_ENABLE + } else if (reg_addr < REG_FLASH_DATA_START) { + return; + } else if (reg_addr <= REG_FLASH_DATA_END) { + if (flash_cmd_run) + return; + flash_content[reg_addr - REG_FLASH_DATA_START] = val; + } else if (reg_addr <= REG_FLASH_CMD) { + if (flash_cmd_run) + return; + + flash_regs[reg_addr - REG_FLASH_ADDR_L] = val; + + if (reg_addr == REG_FLASH_CMD) + flash_cmd_run = 1; +#endif + } +} /* * Host write transaction: sending 01 02 03 04 to device at 0x15 (0x2a == 0x15 << 1) @@ -361,8 +1076,8 @@ static uint8_t i2c_rx_buf[I2C_N_REGS]; * int=a0 CR1=8d CR2=af tx * int=a0 CR1=8d CR2=af tx * int=a0 CR1=8d CR2=af tx - * int=a0 CR1=89 CR2=af tx - * int=b0 CR1=08 CR2=2f stop + * int=a0 CR1=89 CR2=af tx NACK from host (last read byte) + * int=b0 CR1=08 CR2=2f stop STOP condition reported * * CR1: * 7: STROBE/PEND (RX/TX: not set on stop IRQ, even though RXSF/TXSF is also set) @@ -391,62 +1106,66 @@ static uint8_t i2c_rx_buf[I2C_N_REGS]; */ #define I2C_ADDR 0x15 -#define I2C_DEBUG 0 -void i2c_b_interupt(void) __interrupt(IRQ_I2CB) +static volatile uint8_t i2c_n = 0; + +// interrupt needs to be enabled for wakeup from powerdown to work +void i2c_b_interrupt(void) __interrupt(IRQ_I2CB) __using(1) { uint8_t saved_page = PAGESW; PAGESW = 0; -#if I2C_DEBUG - puts("i2cb int="); - put_hex_b(P0_I2CBINT); - puts(" CR1="); - put_hex_b(P0_I2CBCR1); - puts(" CR2="); - put_hex_b(P0_I2CBCR2); - puts("\n"); -#endif + uint8_t intf = P0_I2CBINT; + uint8_t cr1 = P0_I2CBCR1; + uint8_t cr2 = P0_I2CBCR2; // handle stop condition - if (P0_I2CBINT & BIT(4)) { - if (i2c_rx_cnt) { - //XXX: process received data - - puts("I2C RX: "); - for (uint8_t i = 0; i < i2c_rx_cnt; i++) - put_hex_b(i2c_rx_buf[i]); - puts("\n"); - } - - i2c_tx_cnt = 0; - i2c_rx_cnt = 0; - goto out_ack; + if (intf & BIT(4)) { + i2c_n = 0; + P0_I2CBINT &= ~(BIT(4) | BIT(6) | BIT(7)); + goto out_restore_page; } - // handle TX - if (P0_I2CBINT & BIT(7)) { - if (i2c_tx_cnt < I2C_N_REGS) - P0_I2CBDB = i2c_tx_buf[i2c_tx_cnt++]; - else - P0_I2CBDB = 0xff; + // handle TX (byte to be sent to master - this is timing sensitive!) + if (intf & BIT(7)) { + // previous TX was the last byte + if (!(cr1 & BIT(2))) + goto tx_ack; - goto out_ack; + P0_I2CBDB = reg_get_value(); + if (reg_addr != 0xff) + reg_addr++; + + i2c_n++; +tx_ack: + P0_I2CBINT &= ~BIT(7); + goto out_restore_page; } - // handle RX - if (P0_I2CBINT & BIT(6)) { - uint8_t empty = P0_I2CBCR1 & BIT(1); + // handle RX (byte received from master) + if (intf & BIT(6)) { uint8_t tmp = P0_I2CBDB; - if (empty && i2c_rx_cnt < I2C_N_REGS) - i2c_rx_buf[i2c_rx_cnt++] = tmp; + // first RX byte is device address, determined by !FULL flag + if (!(cr1 & BIT(1))) + goto rx_ack; - goto out_ack; + // set address + if (i2c_n++ == 0) { + reg_addr = tmp; + goto rx_ack; + } + + // set reg data + reg_set_value(tmp); + reg_addr++; + +rx_ack: + P0_I2CBINT &= ~BIT(6); + goto out_restore_page; } -out_ack: - P0_I2CBINT &= ~(BIT(4) | BIT(7) | BIT(6)); +out_restore_page: P0_I2CBCR1 &= ~BIT(7); // clear data pending PAGESW = saved_page; } @@ -470,7 +1189,7 @@ void i2c_slave_init(void) P0_I2CBCR2 = 0x07 << 1 | BIT(0); // 100kHz mode, enable I2C B controller, enable // setup I2C address - P0_I2CBDAH = 0x00; + P0_I2CBDAH = 0; P0_I2CBDAL = I2C_ADDR; P0_I2CBINT = BIT(5); // enable I2C B stop interrupt @@ -478,81 +1197,29 @@ void i2c_slave_init(void) } // }}} -// {{{ USB +// {{{ USB debugging interface -enum { - UDC_EP_CONTROL = 0, - UDC_EP_ISO, - UDC_EP_BULK, - UDC_EP_INTERRUPT, -}; +#if CONFIG_USB_STACK -#define UDC_EP_CONF(conf, intf, alt, type) \ - (conf << 6) | (intf << 4) | (alt << 2) | type -#define UDC_EP_OUT_CONF(ep1, ep2, ep3, ep4) \ - ep4 | (ep3 << 2) | (ep2 << 4) | (ep1 << 6) +#if USB_DEBUG -static const uint8_t udc_config[5] = { - UDC_EP_CONF(1, 0, 0, UDC_EP_INTERRUPT), - UDC_EP_CONF(1, 0, 0, UDC_EP_INTERRUPT), - UDC_EP_CONF(1, 0, 0, UDC_EP_INTERRUPT), - UDC_EP_CONF(1, 0, 0, UDC_EP_INTERRUPT), - UDC_EP_OUT_CONF(UDC_EP_INTERRUPT, UDC_EP_INTERRUPT, UDC_EP_INTERRUPT, UDC_EP_INTERRUPT), -}; +#define usb_putc(a) putc(a) +#define usb_puts(a) puts(a) +#define usb_put_hex_b(a) put_hex_b(a) +#define usb_put_hex_w(a) put_hex_w(a) +#define usb_put_hex_n(a) put_hex_n(a) +#define usb_put_uint(a) put_uint(a) -static void usb_disable(void) -{ - // reset phy/usb - PAGESW = 1; - P1_PHYTEST0 &= ~BIT(6); // phy disable - P1_UDCCTRL &= ~BIT(6); // udc disable -} +#else -static void usb_init(void) -{ - PAGESW = 1; - P1_UDCCTRL |= BIT(6); // udc enable - // wait for UDC to complete initialization - while (!(P1_UDCCTRL & BIT(1))); - __asm__("nop"); +#define usb_putc(a) +#define usb_puts(a) +#define usb_put_hex_b(a) +#define usb_put_hex_w(a) +#define usb_put_hex_n(a) +#define usb_put_uint(a) - // setup USB EP depths - P1_UDCEP1BUFDEPTH = 64 - 1; - P1_UDCEP2BUFDEPTH = 64 - 1; - P1_UDCEP3BUFDEPTH = 64 - 1; - P1_UDCEP4BUFDEPTH = 64 - 1; - __asm__("nop"); - __asm__("nop"); - - // configure UDC - for (uint8_t i = 0; i < 4; i++) { - P1_UDCCFDATA = udc_config[i]; - - while (!(P1_UDCCFSTA & BIT(7))); - while (P1_UDCCFSTA & BIT(7)); - } - - P1_UDCCFDATA = udc_config[4]; - while (!(P1_UDCCFSTA & BIT(6))); - - // enable USB - P1_USBCTRL |= BIT(6); - - P1_UDCINT0EN = 0; - P1_UDCINT1EN = 0; - P1_UDCINT2EN = 0; - P1_UDCEPCTRL = 0xf; - P1_UDCINT0STA = 0; - P1_UDCINT1STA = 0; - P1_UDCINT2STA = 0; - - // enable phy - P1_PHYTEST0 |= BIT(5) | BIT(6); - __asm__("nop"); - __asm__("nop"); - - PAGESW = 0; -} +#endif #define USB_ID(w) (uint16_t)w & 0xff, ((uint16_t)w >> 8) #define USB_BCD(a, b) b, a @@ -641,16 +1308,17 @@ static const uint8_t * const usb_strings[] = { usb_string_product, }; -static uint16_t usb_ep0_in_remaining; -static uint8_t const* usb_ep0_in_ptr; +static uint16_t usb_ep0_in_remaining = 0; +static uint8_t const* usb_ep0_in_ptr; static uint8_t usb_command_status = 0; -static uint8_t usb_key_change = 0; static uint8_t usb_command[8]; static uint8_t usb_response[8]; +static volatile __bit usb_key_change = 0; -static void usb_tasks(void) +static void usb_tasks(void) __using(1) { uint8_t buf[8]; + uint8_t saved_page = PAGESW; PAGESW = 1; @@ -671,10 +1339,7 @@ static void usb_tasks(void) //XXX: what about others? //XXX: reset software variables... - - EA = 0; - puts("usb reset int\n"); - EA = 1; + usb_puts("usb rst\n"); // ack reset request P1_UDCINT0STA &= ~BIT(5); @@ -682,12 +1347,21 @@ static void usb_tasks(void) // ep0 setup request received if (P1_UDCINT0STA & BIT(1)) { + usb_puts("ep0 su: "); + // buf: bReqType bReq wVal(l/h) wIndex wLength - for (uint8_t i = 0; i < 8; i++) + for (uint8_t i = 0; i < 8; i++) { buf[i] = P1_UDCEP0BUFDATA; + usb_put_hex_b(buf[i]); + } + + usb_puts("\n"); + + //P1_UDCEPBUF0CTRL |= BIT(0); + //P1_UDCEPBUF0CTRL &= ~BIT(0); // how much data to send to ep0 in - usb_ep0_in_remaining = (uint16_t)((buf[7] << 8) | buf[6]); + usb_ep0_in_remaining = (((uint16_t)buf[7] << 8) | buf[6]); uint16_t in0_len = 0; // standard commands @@ -732,38 +1406,46 @@ ack_ep0_setup: // USB host initiated EP0 IN transfer if (P1_UDCINT1STA & BIT(0)) { + // ack interrupt + P1_UDCINT1STA &= ~BIT(0); + // check if we're ready to send to ep0 - if (!(P1_UDCEPBUF0CTRL & BIT(1))) { + if (!(P1_UDCEPBUF0CTRL & BIT(1)) && (P1_UDCBUFSTA & BIT(0))) { // if ep0 in buffer not empty, clear it first - if (!(P1_UDCBUFSTA & BIT(0))) { + //if (!(P1_UDCBUFSTA & BIT(0))) { // clear ep0 buffer - P1_UDCEPBUF0CTRL |= BIT(0); - P1_UDCEPBUF0CTRL &= ~BIT(0); - } + //P1_UDCEPBUF0CTRL |= BIT(0); + //P1_UDCEPBUF0CTRL &= ~BIT(0); + //} + + usb_puts("ep0 in: ptr="); + usb_put_hex_w((uint16_t)usb_ep0_in_ptr); + usb_puts(" rem="); + usb_put_hex_w(usb_ep0_in_remaining); + usb_puts(" "); for (uint8_t n = 0; n < 64; n++) { - // push data to EP0 in (max 8 bytes) + // push data to EP0 in (max 64 bytes) if (usb_ep0_in_remaining > 0) { usb_ep0_in_remaining--; + usb_put_hex_b(*usb_ep0_in_ptr); P1_UDCEP0BUFDATA = *usb_ep0_in_ptr++; } else { break; } } + usb_puts("\n"); + // confirm sending data P1_UDCEPBUF0CTRL |= BIT(1); - // ack interrupt - P1_UDCINT1STA &= ~BIT(0); } } // data received on ep0 out if (P1_UDCINT1STA & BIT(1)) { + usb_puts("ep0 out\n"); // we don't handle any control transfers that send us data - EA = 0; - puts("usb EP0 OUT int\n"); - EA = 1; // reset ep0 buf P1_UDCEPBUF0CTRL |= BIT(0); @@ -786,6 +1468,7 @@ ack_ep0_setup: for (uint8_t i = 0; i < 8; i++) usb_command[i] = P1_UDCEP1BUFDATA; usb_command_status = 1; + usb_puts("ep1 out\n"); P1_UDCINT1STA &= ~BIT(3); @@ -807,10 +1490,7 @@ ack_ep0_setup: if (usb_command[0] == 0x01) { // bootloader mode - EA = 0; - __asm__("mov r6,#0x5a"); - __asm__("mov r7,#0xe7"); - __asm__("ljmp 0x0118"); + jump_to_usb_bootloader = 1; } else { // command unknown usb_response[1] = 1; @@ -838,20 +1518,25 @@ ack_ep0_setup: // USB host initiated EP3 IN transfer if (P1_UDCINT1STA & BIT(6)) { - // push printf debug buffer to ep3 in - if (!(P1_UDCEPBUF0CTRL & BIT(7)) && log_start != log_end) { - uint8_t cnt = 0; +#if CONFIG_DEBUG_LOG + // all log_* variables need to be accessed with interrupts + // disabled + __critical { + // push printf debug buffer to ep3 in + if (!(P1_UDCEPBUF0CTRL & BIT(7)) && log_start != log_end) { + uint8_t cnt = 0; - while (cnt < 64 && log_start != log_end) { - log_start = (log_start + 1) % 1024; - P1_UDCEP3BUFDATA = log_buffer[log_start]; // push data to fifo - cnt++; + while (cnt < 64 && log_start != log_end) { + log_start = (log_start + 1) % 1024; + P1_UDCEP3BUFDATA = log_buffer[log_start]; // push data to fifo + cnt++; + } + + P1_UDCEP3DATAINCNT = cnt - 1; + P1_UDCEPBUF0CTRL |= BIT(7); // EP3 data ready } - - P1_UDCEP3DATAINCNT = cnt - 1; - P1_UDCEPBUF0CTRL |= BIT(7); // EP3 data ready } - +#endif // ack P1_UDCINT1STA &= ~BIT(6); } @@ -861,7 +1546,7 @@ ack_ep0_setup: // push key change events to ep4 in if (!(P1_UDCEPBUF1CTRL & BIT(1)) && usb_key_change) { for (uint8_t i = 0; i < 12; i++) - P1_UDCEP4BUFDATA = i2c_tx_buf[i + 4]; + P1_UDCEP4BUFDATA = ro_regs[i + REG_KEYMATRIX_STATE]; P1_UDCEP4DATAINCNT = 12 - 1; P1_UDCEPBUF1CTRL |= BIT(1); // EP4 data ready @@ -874,24 +1559,123 @@ ack_ep0_setup: // suspend request if (P1_UDCINT0STA & BIT(6)) { - EA = 0; - puts("usb suspend int\n"); - EA = 1; + usb_puts("usb suspend\n"); + + // host requests suspend, we satisfy it + + // clear device resume request bit, we can set it later to wake + // the host / resume USB activity + P1_UDCCTRL &= ~BIT(5); // ack P1_UDCINT0STA &= ~BIT(6); - - //XXX: handle suspend properly - - // suspend UDC - P1_UDCCTRL &= ~BIT(5); } + + // resume request + if (P1_UDCINT0STA & BIT(3)) { + usb_puts("usb resume\n"); + + // ack + P1_UDCINT0STA &= ~BIT(3); + } + + PAGESW = saved_page; } +void usb_interrupt(void) __interrupt(IRQ_USB) __using(1) +{ + usb_tasks(); +} + +enum { + UDC_EP_CONTROL = 0, + UDC_EP_ISO, + UDC_EP_BULK, + UDC_EP_INTERRUPT, +}; + +#define UDC_EP_CONF(conf, intf, alt, type) \ + (conf << 6) | (intf << 4) | (alt << 2) | type +#define UDC_EP_OUT_CONF(ep1, ep2, ep3, ep4) \ + ep4 | (ep3 << 2) | (ep2 << 4) | (ep1 << 6) + +static const uint8_t udc_config[5] = { + UDC_EP_CONF(1, 0, 0, UDC_EP_INTERRUPT), + UDC_EP_CONF(1, 0, 0, UDC_EP_INTERRUPT), + UDC_EP_CONF(1, 0, 0, UDC_EP_INTERRUPT), + UDC_EP_CONF(1, 0, 0, UDC_EP_INTERRUPT), + UDC_EP_OUT_CONF(UDC_EP_INTERRUPT, UDC_EP_INTERRUPT, UDC_EP_INTERRUPT, UDC_EP_INTERRUPT), +}; + +static void usb_init(void) +{ + PAGESW = 1; + + P1_UDCCTRL |= BIT(6); // udc enable + // wait for UDC to complete initialization + while (!(P1_UDCCTRL & BIT(1))); + __asm__("nop"); + + // setup USB EP depths + P1_UDCEP1BUFDEPTH = 64 - 1; + P1_UDCEP2BUFDEPTH = 64 - 1; + P1_UDCEP3BUFDEPTH = 64 - 1; + P1_UDCEP4BUFDEPTH = 64 - 1; + __asm__("nop"); + __asm__("nop"); + + // configure UDC + for (uint8_t i = 0; i < 4; i++) { + P1_UDCCFDATA = udc_config[i]; + + while (!(P1_UDCCFSTA & BIT(7))); + while (P1_UDCCFSTA & BIT(7)); + } + + P1_UDCCFDATA = udc_config[4]; + while (!(P1_UDCCFSTA & BIT(6))); + + // enable USB EPRDY + P1_USBCTRL |= BIT(6); + + P1_UDCEPCTRL = 0xf; + P1_UDCINT0STA = 0; + P1_UDCINT1STA = 0; + P1_UDCINT2STA = 0; + + P1_UDCINT0EN = BIT(5) | BIT(1) | BIT(6) | BIT(3); + P1_UDCINT1EN = BIT(0) | BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(6); + P1_UDCINT2EN = BIT(2); + //P1_UDCINT0EN = 0; + //P1_UDCINT1EN = 0; + //P1_UDCINT2EN = 0; + + // enable phy, wakeup enable + P1_PHYTEST0 |= BIT(5) | BIT(6); + __asm__("nop"); + __asm__("nop"); + + PAGESW = 0; + + // enable USB interrupts + P0_EIE2 |= BIT(2); +} + +#endif + +static void usb_disable(void) +{ + // reset phy/usb + PAGESW = 1; + + P1_PHYTEST0 &= ~(BIT(6) | BIT(5)); // phy disable + P1_UDCCTRL &= ~BIT(6); // udc disable +} + +// }}} + void main(void) { - uint8_t scan_active = 0; - PAGESW = 0; // setup interrupts @@ -919,6 +1703,9 @@ void main(void) // enable both timers TCON = 0x50; + // protect FLASH from 0x0000 to 0x4000 from being accessed by code at 0x4000+ + P0_EPPOINTL = 0x80; + // setup watchdog (timer base is 8ms, prescaler sets up timeout /128 = ~1s) // P0_WDTCR = 0x87; // enable watchdog ~1s // P0_WDTKEY = 0x4e; // reset watchdog @@ -937,7 +1724,7 @@ void main(void) // enable pullups only all port 6 pins and make those pins into input PAGESW = 0; P0_PHCON0 = 0x00; - P0_PHCON1 = 0xff; // port 6 pull-up enable + P0_PHCON1 = 0xffu; // port 6 pull-up enable P0_P6M0 = 0xff; // port 6 input PAGESW = 1; P1_PHCON2 = 0x00; @@ -945,7 +1732,11 @@ void main(void) // enable auto-tuning internal RC oscillator based on USB SOF packets P1_IRCCTRL &= ~BIT(1); // disable manual trim - puts("ppkb firmware 0.1\n"); +#if CONFIG_STOCK_FW + puts("ppkb firmware " FW_REVISION_STR " (stock)\n"); +#else + puts("ppkb firmware " FW_REVISION_STR " (user)\n"); +#endif i2c_slave_init(); @@ -953,94 +1744,173 @@ void main(void) usb_disable(); +#if !CONFIG_USB_STACK + PAGESW = 1; + + // GPIO on USB pins + P1_USBCTRL &= ~BIT(7); + + // turn off PLL48 + P1_UDCCTRL |= BIT(0); + + // turn off unused USB resources (phy power down, PLL48 powerdown + P1_USBCTRL |= BIT(0) | BIT(1); + + // enable auto-tuning internal RC oscillator based on USB SOF packets + P1_IRCCTRL |= BIT(1); // enable manual trim +#endif + +#if CONFIG_FLASH_ENABLE + for (uint8_t i = 0; i < 128; i++) + flash_content[i] = 0; +#endif + // enable interrupts ET1 = 1; EA = 1; ext_int_deassert(); -#if POLL_INPUT - keyscan_active(); -#else keyscan_idle(); -#endif - uint8_t asserted = 0; - uint8_t usb_initialized = 0; + + __bit usb_initialized = 0; + __bit user_app_checked = 0; uint16_t ticks = 0; while (1) { - if (usb_initialized) - usb_tasks(); + // execute I2C system/flashing commands, once the I2C + // transaction ends, as soon as possible + if (i2c_n == 0) { + exec_system_command(); - if (!run_tasks) { - // power down (timers don't work in power-down) - //PCON |= BIT(1); - // go to idle CPU mode when there's nothing to do (doesn't help much) - // switching to LOSC may work better - //PCON |= BIT(0); +#if CONFIG_FLASH_ENABLE + exec_flashing_command(); +#endif + } + + // if we were asked to jump to USB IAP, do it + if (jump_to_usb_bootloader) + __asm__ ("ljmp _usb_bootloader_jump"); + + // if the 20ms timer did not expire yet, check if we can + // powerdown, otherwise busyloop + if (!run_timed_tasks) { +#if CONFIG_USB_STACK + PAGESW = 1; + __bit usb_suspended = !!(P1_UDCCTRL & BIT(2)); +#endif + PAGESW = 0; + __bit i2c_idle = !(P0_I2CBCR2 & BIT(7)) && i2c_n == 0; + + // if USB is suspended by host and I2C has no activity, + // and we're not in active scanning mode, power down the MCU + if (i2c_idle && !scan_active + && !p6_changed +#if CONFIG_USB_STACK + && usb_initialized && usb_suspended +#endif +#if CONFIG_STOCK_FW + && user_app_checked +#endif + ) { + // go to idle CPU mode when there's nothing to + // do, any interrupt will wake us + //PCON |= BIT(0); + + // enable interrupt whenever P6 is different + // from the current value (which would be + // whenever some key is pressed, because by + // default all pins on P6 are pulled high) + // + // input change detection works by comparing the + // pin state against the P6 latch for output + p6_changed = 0; + P6 = P6; + P0_ICEN = BIT(5); + ICIE = 1; + + // power down (timers don't work in power-down) + PCON |= BIT(1) | BIT(0); + __asm__("nop"); + + // we may not be woken up only by IC interrupt, so + // disable IC interrupts after each wakeup + ICIE = 0; + +#if CONFIG_USB_STACK + // if we were woken up by USB host, USBCTRL.5 + // will be set, clear it + PAGESW = 1; + if (!(P1_UDCCTRL & BIT(2))) + P1_USBCTRL &= ~BIT(5); +#endif + } - __asm__("nop"); continue; } + // every 20ms we will get here to perform some timed tasks ticks++; - run_tasks = 0; + run_timed_tasks = 0; - // usb init needs to run after 500ms - if (ticks > 500 / 20 && !usb_initialized) { +#if CONFIG_STOCK_FW + // after 1s check if we should jump to user firmware + if (!user_app_checked && ticks > 1000 / 20) { + if (app_flag == 1 && ctl_regs[REG_SYS_USER_APP_BLOCK - REG_SYS_CONFIG] != REG_SYS_USER_APP_BLOCK_MAGIC) + jmp_to_user_fw(); + + user_app_checked = 1; + } +#endif + +#if CONFIG_USB_STACK + // after 500ms, init usb + if (!usb_initialized && ticks > 500 / 20) { usb_init(); usb_initialized = 1; } - -#if POLL_INPUT - // every 20ms we will scan the keyboard keys state and check for changes - uint8_t keys[12]; - uint8_t active_rows = keyscan_scan(keys); - - // pressing FN+PINE+F switches to flashing mode (keys 1:2 3:5 5:2, electrically) - if (keys[0] & BIT(2) && keys[2] & BIT(5) && keys[4] & BIT(2)) { - EA = 0; - __asm__("mov r6,#0x5a"); - __asm__("mov r7,#0xe7"); - __asm__("ljmp 0x0118"); - } - - // check for changes - if (!memcmp(i2c_tx_buf + 4, keys, 12)) - continue; - - // signal interrupt - memcpy(i2c_tx_buf + 4, keys, 12); - ext_int_assert(); - delay_us(100); - ext_int_deassert(); - usb_key_change = 1; -#else - //XXX: not figured out yet, not tested, not working - if (scan_active) { - uint8_t active_rows = keyscan_scan(i2c_tx_buf + 4); - if (!active_rows) { - scan_active = 0; - keyscan_idle(); - - // power down - //PCON |= BIT(1); - //__asm__("nop"); - } - - // pressing FN+PINE+F switches to flashing mode (keys 1:2 3:5 5:2, electrically) - if (i2c_tx_buf[4 + 0] & BIT(2) && i2c_tx_buf[4 + 2] & BIT(5) && i2c_tx_buf[4 + 4] & BIT(2)) { - EA = 0; - __asm__("mov r6,#0x5a"); - __asm__("mov r7,#0xe7"); - __asm__("ljmp 0x0118"); - } - - continue; - } - - if (keyscan_idle_is_pressed()) { - scan_active = 1; - keyscan_active(); - } #endif + + // if active scanning is not active and port 6 change was + // detected, and some key is still pressed, enter active + // scanning mode + if (!scan_active && keyscan_idle_is_pressed()) + keyscan_active(); + + // if we're in active scanning, scan the keys, and report + // new state + if (scan_active) { + uint8_t keys[12]; + uint8_t active_rows = keyscan_scan(keys); + + // check for changes + if (memcmp(ro_regs + REG_KEYMATRIX_STATE, keys, 12)) { + // update regs + __critical { + memcpy(ro_regs + REG_KEYMATRIX_STATE, keys, 12); + ro_regs[REG_KEYMATRIX_STATE_CRC8] = crc8(ro_regs + REG_KEYMATRIX_STATE, 12); + } + + // signal interrupt + ext_int_assert(); + delay_us(100); + ext_int_deassert(); +#if CONFIG_USB_STACK + usb_key_change = 1; + + // USB wakeup + PAGESW = 1; + if (P1_UDCCTRL & BIT(2)) { + P1_UDCCTRL |= BIT(5); + P1_UDCCTRL &= ~BIT(5); + } +#endif + + // pressing FN+PINE+F switches to flashing mode (keys 1:2 3:5 5:2, electrically) + if (keys[0] & BIT(2) && keys[2] & BIT(5) && keys[4] & BIT(2)) + jump_to_usb_bootloader = 1; + } + + if (!active_rows) + keyscan_idle(); + } } } diff --git a/firmware/registers.h b/firmware/registers.h new file mode 100644 index 0000000..042cead --- /dev/null +++ b/firmware/registers.h @@ -0,0 +1,71 @@ +/** + * Pinephone Keyboard Firmware + * + * Copyright (C) 2021 Ondřej Jirman + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __PPKB_I2C_REGISTERS__ +#define __PPKB_I2C_REGISTERS__ + +// defines register API in BCD format (currently 1.0) +// on incompatible change this may need to be changed +#define FW_REVISION 0x10 + +#define REG_DEVID_K 0x00 +#define REG_DEVID_B 0x01 +#define REG_FW_REVISION 0x02 +#define REG_FW_FEATURES 0x03 +#define REG_FW_FEATURES_USB_DEBUGGER BIT(0) +#define REG_FW_FEATURES_FLASHING_MODE BIT(1) +#define REG_FW_FEATURES_SELF_TEST BIT(2) +#define REG_FW_FEATURES_STOCK_FW BIT(3) + +#define REG_KEYMATRIX_SIZE 0x06 +#define REG_KEYMATRIX_STATE_CRC8 0x07 +#define REG_KEYMATRIX_STATE 0x08 +#define REG_KEYMATRIX_STATE_END 0x13 + +#define REG_SYS_CONFIG 0x20 +#define REG_SYS_CONFIG_SCAN_BLOCK BIT(0) +#define REG_SYS_CONFIG_POLL_MODE BIT(1) +#define REG_SYS_CONFIG_USB_DEBUG_EN BIT(2) + +#define REG_SYS_COMMAND 0x21 +#define REG_SYS_COMMAND_MCU_RESET 'r' +#define REG_SYS_COMMAND_SELFTEST 't' +#define REG_SYS_COMMAND_USB_IAP 'i' + +#define REG_SYS_USER_APP_BLOCK 0x22 +#define REG_SYS_USER_APP_BLOCK_MAGIC 0x53 + +#define REG_FLASH_DATA_START 0x70 +#define REG_FLASH_DATA_END 0xef +#define REG_FLASH_ADDR_L 0xf0 +#define REG_FLASH_ADDR_H 0xf1 +#define REG_FLASH_CRC8 0xf2 + +#define REG_FLASH_UNLOCK 0xf3 +#define REG_FLASH_UNLOCK_MAGIC 0x46 + +#define REG_FLASH_CMD 0xf4 +#define REG_FLASH_CMD_READ_ROM 0x52 +#define REG_FLASH_CMD_WRITE_ROM 0x57 +#define REG_FLASH_CMD_ERASE_ROM 0x45 +#define REG_FLASH_CMD_COMMIT 0x43 + +#define REG_DEBUG_LOG 0xff + +#endif diff --git a/firmware/stock-ivt.asm b/firmware/stock-ivt.asm new file mode 100644 index 0000000..51a6acb --- /dev/null +++ b/firmware/stock-ivt.asm @@ -0,0 +1,101 @@ +/** + * Pinephone Keyboard Firmware + * + * Copyright (C) 2021 Ondřej Jirman + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "em85f684a.h" + + .area RSEG (ABS,DATA) + .org 0x0000 + ar0 = 0x00 + psw0 = 0xd0 + +.macro irq_user irq + . = (ivct_start + irq * 8 + 3) + ljmp (irq * 8 + 0x4003) + .ds 5 +.endm + +.macro irq_ignore irq + . = (ivct_start + irq * 8 + 3) + reti + .ds 7 +.endm + +.macro irq_stock irq, name + . = (ivct_start + irq * 8 + 3) + push psw0 + push ar0 + mov r0, #_stock_flag + ajmp name +.endm + +.macro irq_stock_fwd irq, name +name'_fwd: + cjne @r0, #1, 001$ + pop ar0 + pop psw0 + ljmp name +001$: + pop ar0 + pop psw0 + ljmp (irq * 8 + 0x4003) +.endm + + .module ivect + .area IVECT (REL) + + ivct_start = . + + ljmp __sdcc_gsinit_startup + + irq_ignore 5 + irq_ignore 9 + irq_ignore 13 + irq_ignore 19 + irq_ignore 21 + irq_ignore 22 + irq_ignore 25 + irq_ignore 26 + irq_ignore 27 + + irq_user IRQ_EINT0 + irq_user IRQ_TIMER0 + irq_user IRQ_EINT1 + irq_stock IRQ_TIMER1, _timer1_interrupt_fwd + irq_user IRQ_UART0 + irq_stock IRQ_PINCHANGE, _pinchange_interrupt_fwd + irq_user IRQ_LVD + irq_user IRQ_SYSTEMHOLD + irq_user IRQ_INT2_3 + irq_user IRQ_SPI + irq_user IRQ_PWMD + irq_user IRQ_PWME + irq_user IRQ_PWMF + irq_user IRQ_TIMER3 + irq_user IRQ_PWMA + irq_user IRQ_USB + irq_stock IRQ_USB _usb_interrupt_fwd + irq_user IRQ_I2CA + irq_user IRQ_PWMB + irq_user IRQ_PWMC + irq_stock IRQ_I2CB _i2c_b_interrupt_fwd + + irq_stock_fwd IRQ_TIMER1, _timer1_interrupt + irq_stock_fwd IRQ_PINCHANGE, _pinchange_interrupt + irq_stock_fwd IRQ_I2CB, _i2c_b_interrupt + irq_stock_fwd IRQ_USB, _usb_interrupt diff --git a/i2c-charger-ctl.c b/i2c-charger-ctl.c new file mode 100644 index 0000000..0d67081 --- /dev/null +++ b/i2c-charger-ctl.c @@ -0,0 +1,478 @@ +/* + * Pinephone keyboard power management daemon/tool. + * + * Copyright (C) 2021 Ondřej Jirman + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "common.c" + +/* + * - Independent control of + * - Boost (5V VOUT to power Pinephone) + * - Battery Charger + * + * - Optional automatic power on when load is inserted + * - Optional auto enable of VOUT when disconnecting VIN (reg 0x04) + * + * - Optiobal automatic shutdown when VOUT has light load (customizable via reg + * 0x0c, min. is 100mA, shutdown lastsa 8-64s (see reg 0x04)) + * + * - Charger_CTL1 0x22 + * - Control of charging current based on VOUT undervoltage (it tries + * to keep VOUT in a certain range by reducing load on VIN by + * decreasing charging current?) + * + * - Battery type selection 4.2/4.3/4.35V + * - + extra margin 0-42mV during constant voltage phase? + * - External (via VSET pin) or internal setting (via reg 0x24) + * + * - Charging current selection (100mA - 2.3A ?) + * + * - Charging status register + * - charging state - idle, trickle, constant voltage/current phase, full, + * timeout + * - LED heavey load indication + * - VIN overvoltage indication (> 5.6V) + * + * - Button press status + * - current state: UP/DOWN + * - long press + * - short press + * + * GPIO: + * + * - KEY input + * - Long press button time selection 1-4s + * - Enable/disable 2x short press shutdown function + * - L3/L4 function selection: + * - GPIO0/1 + * - normal function + * - LIGHT pin function selection: + * - GPIO2 + * - VREF + * - WLED + * - VSET + * - VSET (normal function to select battery voltage via PIN setting) + * - GPIO4 + * - RSET + * - GPIO3 + * - battery internal resistance selection via resistor on the RSET pin + * + * - separate input/output enable register for all 5 GPIOs + * - GPIO data register to read/write values to pins + * + * ADC: + * + * - 14 bit two register VBAT, IBAT, VBAT_OCV readings +*/ + +#define SYS_CTL0 0x01 +#define SYS_CTL1 0x02 +#define SYS_CTL2 0x0c +#define SYS_CTL3 0x03 +#define SYS_CTL4 0x04 +#define SYS_CTL5 0x07 + +#define Charger_CTL1 0x22 +#define Charger_CTL2 0x24 +#define CHG_DIG_CTL4 0x25 +#define CHG_DIG_CTL4_2 0x26 + +#define READ0 0x71 +#define READ1 0x72 +#define READ2 0x77 + +// bits 4-0 are mapped to gpio4 - gpio0 +#define MFP_CTL0 0x51 +#define MFP_CTL1 0x52 +#define GPIO_INEN 0x53 +#define GPIO_OUTEN 0x54 +#define GPIO_DATA 0x55 + +#define BATVADC_DAT_L 0xa2 +#define BATVADC_DAT_H 0xa3 +#define BATIADC_DAT_L 0xa4 +#define BATIADC_DAT_H 0xa5 +#define BATOCV_DAT_L 0xa8 +#define BATOCV_DAT_H 0xa9 + +uint8_t read_power(int fd, uint8_t reg) +{ + int ret; + uint8_t val; + struct i2c_msg msgs[] = { + { POWER_ADDR, 0, 1, ® }, // address + { POWER_ADDR, I2C_M_RD, 1, &val }, + }; + + struct i2c_rdwr_ioctl_data msg = { + .msgs = msgs, + .nmsgs = sizeof(msgs) / sizeof(msgs[0]) + }; + + ret = ioctl(fd, I2C_RDWR, &msg); + syscall_error(ret < 0, "I2C_RDWR failed"); + + return val; +} + +void write_power(int fd, uint8_t reg, uint8_t val) +{ + int ret; + uint8_t buf[] = {reg, val}; + + struct i2c_msg msgs[] = { + { POWER_ADDR, 0, 2, buf }, + }; + + debug("wr 0x%02hhx: %02hhx\n", reg, val); + + struct i2c_rdwr_ioctl_data msg = { + .msgs = msgs, + .nmsgs = sizeof(msgs) / sizeof(msgs[0]) + }; + + ret = ioctl(fd, I2C_RDWR, &msg); + syscall_error(ret < 0, "I2C_RDWR failed"); +} + +void update_power(int fd, uint8_t reg, uint8_t mask, uint8_t val) +{ + uint8_t tmp; + + tmp = read_power(fd, reg); + tmp &= ~mask; + tmp |= val & mask; + write_power(fd, reg, tmp); +} + +// in mV +unsigned get_bat_voltage(int fd) +{ + unsigned l = read_power(fd, BATVADC_DAT_L); + unsigned h = read_power(fd, BATVADC_DAT_H); + + if (h & 0x20) + return 2600 - ((~l & 0xff) + ((~h & 0x1f) << 8) + 1) * 1000 / 3724; + + return 2600 + (l + (h << 8)) * 1000 / 3724; +} + +int get_bat_current(int fd) +{ + unsigned l = read_power(fd, BATIADC_DAT_L); + unsigned h = read_power(fd, BATIADC_DAT_H); + + if (h & 0x20) + return - (int)((~l & 0xff) + ((~h & 0x1f) << 8) + 1) * 1000 / 1341; + + return (l + (h << 8)) * 1000 / 1341; +} + +unsigned get_bat_oc_voltage(int fd) +{ + unsigned l = read_power(fd, BATOCV_DAT_L); + unsigned h = read_power(fd, BATOCV_DAT_H); + + if (h & 0x20) + return 2600 - ((~l & 0xff) + ((~h & 0x1f) << 8) + 1) * 1000 / 3724; + + return 2600 + (l + (h << 8)) * 1000 / 3724; +} + +const char* get_chg_status_text(uint8_t s) +{ + switch (s) { + case 0: return "Idle"; + case 1: return "Trickle charge"; + case 2: return "Constant current phase"; + case 3: return "Constant voltage phase"; + case 4: return "Constant voltage stop"; + case 5: return "Full"; + case 6: return "Timeout"; + default: return "Unknown"; + } +} + +void power_status(int fd) +{ + uint8_t r0 = read_power(fd, READ0); + uint8_t r1 = read_power(fd, READ1); + uint8_t r2 = read_power(fd, READ2); + uint8_t s0 = read_power(fd, SYS_CTL0); + + printf("Charger: %s (%s%s%s%s%s%s%s)\n", get_chg_status_text((r0 >> 5) & 0x7), + r0 & BIT(4) ? " chg_op" : "", + r0 & BIT(3) ? " chg_end" : "", + r0 & BIT(2) ? " cv_timeout" : "", + r0 & BIT(1) ? " chg_timeout" : "", + r0 & BIT(0) ? " trickle_timeout" : "", + r1 & BIT(6) ? " VIN overvoltage" : "", + r1 & BIT(5) ? " <= 75mA load" : "" + ); + + printf("Button: %02hhx (%s%s%s%s)\n", r2, + r2 & BIT(3) ? " btn_press" : " btn_not_press", + r2 & BIT(2) ? " double_press" : "", + r2 & BIT(1) ? " long_press" : "", + r2 & BIT(0) ? " short_press" : "" + ); + + // this has some nice undocummneted status bits + printf("0x70: %02hhx\n", read_power(fd, 0x70)); + + update_power(fd, READ2, 0x7, 0x7); +} + +// dump registers + +struct bitinfo { + const char* name; + uint8_t shift; + uint8_t len; + void (*fmt)(char* out, size_t out_size, uint8_t val); +}; + +struct reginfo { + uint8_t reg; + const char* name; + struct bitinfo* bits; +}; + +#define REG_START(addr, name) [addr] = { addr, name, (struct bitinfo[]){ +#define REG_END {} }}, +#define REG_BITS(name, s, l) { #name, s, l }, +#define REG(addr, name) REG_START(addr, name) { name, 0, 8 }, REG_END +#define REG_SIMPLE(n) REG(n, #n) + +struct reginfo regs[256] = { + REG_START(SYS_CTL0, "SYS_CTL0") + REG_BITS(CHARGER_EN, 1, 1) + REG_BITS(BOOST_EN, 2, 1) + REG_BITS(LIGHT_EN, 3, 1) + REG_BITS(FLASHLIGHT_DET_EN, 4, 1) + REG_END + REG_START(SYS_CTL1, "SYS_CTL1") + REG_BITS(AUTO_POWERON_ON_VIN_INSERT_EN, 0, 1) + REG_BITS(LIGHT_LOAD_AUTO_SHUTDOWN_EN, 1, 1) + REG_END + REG_START(SYS_CTL2, "SYS_CTL2") + REG_BITS(LIGHT_SHUTDOWN_CURRENT, 3, 5) + REG_END + REG_START(SYS_CTL3, "SYS_CTL3") + REG_BITS(DOUBLE_PRESS_SHUTDOWN_EN, 5, 1) + REG_BITS(LONG_PRESS_TIME, 6, 2) + REG_END + REG_START(SYS_CTL4, "SYS_CTL4") + REG_BITS(SHUTDOWN_TIME, 6, 2) + REG_BITS(VIN_PULLOUT_BOOST_ON, 5, 1) + REG_END + REG_START(SYS_CTL5, "SYS_CTL5") + REG_BITS(NTC_EN, 6, 1) + REG_BITS(FLASH_LED_EN_0_LONG_PRESS_1_DOUBLE_PRESS, 1, 1) + REG_BITS(SHUTDOWN_1_LONG_PRESS_0_DOUBLE_PRESS, 0, 1) + REG_END + REG_START(Charger_CTL1, "Charger_CTL1") + REG_BITS(UV_LOOP, 2, 2) + REG_END + REG_START(Charger_CTL2, "Charger_CTL2") + REG_BITS(BAT_TYPE, 5, 2) + REG_BITS(CV_PRESSURE, 1, 2) + REG_END + REG_START(CHG_DIG_CTL4_2, "CHG_DIG_CTL4_2") + REG_BITS(BAT_TYPE_SEL_1_VSET_PIN_0_REGISTER, 6, 1) + REG_END + REG_START(CHG_DIG_CTL4, "CHG_DIG_CTL4") + REG_BITS(CHG_CURRENT, 0, 5) + REG_END + REG_SIMPLE(MFP_CTL0) + REG_SIMPLE(MFP_CTL1) + REG_SIMPLE(GPIO_INEN) + REG_SIMPLE(GPIO_OUTEN) + REG_SIMPLE(GPIO_DATA) + REG_SIMPLE(BATVADC_DAT_L) + REG_SIMPLE(BATVADC_DAT_H) + REG_SIMPLE(BATOCV_DAT_L) + REG_SIMPLE(BATOCV_DAT_H) + REG_SIMPLE(BATIADC_DAT_L) + REG_SIMPLE(BATIADC_DAT_H) + + REG_START(0x70, "READ_70") +// REG_BITS(DISCHARGING, 2, 1) + REG_BITS(VOUT_BOOST, 2, 1) + REG_BITS(CHARGING, 3, 1) + REG_BITS(VIN_INSERTED, 4, 1) + REG_BITS(VIN_NOT_INSERTED, 5, 1) + REG_END + + REG_SIMPLE(READ0) + REG_SIMPLE(READ1) + REG_SIMPLE(READ2) +}; + +static void dump_regs(int fd) +{ + for (int addr = 0; addr <= 0xff; addr++) { + struct reginfo* ri = ®s[addr]; + + uint8_t val = read_power(fd, addr); + if (val == 0 && !ri->name) + continue; + + printf("%02x: %02hhx", addr, val); + if (ri->name) { + printf(" (%s)", ri->name); + + for (int i = 0; ri->bits[i].name; i++) { + struct bitinfo* bi = &ri->bits[i]; + uint8_t bval = (val >> bi->shift) & (((1u) << (bi->len)) - 1); + + printf(" %s=0x%02hhx", bi->name, bval); + } + } + printf("\n"); + } +} + +static void usage(void) +{ + printf( + "Usage: ppkb-charger-ctl [--verbose] [--help]\n" + " [...]\n" + "\n" + "Options:\n" + " -c, --current Change the charging current (mA).\n" + " -v, --verbose Show details of what's going on.\n" + " -h, --help This help.\n" + "\n" + "Commands:\n" + " info Display information about the current state of the charger chip.\n" + " power-on Power on VOUT (boost output to the phone and keyboard).\n" + " power-off Power off VOUT.\n" + " charger-on Start charging the battery.\n" + " charger-off Stop charging the battery.\n" + " auto Switch to automatic control of VOUT/Charging (default configuration).\n" + " dump Dump charger chip registers.\n" + "\n" + "Pinephone keyboard charger control tool " VERSION "\n" + "Written by Ondrej Jirman , 2021\n" + "Licensed under GPLv3, see https://xff.cz/git/pinephone-keyboard/ for\n" + "more information.\n" + ); + + exit(2); +} + +int main(int ac, char* av[]) +{ + int fd, ret; + int current = -1; + + while (1) { + int option_index = 0; + struct option long_options[] = { + { "current", required_argument, 0, 'c' }, + { "verbose", no_argument, 0, 'v' }, + { "help", no_argument, 0, 'h' }, + { 0, 0, 0, 0 } + }; + + int c = getopt_long(ac, av, "c:vh", long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'c': + errno = 0; + char* next = NULL; + current = strtol(optarg, &next, 10); + if (errno || next == optarg) { + printf("ERROR: Can't parse --current %s\n\n", optarg); + usage(); + } + break; + case 'v': + verbose = 1; + break; + case 'h': + case '?': + default: + usage(); + break; + } + } + + if (optind == ac) + usage(); + + if (current > 2000) { + printf("ERROR: --current %d too big\n\n", current); + usage(); + } + + if (current != -1 && current < 100) { + printf("ERROR: --current %d too small\n\n", current); + usage(); + } + + fd = pogo_i2c_open(); + + if (current != -1) { + //update_power(fd, SYS_CTL0, BIT(2), BIT(2)); + } + + int lfd = gpio_setup_pl12(GPIO_V2_LINE_FLAG_INPUT | GPIO_V2_LINE_FLAG_BIAS_PULL_UP | /*GPIO_V2_LINE_FLAG_ACTIVE_HIGH |*/ GPIO_V2_LINE_FLAG_EDGE_FALLING); + +// update_power(fd, SYS_CTL1, 0x03, 0x00); // disable automatic control based on load detection +// update_power(fd, SYS_CTL0, 0x1e, BIT(1) | BIT(2)); // 2=boost 1=charger enable +// update_power(fd, SYS_CTL3, BIT(5), 0); // disable "2x key press = shutdown" function +// update_power(fd, SYS_CTL4, BIT(5), 0); // disable "VIN pull out -> VOUT auto-enable" function +// update_power(fd, CHG_DIG_CTL4, 0x1f, 15); // set charging current (in 100mA steps) + + for (int i = optind; i < ac; i++) { + if (!strcmp(av[i], "power-on")) { + update_power(fd, SYS_CTL0, BIT(2), BIT(2)); + } else if (!strcmp(av[i], "power-off")) { + update_power(fd, SYS_CTL0, BIT(2), 0); + update_power(fd, SYS_CTL1, 0x03, 0x00); // disable automatic control based on load detection + update_power(fd, SYS_CTL4, BIT(5), 0); // disable "VIN pull out -> VOUT auto-enable" function + } else if (!strcmp(av[i], "charger-on")) { + update_power(fd, SYS_CTL0, BIT(1), BIT(1)); + } else if (!strcmp(av[i], "charger-off")) { + update_power(fd, SYS_CTL0, BIT(1), 0); + } else if (!strcmp(av[i], "info")) { + power_status(fd); + printf("V=%u mV (OCV %u mV) I=%d mA\n", + get_bat_voltage(fd), + get_bat_oc_voltage(fd), + get_bat_current(fd)); + } else if (!strcmp(av[i], "dump")) { + dump_regs(fd); + } else if (!strcmp(av[i], "auto")) { + // enable automatic control based on load detection + update_power(fd, SYS_CTL1, 0x03, 0x03); + // disable "2x key press = shutdown" function + update_power(fd, SYS_CTL3, BIT(5), BIT(5)); + // disable "VIN pull out -> VOUT auto-enable" function + update_power(fd, SYS_CTL4, BIT(5), BIT(5)); + } else { + printf("ERROR: Unknown command: %s\n\n", av[i]); + usage(); + } + } + + return 0; +} diff --git a/i2c-debugger.c b/i2c-debugger.c new file mode 100644 index 0000000..4f4fbcc --- /dev/null +++ b/i2c-debugger.c @@ -0,0 +1,60 @@ +/* + * Pinephone keyboard I2C debugging tool. + * + * Copyright (C) 2021 Ondřej Jirman + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "common.c" +#include "firmware/registers.h" + +void dump_log(int fd) +{ + int ret; + uint8_t addr = REG_DEBUG_LOG; + uint8_t buf[64]; + struct i2c_msg msgs[] = { + { KB_ADDR, 0, 1, &addr }, // set 0xff address + { KB_ADDR, I2C_M_RD, sizeof(buf), buf }, + }; + + struct i2c_rdwr_ioctl_data msg = { + .msgs = msgs, + .nmsgs = sizeof(msgs) / sizeof(msgs[0]) + }; + + ret = ioctl(fd, I2C_RDWR, &msg); + syscall_error(ret < 0, "I2C_RDWR failed"); + + int i; + for (i = 0; i < sizeof(buf) && buf[i]; i++); + + if (i > 0) + xwrite(1, buf, i); +} + +int main(int ac, char* av[]) +{ + int fd, ret; + + fd = pogo_i2c_open(); + + while (1) { + dump_log(fd); + usleep(10000); + } + + return 0; +} diff --git a/i2c-flasher.c b/i2c-flasher.c new file mode 100644 index 0000000..0422884 --- /dev/null +++ b/i2c-flasher.c @@ -0,0 +1,509 @@ +/* + * Pinephone keyboard I2C flashing tool. + * + * Copyright (C) 2021 Ondřej Jirman + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "common.c" +#include "firmware/registers.h" + +static int iic_fd = -1; + +static void dump_log(void) +{ + int ret; + uint8_t addr = 0xff; + uint8_t buf[64]; + struct i2c_msg msgs[] = { + { KB_ADDR, 0, 1, &addr }, // set 0xff address + { KB_ADDR, I2C_M_RD, sizeof(buf), buf }, + }; + + struct i2c_rdwr_ioctl_data msg = { + .msgs = msgs, + .nmsgs = sizeof(msgs) / sizeof(msgs[0]) + }; + + ret = ioctl(iic_fd, I2C_RDWR, &msg); + syscall_error(ret < 0, "I2C_RDWR failed"); + + int i; + for (i = 0; i < sizeof(buf) && buf[i]; i++); + + if (i > 0) + xwrite(1, buf, i); +} + +static void wr_buf(uint8_t addr, uint8_t* buf, size_t size) +{ + int ret; + uint8_t mbuf[size + 1]; + + mbuf[0] = addr; + memcpy(mbuf + 1, buf, size); + + struct i2c_msg msgs[] = { + { KB_ADDR, 0, size + 1, mbuf }, + }; + + struct i2c_rdwr_ioctl_data msg = { + .msgs = msgs, + .nmsgs = sizeof(msgs) / sizeof(msgs[0]) + }; + + debug("WR[%02hhx]:", addr); + for (int i = 0; i < size; i++) + debug(" %02hhx", buf[i]); + debug("\n"); + + ret = ioctl(iic_fd, I2C_RDWR, &msg); + syscall_error(ret < 0, "I2C_RDWR failed"); +} + +static void rd_buf(uint8_t addr, uint8_t* buf, size_t size) +{ + int ret; + struct i2c_msg msgs[] = { + { KB_ADDR, 0, 1, &addr }, + { KB_ADDR, I2C_M_RD, size, buf }, + }; + + struct i2c_rdwr_ioctl_data msg = { + .msgs = msgs, + .nmsgs = sizeof(msgs) / sizeof(msgs[0]) + }; + + ret = ioctl(iic_fd, I2C_RDWR, &msg); + syscall_error(ret < 0, "I2C_RDWR failed"); + + debug("RD[%02hhx]:", addr); + for (int i = 0; i < size; i++) { + if (i % 8 == 0 && i > 0) + debug("\n "); + debug(" %02hhx", buf[i]); + } + debug("\n"); +} + +static int rd_buf_nofail(uint8_t addr, uint8_t* buf, size_t size) +{ + int ret; + struct i2c_msg msgs[] = { + { KB_ADDR, 0, 1, &addr }, + { KB_ADDR, I2C_M_RD, size, buf }, + }; + + struct i2c_rdwr_ioctl_data msg = { + .msgs = msgs, + .nmsgs = sizeof(msgs) / sizeof(msgs[0]) + }; + + ret = ioctl(iic_fd, I2C_RDWR, &msg); + + if (ret == 2) { + debug("RD[%02hhx]:", addr); + for (int i = 0; i < size; i++) { + if (i % 8 == 0 && i > 0) + debug("\n "); + debug(" %02hhx", buf[i]); + } + debug("\n"); + } + + return ret == 2 ? 0 : -1; +} + +static uint8_t rd_reg(uint8_t addr) +{ + uint8_t reg; + rd_buf(addr, ®, 1); + return reg; +} + +static int wait_flash_cmd_done(uint8_t cmd) +{ + int ret; + + for (int i = 0; i < 10; i++) { + uint8_t status; + + ret = rd_buf_nofail(REG_FLASH_CMD, &status, 1); + if (ret == 0) { + if (status == 0xffu) + error("Flashing command 0x%02hhx failed", cmd); + else if (status == 0x00) + return 0; + } + + usleep(5000); + } + + error("Flashing command 0x%02hhx timed out", cmd); +} + +static void read_rom_block(uint8_t* out, uint16_t addr) +{ + uint8_t read_rom_start[] = { + addr & 0xff, // Addr L + (addr >> 8) & 0xff, // Addr H + 0x00, // CRC-8 + REG_FLASH_UNLOCK_MAGIC, // Unlock + REG_FLASH_CMD_READ_ROM, // Read ROM + }; + + wr_buf(REG_FLASH_ADDR_L, read_rom_start, sizeof read_rom_start); + + wait_flash_cmd_done(REG_FLASH_CMD_READ_ROM); + + rd_buf(REG_FLASH_DATA_START, out, 128); + + if (rd_reg(REG_FLASH_CRC8) != crc8(out, 128)) + error("CRC8 failure on ROM read"); +} + +static void write_rom_block(uint16_t addr, uint8_t* data) +{ + uint8_t write_rom_start[5] = { + addr & 0xff, // Addr L + (addr >> 8) & 0xff, // Addr H + crc8(data, 128), // CRC-8 + REG_FLASH_UNLOCK_MAGIC, // Unlock + REG_FLASH_CMD_WRITE_ROM, // Write ROM + }; + + wr_buf(REG_FLASH_DATA_START, data, 128); + wr_buf(REG_FLASH_ADDR_L, write_rom_start, sizeof write_rom_start); + + usleep(5000); + wait_flash_cmd_done(REG_FLASH_CMD_WRITE_ROM); +} + +static void run_flash_cmd(uint8_t cmd) +{ + uint8_t cmd_data[] = { + REG_FLASH_UNLOCK_MAGIC, // Unlock + cmd, // Command + }; + + wr_buf(REG_FLASH_UNLOCK, cmd_data, sizeof cmd_data); + + usleep(10000); + wait_flash_cmd_done(cmd); +} + +static bool is_block_empty(uint8_t* buf) +{ + for (unsigned i = 0; i < 128; i++) + if (buf[i] != 0xff) + return false; + + return true; +} + +static int is_kb_stock_connected(void) +{ + uint8_t devid[5]; + int ret; + + ret = rd_buf_nofail(REG_DEVID_K, devid, sizeof devid); + if (ret) + return 0; + + if (devid[REG_DEVID_K] != 'K' || devid[REG_DEVID_B] != 'B') // keyboard firmware magic + return 0; + if (!(devid[REG_FW_FEATURES] & REG_FW_FEATURES_STOCK_FW)) // stock firmware flag + return 0; + + if (devid[REG_FW_REVISION] != 0x10) + error("Unsupported stock pinephone keyboard firmware version %02hhx, expecting 0x10\n", devid[2]); + if (!(devid[REG_FW_FEATURES] & REG_FW_FEATURES_FLASHING_MODE)) + error("Your stock pinephone keyboard firmware doesn't have flashing support\n"); + + return 1; +} + +static void usage(void) +{ + printf( + "Usage: ppkb-i2c-flasher [--rom-in ] [--rom-out ] [--verbose]\n" + " [--help] [...]\n" + "\n" + "Options:\n" + " -i, --rom-in Specify path to binary file you want to flash.\n" + " -o, --rom-out Specify path where you want to store the contents\n" + " of code ROM read from the device.\n" + " -s, --size Specify how many bytes of code rom to flash\n" + " starting from offset 0x4000 in the rom file.\n" + " -e, --entry \n" + " Specify how to enter the stock firmware:\n" + " - manual: Ask the user to power-cycle the keyboard\n" + " - i2c: Send I2C command to make supporting user\n" + " - none: Assume stock firmware is already running\n" + " -v, --verbose Show details of what's going on.\n" + " -h, --help This help.\n" + "\n" + "Commands:\n" + " info Display information about the current firmware.\n" + " read Read ROM from the device to --rom-out file.\n" + " write Flash ROM file to the device from --rom-in.\n" + " erase Erase the user firmware.\n" + " reset Perform software reset of the MCU.\n" + " usbiap Restart to USB IAP mode.\n" + "\n" + "Format of the ROM files is a flat binary. Only the part of it starting\n" + "from 0x4000 will be flashed. Use -s to specify how many bytes to write.\n" + "The stock firmware between 0x2000 and 0x4000 will be preserved.\n" + "\n" + "Pinephone keyboard I2C flashing tool " VERSION "\n" + "Written by Ondrej Jirman , 2021\n" + "Licensed under GPLv3, see https://xff.cz/git/pinephone-keyboard/ for\n" + "more information.\n" + ); + + exit(2); +} + +int main(int ac, char* av[]) +{ + char* rom_in = NULL; + char* rom_out = NULL; + char* entry_type = "i2c"; + int size = 0x1200; + int ret; + + while (1) { + int option_index = 0; + struct option long_options[] = { + { "rom-in", required_argument, 0, 'i' }, + { "rom-out", required_argument, 0, 'o' }, + { "size", required_argument, 0, 's' }, + { "entry", required_argument, 0, 'e' }, + { "verbose", no_argument, 0, 'v' }, + { "help", no_argument, 0, 'h' }, + { 0, 0, 0, 0 } + }; + + int c = getopt_long(ac, av, "i:o:s:e:vh", long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'o': + rom_out = strdup(optarg); + break; + case 'i': + rom_in = strdup(optarg); + break; + case 'e': + entry_type = strdup(optarg); + break; + case 's': + if (strstr(optarg, "0x") == optarg) { + errno = 0; + char* next = NULL; + size = strtol(optarg + 2, &next, 16); + if (errno || next == optarg + 2) { + printf("ERROR: Can't parse --size %s\n\n", optarg); + usage(); + } + } else { + errno = 0; + char* next = NULL; + size = strtol(optarg, &next, 10); + if (errno || next == optarg) { + printf("ERROR: Can't parse --size %s\n\n", optarg); + usage(); + } + } + break; + case 'v': + verbose = 1; + break; + case 'h': + case '?': + default: + usage(); + break; + } + } + + if (optind == ac) + usage(); + + if (size < 128) { + printf("ERROR: --size 0x%04x too small\n\n", size); + usage(); + } + + if (size > 0x4000) { + printf("ERROR: --size 0x%04x too large\n\n", size); + usage(); + } + + if (size % 128 != 0) { + printf("ERROR: --size 0x%04x is not multiple of 128\n\n", size); + usage(); + } + + for (int i = optind; i < ac; i++) { + if (!strcmp(av[i], "read")) { + if (!rom_out) { + printf("ERROR: You must specify target file to write rom contents to via --rom-out\n\n"); + usage(); + } + } else if (!strcmp(av[i], "write")) { + if (!rom_in) { + printf("ERROR: You must source file for flashing via --rom-in\n\n"); + usage(); + } + } else if (!strcmp(av[i], "info")) { + ; + } else if (!strcmp(av[i], "reset")) { + ; + } else if (!strcmp(av[i], "erase")) { + ; + } else if (!strcmp(av[i], "usbiap")) { + ; + } else { + printf("ERROR: Unknown command: %s\n\n", av[i]); + usage(); + } + } + + printf("Opening keyboard I2C device\n"); + iic_fd = pogo_i2c_open(); + + if (!is_kb_stock_connected()) { + if (!strcmp(entry_type, "i2c")) { + // send MCU reset command + uint8_t cmd[] = {REG_SYS_COMMAND_MCU_RESET}; + wr_buf(REG_SYS_COMMAND, cmd, sizeof cmd); + + // stock firmware should report itself quickly + // + // tell firmware to block enrty to user app (we have 1s + // window to do this after reset) + int i; + for (i = 0; i < 10; i++) { + if (is_kb_stock_connected()) { + uint8_t cmd[] = {REG_SYS_USER_APP_BLOCK_MAGIC}; + wr_buf(REG_SYS_USER_APP_BLOCK, cmd, sizeof cmd); + break; + } + + usleep(250000); + } + + if (i == 10) + error("Reset command issued over I2C failed, stock firmware failed to report itself within 2.5s"); + } else if (!strcmp(entry_type, "manual")) { + printf("Please power off the keyboard by holding the keyboard power key for > 12s, then release the power key and press it shortly, once, to power it on again.\n"); + + while (true) { + if (is_kb_stock_connected()) { + uint8_t cmd[] = {REG_SYS_USER_APP_BLOCK_MAGIC}; + wr_buf(REG_SYS_USER_APP_BLOCK, cmd, sizeof cmd); + break; + } + + usleep(250000); + } + } else if (!strcmp(entry_type, "none")) { + error("Stock pinephone keyboard firmware not detected running on the keyboard\n"); + } else { + error("Unknown entry method %s", entry_type); + } + + // if after 1s the stock firmware is still running, + // everything is ok + usleep(1000000); + if (!is_kb_stock_connected()) + error("Failed to block the user app from running"); + } + + for (int i = optind; i < ac; i++) { + if (!strcmp(av[i], "read")) { + printf("Reading code ROM\n"); + uint8_t rom[0x8000]; + for (unsigned i = 0; i < sizeof(rom); i += 128) + read_rom_block(rom + i, i); + + int fd = open(rom_out, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (fd >= 0) { + ssize_t wr = write(fd, rom, sizeof rom); + syscall_error(wr != sizeof(rom), "write failed"); + close(fd); + } + } else if (!strcmp(av[i], "write")) { + int fd; + + uint8_t rom[0x8000]; + memset(rom, 0xff, sizeof rom); + + fd = open(rom_in, O_RDONLY); + syscall_error(fd < 0, "open(%s) failed", rom_in); + ssize_t len = read(fd, rom, 0x8000); + syscall_error(len < 0, "read failed"); + close(fd); + if (len != 0x8000) + error("Invalid ROM file (%s) size (%d), must be 32768 bytes", rom_in, (int)len); + + printf("Flashing code ROM\n"); + for (unsigned i = 0x4000; i < 0x4000 + size; i += 128) + write_rom_block(i, rom + i); + + uint8_t rd_rom[0x8000]; + for (unsigned i = 0x4000; i < 0x4000 + size; i += 128) { + read_rom_block(rd_rom + i, i); + + if (memcmp(rd_rom + i, rom + i, 128)) { + printf("WARNING: Block 0x%04x write failed, retrying...\n"); + error("Retries disabled"); + } + } + + printf("Finishing flashing\n"); + run_flash_cmd(REG_FLASH_CMD_COMMIT); + } else if (!strcmp(av[i], "info")) { + uint8_t devid[5]; + + rd_buf(0x00, devid, sizeof devid); + + printf("DEVID Register dump:\n"); + for (int i = 0; i < sizeof(devid); i++) + printf("0x%02x: 0x%02hhx\n", i, devid[i]); + } else if (!strcmp(av[i], "reset")) { + printf("Restarting the MCU\n"); + + // send MCU reset command + uint8_t cmd[] = {REG_SYS_COMMAND_MCU_RESET}; + wr_buf(REG_SYS_COMMAND, cmd, sizeof cmd); + } else if (!strcmp(av[i], "usbiap")) { + printf("Restarting to USB IAP mode, if you don't have USB interface soldered on, you'll have to power-cycle the keyboard to get out of this flashing mode.\n"); + + // send MCU reset command + uint8_t cmd[] = {REG_SYS_COMMAND_USB_IAP}; + wr_buf(REG_SYS_COMMAND, cmd, sizeof cmd); + } else if (!strcmp(av[i], "erase")) { + run_flash_cmd(REG_FLASH_CMD_ERASE_ROM); + } else { + printf("ERROR: Unknown command: %s\n\n", av[i]); + usage(); + } + } + + return 0; +} diff --git a/inputd/main.c b/i2c-inputd.c similarity index 65% rename from inputd/main.c rename to i2c-inputd.c index 25f17e4..8bf0b2e 100644 --- a/inputd/main.c +++ b/i2c-inputd.c @@ -19,100 +19,20 @@ // {{{ includes -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "common.c" +#include "firmware/registers.h" -#include -#include -#include #include #include -#define DEBUG 0 -#define MEGI_PROTO_BUG 0 - -#if DEBUG -#define debug(args...) printf(args) -#else -#define debug(args...) -#endif - -// }}} -// {{{ utils - -static void syscall_error(int is_err, const char* fmt, ...) -{ - va_list ap; - - if (!is_err) - return; - - fprintf(stderr, "ERROR: "); - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - fprintf(stderr, ": %s\n", strerror(errno)); - - exit(1); -} - -static void error(const char* fmt, ...) -{ - va_list ap; - - fprintf(stderr, "ERROR: "); - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - fprintf(stderr, "\n"); - - exit(1); -} - -bool read_file(const char* path, char* buf, size_t size) -{ - int fd; - ssize_t ret; - - fd = open(path, O_RDONLY); - if (fd < 0) - return false; - - ret = read(fd, buf, size); - close(fd); - if (ret < 0) - return false; - - if (ret < size) { - buf[ret] = 0; - return true; - } else { - buf[size - 1] = 0; - return false; - } -} - -#define KB_ADDR 0x15 +#define MEGI_PROTO_BUG 1 int read_kb(int fd, uint8_t data[16]) { int ret; + uint8_t b = 5; struct i2c_msg msgs[] = { + { KB_ADDR, 0, 1, &b }, { KB_ADDR, I2C_M_RD, 16, data }, }; @@ -122,17 +42,16 @@ int read_kb(int fd, uint8_t data[16]) }; ret = ioctl(fd, I2C_RDWR, &msg); - //syscall_error(ret < 0, "I2C_RDWR failed"); + syscall_error(ret < 0, "I2C_RDWR failed"); return ret == 1 ? 0 : -1; } -#if 0 -int write_kb(int fd, uint8_t data[16]) +int write_kb(int fd, uint8_t* data) { int ret; struct i2c_msg msgs[] = { - { KB_ADDR, 0, 16, data }, + { KB_ADDR, 0, 4, data }, }; struct i2c_rdwr_ioctl_data msg = { @@ -141,102 +60,10 @@ int write_kb(int fd, uint8_t data[16]) }; ret = ioctl(fd, I2C_RDWR, &msg); - //syscall_error(ret < 0, "I2C_RDWR failed"); + syscall_error(ret < 0, "I2C_RDWR failed"); return ret == 1 ? 0 : -1; } -#endif - -static int gpiochip_open(void) -{ - int ret; - char path[256], buf[1024]; - int fd = -1; - - for (int i = 0; i < 8; i++) { - snprintf(path, sizeof path, "/sys/bus/gpio/devices/gpiochip%d/uevent", i); - if (!read_file(path, buf, sizeof buf)) - continue; - - if (!strstr(buf, "OF_FULLNAME=/soc/pinctrl@1f02c00")) - continue; - - snprintf(path, sizeof path, "/dev/gpiochip%d", i); - - int fd = open(path, O_RDWR); - syscall_error(fd < 0, "open(%s) failed"); - - //ret = ioctl(fd, I2C_SLAVE, addr); - //syscall_error(ret < 0, "I2C_SLAVE failed"); - - return fd; - } - - error("Can't find POGO I2C adapter"); - return -1; -} - -static int setup_gpio(void) -{ - int ret; - struct gpio_v2_line_request req = { - .num_lines = 1, - .offsets[0] = 12, - .config.flags = GPIO_V2_LINE_FLAG_INPUT | GPIO_V2_LINE_FLAG_BIAS_PULL_UP | /*GPIO_V2_LINE_FLAG_ACTIVE_HIGH |*/ GPIO_V2_LINE_FLAG_EDGE_FALLING, - .consumer = "ppkbd", - }; - - int fd = gpiochip_open(); - - ret = ioctl(fd, GPIO_V2_GET_LINE_IOCTL, &req); - syscall_error(ret < 0, "GPIO_V2_GET_LINE_IOCTL failed"); - - close(fd); - - return req.fd; -} - -static int get_int_value(int lfd) -{ - int ret; - struct gpio_v2_line_values vals = { - .mask = 1, - }; - - ret = ioctl(lfd, GPIO_V2_LINE_GET_VALUES_IOCTL, &vals); - syscall_error(ret < 0, "GPIO_V2_GET_LINE_IOCTL failed"); - - return vals.bits & 0x1; -} - -static int pogo_i2c_open(void) -{ - int ret; - char path[256], buf[1024]; - int fd = -1; - - for (int i = 0; i < 8; i++) { - snprintf(path, sizeof path, "/sys/class/i2c-adapter/i2c-%d/uevent", i); - if (!read_file(path, buf, sizeof buf)) - continue; - - if (!strstr(buf, "OF_FULLNAME=/soc/i2c@1c2b400")) - continue; - - snprintf(path, sizeof path, "/dev/i2c-%d", i); - - int fd = open(path, O_RDWR); - syscall_error(fd < 0, "open(%s) failed"); - - //ret = ioctl(fd, I2C_SLAVE, addr); - //syscall_error(ret < 0, "I2C_SLAVE failed"); - - return fd; - } - - error("Can't find POGO I2C adapter"); - return -1; -} #include "kmap.h" @@ -360,7 +187,7 @@ void on_press(uint8_t phys_idx) fn_mode = 1; return; } - + keys = keymap_fn[phys_idx]; } else if (pine_idx >= 0 || pine_mode) { if (key == KEY_ESC) { @@ -454,7 +281,6 @@ void update_keys(uint8_t* map) } } - // which new keys are pressed? for (int i = 0; i < n_keys; i++) { int key = keys[i]; @@ -468,18 +294,6 @@ void update_keys(uint8_t* map) } } -uint64_t time_abs(void) -{ - struct timespec tmp; - int ret; - - ret = clock_gettime(CLOCK_MONOTONIC, &tmp); - if (ret < 0) - return 0; - - return tmp.tv_sec * 1000000000ull + tmp.tv_nsec; -} - int main(int ac, char* av[]) { int fd, ret; @@ -487,7 +301,7 @@ int main(int ac, char* av[]) fd = pogo_i2c_open(); uinput_fd = open_uinput_dev(); - int lfd = setup_gpio(); + int lfd = gpio_setup_pl12(GPIO_V2_LINE_FLAG_INPUT | GPIO_V2_LINE_FLAG_BIAS_PULL_UP | /*GPIO_V2_LINE_FLAG_ACTIVE_HIGH |*/ GPIO_V2_LINE_FLAG_EDGE_FALLING); struct pollfd fds[2] = { { .fd = lfd, .events = POLLIN, }, @@ -495,6 +309,9 @@ int main(int ac, char* av[]) debug("\033[2J"); + uint8_t buf[4] = {1, 2, 3, 4}; + ret = write_kb(fd, buf); + while (1) { ret = poll(fds, 1, 10000); syscall_error(ret < 0, "poll failed"); @@ -510,9 +327,7 @@ int main(int ac, char* av[]) if (ret) continue; -#if DEBUG - print_bitmap(buf + 4); -#endif +// print_bitmap(buf + 4); update_keys(buf + 4); } } diff --git a/i2c-selftest.c b/i2c-selftest.c new file mode 100644 index 0000000..31145de --- /dev/null +++ b/i2c-selftest.c @@ -0,0 +1,114 @@ +/* + * Pinephone keyboard I2C debugging tool. + * + * Copyright (C) 2021 Ondřej Jirman + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "common.c" +#include "firmware/registers.h" + +static int iic_fd = -1; + +static void dump_log(void) +{ + int ret; + uint8_t addr = REG_DEBUG_LOG; + uint8_t buf[64]; + struct i2c_msg msgs[] = { + { KB_ADDR, 0, 1, &addr }, // set 0xff address + { KB_ADDR, I2C_M_RD, sizeof(buf), buf }, + }; + + struct i2c_rdwr_ioctl_data msg = { + .msgs = msgs, + .nmsgs = sizeof(msgs) / sizeof(msgs[0]) + }; + + ret = ioctl(iic_fd, I2C_RDWR, &msg); + syscall_error(ret < 0, "I2C_RDWR failed"); + + int i; + for (i = 0; i < sizeof(buf) && buf[i]; i++); + + if (i > 0) + xwrite(1, buf, i); +} + +static void wr_buf(uint8_t addr, uint8_t* buf, size_t size) +{ + int ret; + uint8_t mbuf[size + 1]; + + mbuf[0] = addr; + memcpy(mbuf + 1, buf, size); + + struct i2c_msg msgs[] = { + { KB_ADDR, 0, size + 1, mbuf }, + }; + + struct i2c_rdwr_ioctl_data msg = { + .msgs = msgs, + .nmsgs = sizeof(msgs) / sizeof(msgs[0]) + }; + + debug("WR[%02hhx]:", addr); + for (int i = 0; i < size; i++) + debug(" %02hhx", buf[i]); + debug("\n"); + + ret = ioctl(iic_fd, I2C_RDWR, &msg); + syscall_error(ret < 0, "I2C_RDWR failed"); +} + +static void rd_buf(uint8_t addr, uint8_t* buf, size_t size) +{ + int ret; + struct i2c_msg msgs[] = { + { KB_ADDR, 0, 1, &addr }, + { KB_ADDR, I2C_M_RD, size, buf }, + }; + + struct i2c_rdwr_ioctl_data msg = { + .msgs = msgs, + .nmsgs = sizeof(msgs) / sizeof(msgs[0]) + }; + + ret = ioctl(iic_fd, I2C_RDWR, &msg); + syscall_error(ret < 0, "I2C_RDWR failed"); + + debug("RD[%02hhx]:", addr); + for (int i = 0; i < size; i++) { + if (i % 8 == 0 && i > 0) + debug("\n "); + debug(" %02hhx", buf[i]); + } + debug("\n"); +} + +int main(int ac, char* av[]) +{ + iic_fd = pogo_i2c_open(); + + uint8_t cmd[] = {REG_SYS_COMMAND_SELFTEST}; + wr_buf(REG_SYS_COMMAND, cmd, sizeof cmd); + + while (1) { + dump_log(); + usleep(10000); + } + + return 0; +} diff --git a/inputd/build.sh b/inputd/build.sh deleted file mode 100755 index e2fb71d..0000000 --- a/inputd/build.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -php map-to-c.php factory-keymap.txt > kmap.h -gcc -o ppkbd main.c || exit 1 diff --git a/inputd/kmap.h b/inputd/kmap.h deleted file mode 100644 index d805828..0000000 --- a/inputd/kmap.h +++ /dev/null @@ -1,284 +0,0 @@ -static const uint8_t el_phys_map[256] = { - 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, - 0x19, 0x1a, 0x1b, 0x1c, 0xff, 0xff, 0xff, 0xff, - 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, - 0x29, 0x2a, 0x2b, 0x2c, 0xff, 0xff, 0xff, 0xff, - 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, - 0x39, 0x3a, 0x3b, 0xff, 0xff, 0xff, 0xff, 0xff, - 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, - 0x49, 0x4a, 0x4b, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0x51, 0xff, 0xff, 0x54, 0xff, 0x56, 0xff, - 0x58, 0x57, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0x52, 0x53, 0xff, 0x55, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -}; - -static const int used_keys[] = { - KEY_ESC, - KEY_1, - KEY_LEFTSHIFT, - KEY_BACKSLASH, - KEY_F1, - KEY_2, - KEY_F2, - KEY_3, - KEY_DOLLAR, - KEY_F3, - KEY_4, - KEY_EURO, - KEY_F4, - KEY_5, - KEY_GRAVE, - KEY_F5, - KEY_6, - KEY_F6, - KEY_7, - KEY_MINUS, - KEY_F7, - KEY_8, - KEY_EQUAL, - KEY_F8, - KEY_9, - KEY_F9, - KEY_BACKSPACE, - KEY_DELETE, - KEY_TAB, - KEY_Q, - KEY_W, - KEY_E, - KEY_R, - KEY_T, - KEY_Y, - KEY_U, - KEY_I, - KEY_O, - KEY_P, - KEY_ENTER, - KEY_LEFTMETA, - KEY_SYSRQ, - KEY_A, - KEY_S, - KEY_D, - KEY_F, - KEY_G, - KEY_H, - KEY_J, - KEY_K, - KEY_L, - KEY_SEMICOLON, - KEY_INSERT, - KEY_Z, - KEY_X, - KEY_C, - KEY_V, - KEY_B, - KEY_N, - KEY_M, - KEY_COMMA, - KEY_HOME, - KEY_DOT, - KEY_UP, - KEY_SLASH, - KEY_END, - KEY_LEFTCTRL, - KEY_FN, - KEY_LEFTALT, - KEY_SPACE, - KEY_RIGHTALT, - KEY_APOSTROPHE, - KEY_LEFT, - KEY_LEFTBRACE, - KEY_DOWN, - KEY_RIGHTBRACE, - KEY_RIGHT, -}; - -static const char* key_names[] = { - [KEY_ESC] = "ESC", - [KEY_1] = "1", - [KEY_LEFTSHIFT] = "LEFTSHIFT", - [KEY_BACKSLASH] = "BACKSLASH", - [KEY_F1] = "F1", - [KEY_2] = "2", - [KEY_F2] = "F2", - [KEY_3] = "3", - [KEY_DOLLAR] = "DOLLAR", - [KEY_F3] = "F3", - [KEY_4] = "4", - [KEY_EURO] = "EURO", - [KEY_F4] = "F4", - [KEY_5] = "5", - [KEY_GRAVE] = "GRAVE", - [KEY_F5] = "F5", - [KEY_6] = "6", - [KEY_F6] = "F6", - [KEY_7] = "7", - [KEY_MINUS] = "MINUS", - [KEY_F7] = "F7", - [KEY_8] = "8", - [KEY_EQUAL] = "EQUAL", - [KEY_F8] = "F8", - [KEY_9] = "9", - [KEY_F9] = "F9", - [KEY_BACKSPACE] = "BACKSPACE", - [KEY_DELETE] = "DELETE", - [KEY_TAB] = "TAB", - [KEY_Q] = "Q", - [KEY_W] = "W", - [KEY_E] = "E", - [KEY_R] = "R", - [KEY_T] = "T", - [KEY_Y] = "Y", - [KEY_U] = "U", - [KEY_I] = "I", - [KEY_O] = "O", - [KEY_P] = "P", - [KEY_ENTER] = "ENTER", - [KEY_LEFTMETA] = "LEFTMETA", - [KEY_SYSRQ] = "SYSRQ", - [KEY_A] = "A", - [KEY_S] = "S", - [KEY_D] = "D", - [KEY_F] = "F", - [KEY_G] = "G", - [KEY_H] = "H", - [KEY_J] = "J", - [KEY_K] = "K", - [KEY_L] = "L", - [KEY_SEMICOLON] = "SEMICOLON", - [KEY_INSERT] = "INSERT", - [KEY_Z] = "Z", - [KEY_X] = "X", - [KEY_C] = "C", - [KEY_V] = "V", - [KEY_B] = "B", - [KEY_N] = "N", - [KEY_M] = "M", - [KEY_COMMA] = "COMMA", - [KEY_HOME] = "HOME", - [KEY_DOT] = "DOT", - [KEY_UP] = "UP", - [KEY_SLASH] = "SLASH", - [KEY_END] = "END", - [KEY_LEFTCTRL] = "LEFTCTRL", - [KEY_FN] = "FN", - [KEY_LEFTALT] = "LEFTALT", - [KEY_SPACE] = "SPACE", - [KEY_RIGHTALT] = "RIGHTALT", - [KEY_APOSTROPHE] = "APOSTROPHE", - [KEY_LEFT] = "LEFT", - [KEY_LEFTBRACE] = "LEFTBRACE", - [KEY_DOWN] = "DOWN", - [KEY_RIGHTBRACE] = "RIGHTBRACE", - [KEY_RIGHT] = "RIGHT", -}; - -static const int keymap_base[256][2] = { - [0x11] = { KEY_ESC }, - [0x12] = { KEY_1 }, - [0x13] = { KEY_2 }, - [0x14] = { KEY_3 }, - [0x15] = { KEY_4 }, - [0x16] = { KEY_5 }, - [0x17] = { KEY_6 }, - [0x18] = { KEY_7 }, - [0x19] = { KEY_8 }, - [0x1a] = { KEY_9 }, - [0x1c] = { KEY_BACKSPACE }, - [0x21] = { KEY_TAB }, - [0x22] = { KEY_Q }, - [0x23] = { KEY_W }, - [0x24] = { KEY_E }, - [0x25] = { KEY_R }, - [0x26] = { KEY_T }, - [0x27] = { KEY_Y }, - [0x28] = { KEY_U }, - [0x29] = { KEY_I }, - [0x2a] = { KEY_O }, - [0x2b] = { KEY_P }, - [0x2c] = { KEY_ENTER }, - [0x31] = { KEY_LEFTMETA }, - [0x32] = { KEY_A }, - [0x33] = { KEY_S }, - [0x34] = { KEY_D }, - [0x35] = { KEY_F }, - [0x36] = { KEY_G }, - [0x37] = { KEY_H }, - [0x38] = { KEY_J }, - [0x39] = { KEY_K }, - [0x3a] = { KEY_L }, - [0x3b] = { KEY_SEMICOLON }, - [0x41] = { KEY_LEFTSHIFT }, - [0x42] = { KEY_Z }, - [0x43] = { KEY_X }, - [0x44] = { KEY_C }, - [0x45] = { KEY_V }, - [0x46] = { KEY_B }, - [0x47] = { KEY_N }, - [0x48] = { KEY_M }, - [0x49] = { KEY_COMMA }, - [0x4a] = { KEY_DOT }, - [0x4b] = { KEY_SLASH }, - [0x51] = { KEY_LEFTCTRL }, - [0x52] = { KEY_FN }, - [0x53] = { KEY_LEFTALT }, - [0x54] = { KEY_SPACE }, - [0x55] = { KEY_RIGHTALT }, - [0x56] = { KEY_APOSTROPHE }, - [0x57] = { KEY_LEFTBRACE }, - [0x58] = { KEY_RIGHTBRACE }, -}; - -static const int keymap_fn[256][2] = { - [0x12] = { KEY_LEFTSHIFT, KEY_BACKSLASH }, - [0x13] = { KEY_BACKSLASH }, - [0x14] = { KEY_DOLLAR }, - [0x15] = { KEY_EURO }, - [0x16] = { KEY_LEFTSHIFT, KEY_GRAVE }, - [0x17] = { KEY_GRAVE }, - [0x18] = { KEY_MINUS }, - [0x19] = { KEY_EQUAL }, - [0x1a] = { KEY_LEFTSHIFT, KEY_MINUS }, - [0x1c] = { KEY_DELETE }, - [0x31] = { KEY_LEFTSHIFT, KEY_SYSRQ }, - [0x3b] = { KEY_INSERT }, - [0x49] = { KEY_HOME }, - [0x4a] = { KEY_UP }, - [0x4b] = { KEY_END }, - [0x56] = { KEY_LEFT }, - [0x57] = { KEY_DOWN }, - [0x58] = { KEY_RIGHT }, -}; - -static const int keymap_pine[256][2] = { - [0x12] = { KEY_F1 }, - [0x13] = { KEY_F2 }, - [0x14] = { KEY_F3 }, - [0x15] = { KEY_F4 }, - [0x16] = { KEY_F5 }, - [0x17] = { KEY_F6 }, - [0x18] = { KEY_F7 }, - [0x19] = { KEY_F8 }, - [0x1a] = { KEY_F9 }, -}; - diff --git a/keymaps/factory-keymap-megi.txt b/keymaps/factory-keymap-megi.txt new file mode 100644 index 0000000..b8dade0 --- /dev/null +++ b/keymaps/factory-keymap-megi.txt @@ -0,0 +1,63 @@ +# physical layout Row:Col -> KEY COMBO_KEY1 COMBO_KEY2 +# combo1 is using FN key, combo2 is using PINE key +# see factory-keymap.jpg +# https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/input-event-codes.h#L76 + +1:1 ESC +1:2 1 LEFTSHIFT+BACKSLASH F1 +1:3 2 BACKSLASH F2 +1:4 3 DOLLAR F3 # pound, really, but who uses that? also it doesn't have a keycode in Linux +1:5 4 EURO F4 +1:6 5 LEFTSHIFT+GRAVE F5 +1:7 6 GRAVE F6 +1:8 7 MINUS F7 +1:9 8 EQUAL F8 +1:10 9 LEFTSHIFT+MINUS F9 +1:11 0 PLUS F10 +1:12 BACKSPACE DELETE + +2:1 TAB +2:2 Q +2:3 W +2:4 E +2:5 R +2:6 T +2:7 Y Z +2:8 U +2:9 I +2:10 O +2:11 P +2:12 ENTER + +3:1 LEFTMETA LEFTSHIFT+SYSRQ # LEFTMETA = PINE key +3:2 A +3:3 S +3:4 D +3:5 F +3:6 G +3:7 H +3:8 J +3:9 K +3:10 L +3:11 SEMICOLON INSERT + +4:1 LEFTSHIFT +4:2 Z +4:3 X +4:4 C LEFTCTRL +4:5 V +4:6 B +4:7 N +4:8 M +4:9 COMMA HOME +4:10 DOT UP +4:11 SLASH END + +5:1 LEFTCTRL +5:2 FN +5:3 LEFTALT +5:4 SPACE +5:5 RIGHTALT +5:6 APOSTROPHE LEFT +5:7 LEFTBRACE DOWN +5:8 RIGHTBRACE RIGHT diff --git a/inputd/factory-keymap.jpg b/keymaps/factory-keymap.jpg similarity index 100% rename from inputd/factory-keymap.jpg rename to keymaps/factory-keymap.jpg diff --git a/inputd/factory-keymap.txt b/keymaps/factory-keymap.txt similarity index 100% rename from inputd/factory-keymap.txt rename to keymaps/factory-keymap.txt diff --git a/inputd/map-to-c.php b/keymaps/map-to-c.php similarity index 96% rename from inputd/map-to-c.php rename to keymaps/map-to-c.php index 7d4eb3a..5fd3964 100644 --- a/inputd/map-to-c.php +++ b/keymaps/map-to-c.php @@ -20,8 +20,8 @@ * along with this program. If not, see . */ -$pmap = file_get_contents("physical-map.txt"); -$kmap = file_get_contents($argv[1]); +$pmap = file_get_contents($argv[1]); +$kmap = file_get_contents($argv[2]); // high nibble = row, low nibble col $el_phys_map = []; diff --git a/inputd/physical-map.jpg b/keymaps/physical-map.jpg similarity index 100% rename from inputd/physical-map.jpg rename to keymaps/physical-map.jpg diff --git a/inputd/physical-map.txt b/keymaps/physical-map.txt similarity index 100% rename from inputd/physical-map.txt rename to keymaps/physical-map.txt diff --git a/usb-flasher/debugger.c b/usb-debugger.c similarity index 92% rename from usb-flasher/debugger.c rename to usb-debugger.c index dbd736e..ca5316b 100644 --- a/usb-flasher/debugger.c +++ b/usb-debugger.c @@ -17,7 +17,6 @@ * along with this program. If not, see . */ -#define DEBUG 1 #include "common.c" int kb_open(void) @@ -93,21 +92,6 @@ int response(uint8_t res[8]) return 0; } -ssize_t xwrite(int fd, uint8_t* buf, size_t len) -{ - size_t off = 0; - - while (off < len) { - ssize_t ret = write(fd, buf + off, len - off); - if (ret < 0) - return ret; - - off += ret; - } - - return off; -} - int read_stdout(void) { int ret; @@ -125,6 +109,11 @@ int read_stdout(void) if (ret) return ret; + debug("STD%d:", urb.actual_length); + for (int i = 0; i < urb.actual_length; i++) + debug(" %02hhx", buf[i]); + debug("\n"); + if (urb.actual_length > 0) { ssize_t rv = xwrite(1, buf, urb.actual_length); if (rv < 0) @@ -180,15 +169,17 @@ int main(int ac, char* av[]) if (usb_fd < 0) error("Failed to open the keyboard"); - int i = 0; while (1) { ret = read_stdout(); + if (ret < 0 && errno != 110) + syscall_error(true, "read_stdout failed"); ret = read_keys(keys); + if (ret < 0 && errno != 110) + syscall_error(true, "read_keys failed"); + if (ret == 0) print_bitmap(keys); - - i++; } return 0; diff --git a/usb-flasher/flasher.c b/usb-flasher.c similarity index 98% rename from usb-flasher/flasher.c rename to usb-flasher.c index d308bea..d4234d2 100644 --- a/usb-flasher/flasher.c +++ b/usb-flasher.c @@ -17,7 +17,6 @@ * along with this program. If not, see . */ -#define VERSION "1.0" #include "common.c" // }}} @@ -558,14 +557,14 @@ const char* boot_cond_text(uint8_t status) static void usage(void) { printf( - "Usage: ppkb-flasher [--rom-in ] [--rom-out ] [--verbose]\n" - " [--help] [...]\n" + "Usage: ppkb-usb-flasher [--rom-in ] [--rom-out ] [--verbose]\n" + " [--help] [...]\n" "\n" "Options:\n" " -i, --rom-in Specify path to binary file you want to flash.\n" " -o, --rom-out Specify path where you want to store the contents\n" " of code ROM read from the device.\n" - " -s, --rom-size Specify how many bytes of code rom to flash\n" + " -s, --size Specify how many bytes of code rom to flash\n" " starting from offset 0x2000 in the rom file.\n" " -v, --verbose Show details of what's going on.\n" " -h, --help This help.\n" @@ -579,7 +578,7 @@ static void usage(void) "Format of the ROM files is a flat binary. Only the part of it starting\n" "from 0x2000 will be flashed. Use -s to specify how many bytes to write.\n" "\n" - "Pinephone keyboard flashing tool " VERSION "\n" + "Pinephone keyboard USB flashing tool " VERSION "\n" "Written by Ondrej Jirman , 2021\n" "Licensed under GPLv3, see https://xff.cz/git/pinephone-keyboard/ for\n" "more information.\n" @@ -592,7 +591,7 @@ int main(int ac, char* av[]) { char* rom_in = NULL; char* rom_out = NULL; - int size = 0x1000; + int size = 0x1200; int ret; while (1) { @@ -618,7 +617,7 @@ int main(int ac, char* av[]) rom_in = strdup(optarg); break; case 's': - if (strstr("0x", optarg) == optarg) { + if (strstr(optarg, "0x") == optarg) { errno = 0; char* next = NULL; size = strtol(optarg + 2, &next, 16); diff --git a/usb-flasher/build.sh b/usb-flasher/build.sh deleted file mode 100755 index 23b14c1..0000000 --- a/usb-flasher/build.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -set -e - -gcc -o ppkb-flasher flasher.c -gcc -o ppkb-debugger debugger.c diff --git a/usb-flasher/common.c b/usb-flasher/common.c deleted file mode 100644 index 5d12aee..0000000 --- a/usb-flasher/common.c +++ /dev/null @@ -1,205 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -static bool verbose; -#define debug(args...) { if (verbose) printf(args); } - -static void syscall_error(int is_err, const char* fmt, ...) -{ - va_list ap; - - if (!is_err) - return; - - fprintf(stderr, "ERROR: "); - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - fprintf(stderr, ": %s\n", strerror(errno)); - - exit(1); -} - -static void error(const char* fmt, ...) -{ - va_list ap; - - fprintf(stderr, "ERROR: "); - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - fprintf(stderr, "\n"); - - exit(1); -} - -static bool read_file(const char* path, char* buf, size_t size) -{ - int fd; - ssize_t ret; - - fd = open(path, O_RDONLY); - if (fd < 0) - return false; - - ret = read(fd, buf, size); - close(fd); - if (ret < 0) - return false; - - if (ret < size) { - buf[ret] = 0; - return true; - } else { - buf[size - 1] = 0; - return false; - } -} - -static int open_usb_dev(uint16_t vid, uint16_t pid) -{ - char path[256], buf[256]; - struct dirent *e; - unsigned e_vid, e_pid, bus, dev; - int fd = -1, ret; - DIR* d; - - d = opendir("/sys/bus/usb/devices"); - syscall_error(d == NULL, "opendir(/sys/bus/usb/devices) failed"); - - while (true) { - errno = 0; - e = readdir(d); - syscall_error(e == NULL && errno, "readdir(/sys/bus/usb/devices) failed"); - if (!e) - break; - - if (!strcmp(e->d_name, ".") || !strcmp(e->d_name, "..")) - continue; - - snprintf(path, sizeof path, - "/sys/bus/usb/devices/%s/idVendor", e->d_name); - if (!read_file(path, buf, sizeof buf)) - continue; - - ret = sscanf(buf, "%x", &e_vid); - if (ret != 1) - error("Failed to parse %s", path); - - snprintf(path, sizeof path, - "/sys/bus/usb/devices/%s/idProduct", e->d_name); - if (!read_file(path, buf, sizeof buf)) - continue; - - ret = sscanf(buf, "%x", &e_pid); - if (ret != 1) - error("Failed to parse %s", path); - - if (e_vid == vid && e_pid == pid) { - snprintf(path, sizeof path, - "/sys/bus/usb/devices/%s/busnum", e->d_name); - if (!read_file(path, buf, sizeof buf)) - error("Failed to read %s", path); - - ret = sscanf(buf, "%u", &bus); - if (ret != 1) - error("Failed to parse %s", path); - - snprintf(path, sizeof path, - "/sys/bus/usb/devices/%s/devnum", e->d_name); - if (!read_file(path, buf, sizeof buf)) - error("Failed to read %s", path); - - ret = sscanf(buf, "%u", &dev); - if (ret != 1) - error("Failed to parse %s", path); - - snprintf(path, sizeof path, - "/dev/bus/usb/%03u/%03u", bus, dev); - - debug("Found %04x:%04x at %s\n", e_vid, e_pid, path); - - fd = open(path, O_RDWR); - syscall_error(fd < 0, "open(%s) failed", path); - break; - } - } - - errno = ENOENT; - closedir(d); - return fd; -} - -static int handle_urb(int usb_fd, struct usbdevfs_urb* urb, int timeout) -{ - int ret; - struct usbdevfs_urb* reaped_urb; - int retries = 0; - -retry: - ret = ioctl(usb_fd, USBDEVFS_SUBMITURB, urb); - if (ret < 0) - return ret; - - struct pollfd fd = { - .fd = usb_fd, - .events = POLLOUT, - }; - - ret = poll(&fd, 1, timeout); - if (ret <= 0) { - if (ret == 0) - errno = ETIMEDOUT; - - int save_errno = errno; - - // on timeout or other poll error, we need to discard and reap the submitted URB - ret = ioctl(usb_fd, USBDEVFS_DISCARDURB, urb); - - // even if discard fails, URB may still be reapable, we need to try reaping anyway - ret = ioctl(usb_fd, USBDEVFS_REAPURBNDELAY, &reaped_urb); - - // reap must immediately succeed, otherwise this is fatal - syscall_error(ret < 0, "USBDEVFS_REAPURBNDELAY failed"); - - errno = save_errno; - return -1; - } - - // hopefully POLLERR means we get some error immediately on reap - - ret = ioctl(usb_fd, USBDEVFS_REAPURB, &reaped_urb); - if (ret < 0) - return ret; - - // EPROTO errors are recoverable - if (urb->status == -71 && retries < 3) { - retries++; - goto retry; - } - - if (urb->status != 0) { - errno = -urb->status; - return -1; - } - - return 0; -}