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
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
customize and play with, and the kernel one will be more available and
better integrated into kernel's suspend/resume functionality, I assume.
From factory the keyboard will come with the stock keyboard firmware. This
firmware will occupy code memory from addresses
0x3fff. The stock firmware will also have a I2C flashing interface,
whose proposed workings are described
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
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:
- fix issues in the stock firmware in the field, or
- to allow a completely differnt way of implementing the keyboard
functionality, for example by implementing a standard I2C HID interface instead
of the propsed non-standard one, or
- to add more functionality to the keyboard by using free GPIO on the
keyboard's micorcontroller to perform HW modifications (there's some free
space inside the keyboard), and hook up some extra peripherals to the MCU and
expose them over I2C to the phone.
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
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
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).