LPC SPI driver prototype

First contact
On LPC side (init):

  LPC_SSP0->DR = (uint8_t)'R';
  LPC_SSP0->DR = (uint8_t)'V';
  LPC_SSP0->DR = (uint8_t)REFORM_MOTHERBOARD_REV;

On host side:

>>> ssp=spi.SPI("/dev/spidev1.0", 1, 1000000)
>>> ssp.transfer(b'RV0')
b'RV\r'
>>> ssp.transfer(b'1234')
b' \n\x02\x17'
>>> ssp.transfer(b'5678')
b'456\x00'
>>> ssp.transfer(b'abcd')
b' \x0c\x04\x16'
>>> ssp.transfer(b'efgh')
b' \x0c\x04\x16'

On LPC side (uart debug):

IRQ: MIS: 02; RIS: 0A; SR: 10; BUF[0303]: 52 56 30
IRQ: MIS: 04; RIS: 0C; SR: 10; BUF[0404]: 31 32 33 34
IRQ: MIS: 04; RIS: 0C; SR: 10; BUF[0404]: 35 36 37 38
IRQ: MIS: 04; RIS: 0C; SR: 10; BUF[0404]: 61 62 63 64
IRQ: MIS: 04; RIS: 0C; SR: 10; BUF[0404]: 65 66 67 68

so far so good.

4 Likes

More practical example:

>>> from  periphery import spi
>>> ssp=spi.SPI("/dev/spidev1.0", 1, 1000000)
>>> ssp.transfer(b'01234567')
b'\x00\x00\x00\x00\x00\x00\x00\x00'
>>> ssp.transfer(b'01234567')
b'\r\x05m\x01\x00\x00d\xff'
>>> ret=_
>>> print("MB Rev: %d"%ret[0])
MB Rev: 13
>>> print("LPC State: %s"%("Charging","Overvolted","Cooldown","Undervolted","Missing","Full","Suspend")[ret[1]])
LPC State: Full
>>> print("Voltage: %d"%(ret[2]<<8 | ret[3]))
Voltage: 27905
>>> print("Current: %d"%(ret[4]<<8 | ret[5]))
Current: 0
>>> print("Percents: %d"%ret[6])
Percents: 100
>>> print("Controls:\n 5V0: %r\n 3V3: %r\n USB: %r\n AUX: %r\n 1V2: %r\n PE1: %r\n SER: %r\n NTC: %r"%(bool(ret[7]&1), bool(ret[7]&2), bool(ret[7]&4), bool(ret[7]&8), bool(ret[7]&16), bool(ret[7]&32), bool(ret[7]&64), bool(ret[7]&128)))
Controls:
 5V0: True
 3V3: True
 USB: True
 AUX: True
 1V2: True
 PE1: True
 SER: True
 NTC: True

Now at toher console disable UART debug

[root@mnr ~]# sleep 1;echo xUAR0 > /dev/hidraw0

and continue with python:

>>> ret=ssp.transfer(b'01234567')
>>> ret
b'\r\x05l\xfe\x00\x00d\xff'
>>> ret=ssp.transfer(b'01234567')
>>> ret
b'\r\x05lx\x00\x00d\xbf'
>>> print("MB Rev: %d"%ret[0])
MB Rev: 13
>>> print("LPC State: %s"%("Charging","Overvolted","Cooldown","Undervolted","Missing","Full","Suspend")[ret[1]])
LPC State: Full
>>> print("Voltage: %d"%(ret[2]<<8 | ret[3]))
Voltage: 27768
>>> print("Current: %d"%(ret[4]<<8 | ret[5]))
Current: 0
>>> print("Percents: %d"%ret[6])
Percents: 100
>>> print("Controls:\n 5V0: %r\n 3V3: %r\n USB: %r\n AUX: %r\n 1V2: %r\n PE1: %r\n SER: %r\n NTC: %r"%(bool(ret[7]&1), bool(ret[7]&2), bool(ret[7]&4), bool(ret[7]&8), bool(ret[7]&16), bool(ret[7]&32), bool(ret[7]&64), bool(ret[7]&128)))
Controls:
 5V0: True
 3V3: True
 USB: True
 AUX: True
 1V2: True
 PE1: True
 SER: False
 NTC: True
>>>

so yea, kind of works, need to polish data sync

4 Likes

Kinda supercool. Would be nice to incorporate it with waybar battery module!

Amazing! Keep us posted!

Yes absolutely. Although checking battery levels through the LPC is a pretty cool aspect of the laptop to me.

1 Like

I’d like to help develop this further, if I understand things correctly.
The SPI connection between the imx and the lpc is working, we just need to add an interface for requesting and transmitting data?

I see that there is a uart connection from the imx and lpc as well, that is currently setup to transmit only due to keyboard resume issues?

  // board_reform.c
  // only send to reform, don't receive from it
  /* Set 0.13 UART TXD */
  LPC_IOCON->PIO1_13 &= ~0x07;

Digging into the keyboard firmware, I see that we can send usb commands to enable/disable the uart connection of lpc to the imx. With the imx being receive only uart from the lpc.

example path for enabling lpc reporting to imx:
imx β†’ usb(β€œUAR1”) β†’ kb β†’ uart(β€œ1u”) β†’ lpc

Seems like the automatic reporting is no longer a feature? I don’t see any uartSends in the lpc main loop. A periodic command from the imx to the kb to the lpc would be required to poll the battery condition to the uart with the current firmware, unless I’m missing where the kb is triggering this periodic send.
imx β†’ usb(β€œRPRT”) β†’ kb β†’ uart(β€œ0c”) β†’ lpc β†’ uart(battery_state) β†’ imu/kb

obv working SPI solves this long indirect chain.

SPI battery reporting would be great, it is absolutely a short/mid term goal for us. Alternatively we could also use USB battery reporting features in the keyboard.

After learning a lot about device tree and kernel builds I have a patch that enable the usermode spi driver for mnt debian kernel package.

As well as a minimal change to the lpc firmware that fills the spi transmit buffer with basic battery stats.

And a quick python program (that should be rewritten and made a kernel driver instead) that reads the battery information from the spi device.
Once my account is approved in gitlab I can put a PR out for the kernel and fw changes while I work on a driver.

A more advanced design that implements a command and poll structure is ideal, but that requires adding spi interrupt handlers to the lpc since the spi buffer is only 8 bytes. This design fits the battery info in the 8 bytes making it trivial for the lpc to fill the byte buffer with the stats at its existing poll rate.

6 Likes

this is very cool work!