megi's PinePhone Development Log RSS

Genocide is bad

2021–06–20: Pinephone keyboard's firmware I2C interface

I've implemented a USB stack for the pinephone keyboard's firmware, to be able to perform printf debugging and tracing of what the MCU does. It's now possible to see MCU's operation using a simple USB debugging tool that shows the output from debugging code on the MCU in real-time.

I've used the new debugger it to get to understand precise behavior of I2C slave peripheral. How interrupts are fired, and what's the contents of various status registers during various I2C transfers from the pinephone SoC over POGO pins.

This understanding can be used to write a more proper I2C control interface for the keyboard, that will be modelled as is typical with various other I2C devices a set of I2C „registers“ that can be read from and written to. The proposed specification for the register set is here. It will be possible to configure the keyboard's scanning behavior in various ways over the I2C, and perform self-tests for QA purposes.

I've also finalized the USB flashing tool. It now has a very nice interface and is quite useable and better documented.

Samuel shared his keyboard kernel driver recently. So you'll have a choice between kernel and userspace drivers for the keyboard. Both have their strengths. Userspace one will be easier to customize and play with, and the kernel one will be more available and better integrated into kernel's suspend/resume functionality, I assume.

User firmware

From factory the keyboard will come with the stock keyboard firmware. This firmware will occupy code memory from addresses 0x2000 to 0x3fff. The stock firmware will also have a I2C flashing interface, whose proposed workings are described here.

The superpower users will be able to write their own firmware, and flash it to the keyboard over I2C directly from the phone, without having to disassemble the keyboard and solder on a USB cable. ;) This firmware will not overwrite the stock firmware, but it will be placed at addresses 0x4000 to 0x7fff, after the stock firmware. If the user firmware is flashed, it will run instead of a stock firmware.

There will be a short time window where the stock firmware will wait for a I2C transactions prior to defering execution to user firmware after MCU reset. This window can be used to prevent the jump to user firmware, and stay in the stock one. This will be the mechanism for recovery in case the user flashes incorrect or borken user firmware.

This mechanism can be used to:

Power user based hardware or firmware modding the keyboard is quite a likely possibility when everyhting is developed in the open using FOSS licensing, with focus on making this relaitvely easy.

I'll propose exposing the free GPIOs as solder pads for the final revision of the keyboard's PCB.

There's just enough free space in the keyboard's body for a little drilling and eg. adding proximity sensor (to detect whether the lid is closed or not), to add notification LEDs visible when the KB is closed, etc.

You can also make much more space inside the keyboard by removing the battery, and replacing it with your own hardware. You may also expose GPIOs externally by adding a new connector to the keyboard body, and have some simple pluggable peripherals.

Next steps

Next, it's necessary to implement the proposed I2C interface. Most complicated part of which is the flashing interface, which will require further reverse engineering of the original USB bootloader, because code ROM flashing registers are not documented.

Once that is done, it will be necessary to again look at making the MCU operation as low power as possible, by using the power down and green modes of the MCU.

Charger interface

Charger's I2C interface will still need quite a bit of thought. There's an issue that the charger chip multiplexes I2C interface with the LED notification pins, and detects what kind of interface is used by checking the voltage on the I2C pins whenever it wakes up, which is not predictable.

I2C interface is shared with the keyboard's MCU, so if charger wakes up when the MCU is communicating with the SoC, it may see low voltage on I2C pins, and misdetect the interface as LED one, and start driving it as such and break communication between MCU and SoC.

Samuel also identified another way the charger can break I2C interface, and that's when the phone is off, and thus it does pull I2C SDA/SCL lines low via 10 kOhm pull-ups to DCDC1 on the SoC side, the voltage on I2C lines will be just ~1.5V, which is not enough to make the charger think I2C interface is used, and it again switches to LED driving mode.

We need to figure out some way to prevent this from happening.

The last proposal is to add mosfets between charger I2C pins and the POGO I2C pins, with gate connected to INT pin. When the INT pin is low (because it's driven low by MCU, or because DCDC1 is off, because the phone is off), these mosfets would disconnect the charger I2C pins from the POGO ones, and this would make the charger always detect the I2C interface mode correctly. It would also be possible for the SoC to disconnect the charger from I2C interface at any time by driving the INT pin low during I2C communication with the MCU.

Lastly when the phone is off, this would save some power by cutting off the path between „grounded“ DCDC1 and pull-ups to VREG (always on 3.1V linear regulator inside the keyboard charger chip).