XO 1.75 HOST to EC Protocol

From OLPC
Revision as of 03:19, 28 October 2010 by Wmb@firmworks.com (talk | contribs) (New channel numbers)
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

Here is a swag at a reliable, race-free protocol for SPI communication between the KB3930 EC and the Armada 610 CPU.

Goals and Assumptions

  • The boot ROM is on a different SPI bus than the EC, so the protocol doesn't have to worry about sharing access to the boot ROM.
  • Bidirectional communication - both sides may send information to the other
  • Multiplex multiple "channels" - keyboard, mouse, events, command responses - in the EC-to-CPU "upstream" direction
  • Optimize for the "EC sends keyboard/mouse data upstream" case, which is more frequent than the "CPU sends commands to EC" case.
  • For the important upstream case, one interrupt per transfer at the CPU end.
  • No indefinite busy-waiting at either end.
  • Positive flow control - both sides can determine when the other side has received the previous transfer and thus when it is safe to send another.
  • Data is only presented to the SPI hardware - on both ends - when it is known to be "safe" to do so.
  • Has to work well with the SPI hardware on the ENE 3930 EC and the Armada 610 CPU.

The hard part - with unadorned SPI - is flow control. How does the master know that the slave processor has accepted the previous data from the slave hardware interface and has thus freed up the hardware to accept new data? The standard SPI signal set has no "backpressure" signal for flow control. SPI is commonly used with the slave device being a "dumb" hardware chip, rather than a processor running complex code with unpredictable latency. Making it work well between two "smart" devices requires extra signaling, which is the subject of this protocol.

If the data rate is low, it might be possible to get away without flow control. The CPU's Rx FIFO has 16 entries, so several short packets would fit in the FIFO. The upstream data rate is typically limited by slow devices like keyboard and mouse, and the CPU is relatively fast (but with variable latency). However, if it is possible to have precise flow control without too much trouble, I think that is better than "just hoping it works most of the time".

Protocol Description and Specification

Hardware Setup

  • The EC is the SPI master, using the SDI interface in host mode.
  • The CPU is the SPI slave, using the SSP interface in slave mode.
  • There are additional GPIOs in the CPU to EC direction - ACK (flow control) and CMD (direction switching). At the EC end, they are configured for "interrupt on rising edge".

The Usual Upstream Case

  • The usual link state is Upstream. In this state, the EC can send unsolicited data upstream whenever it has a byte to send. When the CPU needs to send data downstream (a "command"), it must negotiate a direction switch. After the command/response exchange is finished, the link returns to the Upstream state.
  • In Upstream state, the data packet length is fixed at 2 bytes. The first byte identifies the "channel" - invalid (0), switch (1), command_response (2), keyboard (3), touchpad (4), event (5), or EC_debug (6) - and the second byte is the data for that channel. (We can define additional channels if necessary.)
  • When the EC has a byte to send, it runs a 2-byte SPI transaction (channel byte and data byte) and then enters Wait state.
  • The CPU's SPI interface is normally (i.e. when in Upstream state) configured to interrupt after two bytes have been received. (The CPU hardware cannot interrupt on frame deassertion). The interrupt handler removes the two bytes from the Rx FIFO, distributes the data byte to the specified channel module, then pulses ACK low-then-high. (In normal operation, ACK remains high except during the brief low pulse. If ACK remains low continuously, the EC thereby knows that the CPU is just not listening at all.)
  • The EC is configured to interrupt on the rising edge of ACK. When the CPU pulses ACK, the rising edge triggers that interrupt, which makes the EC transition from Wait back to Upstream. Thus the upstream flow control is accomplished in the usual case.
  • Additionally, a long timeout is used to reset the state machine should the ack not occur.

The Command Case

  • When the CPU wants to send a command, it sets the CMD line high.
  • The EC, in Upstream state, sees that CMD is high. It sends a 2-byte packet (in the normal upstream format) with "channel" == "switch" and data == 0, then enters Switch Wait state.
  • The CPU receives the "switch" packet, loads a 6-byte command packet into its Tx FIFO, changes the interrupt threshold to 6, pulses ACK, and enters Switched state. The first byte of the 6 is the command code. The second contains two 4-bit fields - number of command argument bytes in the 4 MSBs and number or response bytes in the 4 LSBs. The remaining bytes are the command argument bytes, padded with zeros as necessary to fill up 4 bytes.
  • When the EC receives the ACK interrupt, it performs a 6-byte SPI transaction with null transmit data, in order to receive the 6-byte command packet. It then enters Pull Wait state.
  • The CPU is notified, via the FIFO interrupt, that the command has been transferred to the EC. If the response length for that command is 0, the CPU sets the FIFO interrupt threshold to 2, pulses ACK, and returns to the Upstream state (so all is back to normal). If the response length is nonzero, the CPU sets the FIFO interrupt threshold to the response length and enters Response state.
  • The EC receives the ACK interrupt, then executes the indicated command. If the response length for that command is 0, the EC returns to Upstream state. If the response length is nonzero, the EC sends the response packet and enters Wait state.
  • The CPU receives the response packet, forwards the information to the command module, sets the FIFO interrupt threshold back to 2, pulses ACK, and returns to Upstream state.
  • The EC receives the ACK interrupt and returns to Upstream state.
  • Both ends have long timeouts in case the other side fails to respond, as detailed in the complete state diagrams.

Observations

  • The common case of keyboard and mouse data is very simple from the CPU end. The CPU gets an interrupt, pulls out the data packet, and forwards it to the input module.
  • The command-with-response case requires 2 or 3 interrupts - one to mark the direction switch (so Linux knows when it is okay to load up the FIFO), one to clean up after the EC pulls the command, and an optional third one to grab the response.
  • It's unnecessary to use the command mechanism for stuff like SCIs and battery events. The EC can just send them up whenever it gets them, addressed to the event channel.
  • The reason for the fixed length command packet is because the EC doesn't know the command length in advance. At the expense of extra CPU overhead (another interrupt), it would be possible to add an extra state to separate the command arguments from the command code. As it is, the extra 4 bytes (beyond the usual two) "cost" about 8 microseconds of EC time, assuming a 4 MHz SPI clock.
  • The second byte of the command packet, containing the argument and response length, is not really necessary, since both lengths are implicitly known from the command code. Calling out the response length explicitly makes the transport code a bit simpler by avoiding a lookup.

State Diagrams

Note: Due to a problem with MediaWiki's SVG rendering, you will need to click through the images below twice in order to see the diagrams properly

EC State Diagram
CPU State Diagram

Timing Requirements

According to ENE, the edge detector must see a low pulse for 4 cycles of the 8051 main clock - specifically they said:

 Time = 2/(8051 main clk / 2) 
	Exp: main clk = 16Mhz
		T = 2x 1/(16M/2) = 250ns

According to a quick measurement I just made, the time between consecutive writes to GPIO registers is about 350 ns. I measured it by writing a Forth "code word" (assembly language procedure) that performs 20 consecutive "str" instructions to the same GPIO register. I then executed that word 1 M times in a loop, timing it with a stopwatch at 7 seconds. 1 M empty loops is instantaneous, so we have 7 s / 20 M writes = 350 ns / write.

Just to be safe, perhaps udelay(1) between clearing and setting ACK would be prudent.

Implementation Sketch

CPU End

Init

  • Attach interrupt handler to SPI transaction done interrupt
  • Init SSP3 in SPI slave mode
  • Set SSP3 to interrupt when the Rx FIFO contains 2 bytes
  • Set SSP3 Receive Without Transmit to 1
  • Clear CMD to low
  • Set ACK to high

SSP Interrupt handler

  • Switch on state:
    • Upstream state: Switch on channel number from first byte
      • Keyboard: Forward second byte to keyboard module
      • Mouse: Forward second byte to mouse module
      • Event: Forward second byte to event module
      • Switch: Write command frame to Tx FIFO, set FIFO threshold to 6, set SSP3 Receive Without Transmit to 0, set NextState to Switched
    • Switched state: Set SSP3 Receive Without Transmit to 1
      • If expected response length is 0: notify Do_EC_Command that command has been sent, set FIFO threshold to 2, clear CMD to low, cancel command timeout, set NextState to Upstream
      • If expected response length is non0: set FIFO threshold to response length, set NextState to Response
    • Response state: Give response data to Do_EC_Command, set FIFO threshold to 2, clear CMD to low, cancel command timeout, set NextState to Upstream
  • Pulse ACK low then high
  • Return from interrupt

Do_EC_Command() function

  • Prepare a command packet containing the command code, expected response length, and command arguments
  • Schedule a one-shot timeout for 1 second (EC unresponsive, shouldn't happen)
  • Set CMD to high
  • Sleep, waiting for a notification (either response data, no-data command-done, or timeout notification)
  • When notification is received, return data or error to caller

Command Timeout Handler

  • Clear CMD to low
  • Notify Do_EC_Command of timeout
  • Set NextState to Upstream

EC End

Init

  • Init SDI in host mode
  • Attach Acked() to ACK rising edge interrupt and enable the interrupt.
  • Attach CPUCommand() to CMD rising edge interrupt but disable the interrupt.
  • If ACK is high, enter Upstream state, otherwise enter CpuOff state

Acked() Interrupt Handler

  • Disable timeout
  • Advance state machine according to current state:
    • Upstream state: noop (shouldn't happen)
    • CpuOff state: set NextState to Upstream, enable CMD rising edge interrupt
    • Wait state: set NextState to Upstream, enable CMD rising edge interrupt
    • SwitchWait state: SPI_In(6, &command_buf) to get command packet, set NextState to PullWait
    • PullWait state: set NextState to (response length == 0 ? Upstream : Wait), RunCPUCommand(command_buf);

Timeout Interrupt Handler

  • Disable timeout
  • NextState = (ACK is high) ? Upstream : CpuOff

CPUCommand() Interrupt Handler

  • Disable CMD rising edge interrupt
  • Set NextState to SwitchWait
  • outbuf[0] = SWITCH; outbuf[1] = 0
  • SPI_Out(2,outbuf)

QueueInsert(channel, data)

  • If queue is not full, add channel number and data byte to it
  • Call TryRunQueue()

TryRunQueue()

  • If ACK is low, set NextState to CPUOff and exit. Otherwise ...
  • If queue is empty, exit. Otherwise ...
  • Disable interrupts
  • If state is not Upstream, reenable interrupts and exit. Otherwise ...
  • Disable CMD rising edge interrupt
  • Reenable interrupts
  • Deque channel to outbuf[0], data to outbuf[1]
  • Set NextState to Wait
  • SPI_Out(2,outbuf)

SPI_Out(nbytes, &buffer)

  • Enable timeout
  • Set SDICS# to low
  • While nbyes-- :
    • Write next byte to SHITBUF
    • Wait for SHICFG[7] = 1 (not busy) (wait time should be about 2 uS)
  • Set SDICS# to high

SPI_In(nbytes, &buffer)

  • Enable timeout
  • Set SDICS# to low
  • While nbytes-- :
    • *SHITBUF = 0;
    • Wait for SHICFG[7] = 1 (not busy) (wait time should be about 2 uS)
    • *buffer++ = *SHIRBUF;
  • Set SDICS# to high

RunCPUCommand(command_buf)

  • Do whatever is appropriate for the indicated command, calling SPI_Out(response_len, &response) if a response is called for.

OS and OFW Integration

This section discusses how this protocol can fit into the existing Linux and OFW frameworks. There are 4 cases to consider:

  • Keyboard
  • Mouse
  • SCI Events
  • Miscellaneous EC Commands

OFW Integration

OFW Keyboard

OFW's PC keyboard driver (dev/pckbd.fth) depends on its parent node driver (historically dev/i8042.fth) to transport data to and from the keyboard interface microcontroller (an I8042 or equivalent). That parent driver hides the details of accessing the I8042 via I/O ports 0x60 and 0x64 on (historically) ISA bus or (more recently) LPC bus.

As far as OLPC is concerned, the interface between pckbd.fth and i8042.fth boils down to two methods:

  • set-port ( port# -- ) Keyboard driver calls this with port#=0 to select the keyboard port instead of the mouse port
  • get-data? ( -- false | data true ) Polls to see if a keyboard byte is available

There are some other methods involving sending commands to the keyboard, but they are not used when the driver is compiled for OLPC. There are a couple of other methods "get-data" and "clear-out-buf" but they are just simple wrappers around "get-data?" with some timeouts thrown in.

So for the keyboard case, the EC-SPI protocol driver could easily replace the i8042 device node, exporting just the methods above.

OFW Mouse

OFW's mouse driver (dev/ps2mouse.fth) depends on the same i8042 parent node as the keyboard driver. In addition to the methods listed above, the mouse driver also needs the method:

  • put-data ( data -- )

(The driver also calls "put-get-data" but that is the same as calling "put-data" then "get-data", i.e. a trivial repackaging of the core functionality of "get-data?" and "put-data").

So for the mouse we can still use the strategy of replacing the i8042 device node with the EC-SPI protocol driver, with that new driver also exporting the "put-data" method.

We will need to define a new "SEND_PS2" EC command to send keyboard/mouse data downstream (using the EC-SPI protocol's downstream command mechanism). Inside the EC, that new command will have the same effect as if data were written to port 0x60 when IBF=0 in port 0x64. Actually, the new commands needs 2 arguments - port and data. If port is 0, the data goes to the keyboard. If port is 1, the data goes to the mouse (equivalent to writing 0xd4 to port 0x64, then writing the data to port 0x60).

OFW SCI Events

OFW doesn't respond to SCI events from the EC, so its EC-SPI driver can just discard them. Alternatively, if we should want to handle them for diagnostic purposes, we could write a child driver - a peer of pckbd and ps2mouse - to display them. This driver would call the same "set-port" and "get-data?" methods as the keyboard driver, with the port# value 2.

OFW EC Commands

The EC-SPI driver can export the following general purpose method to issue miscellaneous EC commands:

  • ec-command ( [ args ] #args #results cmd-code -- [ results ] error? )

Given that command, OFW's existing EC command code can be implemented easily. For example:

 0 value ec-spi-ih
 : call-ec  ( [ args ] #args #results cmd -- [ results ] )  " ec-command" ec-spi-ih $call-method throw  ;
 : ec-cmd  ( cmd -- )  0 0 rot  call-ec  ;
 : ec-cmd-b@  ( cmd -- b )  0 1 rot  call-ec  ;
 : ec-cmd-b!  ( b cmd -- )  1 0 rot  call-ec  ;
 : ec-cmd-w@  ( cmd -- w )  0 2 rot  call-ec  bwjoin  ;
 : ec-cmd-l!  ( l cmd -- )  >r lbsplit 4 0 r>  call-ec  ;
 : bat-voltage@  ( -- w )  h# 10 ec-cmd-w@  ;
 \ Most commands are implemented in terms of ec-cmd, ec-cmd-b@, ec-cmd-b!, and ec-cmd-w@
 : bat-gauge@  ( -- b )  h# 31 1 1 h# 18  call-ec  ;
 : ec-date!  ( day month year -- )  3 0 h# 1e call-ec  ;

What about cmd66? Should we define a new EC command to encapsulate cmd66, or just use the fact that all of the existing cmd66 codes are of the form 0xdX and none of the other codes are in that range? The cmd66 functions tend to turn things off, so it's probably best if the EC waits for the CPU's ACK after the 6-byte command block before executing the command.

Linux Integration

Linux Keyboard and Mouse

The Linux keyboard and mouse drivers use functions exported from drivers/input/serio/libps2.c, which in turn depends on a lower level driver implementing the "serio" abstraction. The "normal" I8042 implementation of "serio" is in drivers/input/serio/i8042.c. I think a good approach is to implement another serio driver, e.g. "olpcecspi.c". Like i8042.c, it would register two serio ports (by calling serio_register_port()), one for keyboard and one for mouse. It might also register a third serio port for SCI events.

The serio abstraction boils down to a write method and an interrupt handler:

  • serio->write(struct serio *port, unsigned char data) - This sends one byte of data to the associated port - so for our purposes it would just issue the new SEND_PS2 EC command.
  • serio_interrupt(struct serio *serio, unsigned char data, unsigned int flags) - This is a callback in the upstream direction - the olpcecspi driver registers a hardware interrupt handler which calls serio_interrupt to push the data upstream to the correct port.

Linux SCI Events

I'm not sure how SCI stuff works right now. My strawman idea is to register a third serio port whose upstream interrupt routine receives the SCI event notifications and does the Right Thing. I don't think there is any need for downstream communication for SCI events, so

Linux EC Commands

For EC commands, the serio abstraction doesn't match, so the olpcecspi.c module should directly export a function like:

  • error = ec_command(struct serio *, uchar cmd, int nargs, uchar argbuf[], int nres, uchar resuf[])

Perhaps the individual EC commands could be included in/exported from olpcecspi.c, or maybe they should be in a separate file.

Prototyping

We should be able to hook up a KB3930 chip to the MMP2 system for prototyping.

The SPI port is run to SSP3, at GPIO74 - 77 (GPIO74 is SCLK, GPIO75 is CS#, GPIO76 is MOSI, and GPIO77 is MISO.) The CMD signal is run to GPIO78 (TP42), and the ACK signal is run to GPIO79 (TP43).

Here is a table of SSP pinout options for the Armada 610 chip. The bold entry is my recommendation for the best place to connect the EC. It uses SSP3, which is the channel we intend for the final product, is otherwise unconnected, and offers easy soldering points on an accessible connector.

SSP Pinmux Options for MMP2
SSP GPIO MMP2 Usage MMP2 Connection points
SSP3 GPIO_132..134,135 CARD_DAT[2,1,0],CARD_CMD J6[1,14,13,4] (SD connector)
SSP1 GPIO_115..118 J11[19,21,23,25]
SSP3 GPIO_119..122 J11[27,29,31,122]
SSPA2 GPIO_25..28 I2S_BITCLK,SYNC,SDOUT,SDIN J11[46,48,50,52]
SSPA2 GPIO_33..36 SSPA2_CLK,FRM,TXD,RXD J11[34,36,38,40]
SSP1 GPIO_37..40 MMC2_DAT[3..0] J13[30,32,34,36]
SSP1 GPIO_43..46 TWSI2_SCL,SDA,WM8994_LDOEN,HMDI_DET J13[20,22],XX,XX
SSP2 GPIO_47..50 SSP2_CLK,FRM,RXD,GPS_STBY GPS chip U18[20,19,18,5]
SSP1,2 GPIO_55..58 WIFI_GPIO0,1,_PD_N,_RST_N J13[27,29,21,19] (wifi not installed)
SSP3 GPIO_74..77 LED_O,B,R,G R50, R51, R52, R60
SSP4 GPIO_78..81 GPIO_78,79,80,VBUS_FLT_N TP42,43,44 U11[6] (driven)

1.75 Command/Response List


TODO: Add descriptions

CMD_GET_API_VERSION			0x08	
CMD_READ_VOLTAGE			0x10
CMD_READ_CURRENT			0x11
CMD_READ_ACR				0x12
CMD_READ_BATT_TEMPERATURE		0x13
CMD_READ_AMBIENT_TEMPERATURE		0x14  ?? Maybe
CMD_READ_BATTERY_STATUS			0x15
CMD_READ_SOC				0x16
CMD_READ_GAUGE_ID			0x17
CMD_READ_GAUGE_DATA			0x18
CMD_READ_BOARD_ID			0x19
CMD_READ_BATT_ERR_CODE			0x1f
CMD_RESET_EC				0x28
CMD_READ_BATTERY_TYPE			0x2c
CMD_SET_AUTOWAK				0x33
CMD_SET_EC_WAKEUP_TIMER			0x36
CMD_READ_EXT_SCI_MASK			0x37
CMD_WRITE_EXT_SCI_MASK			0x38
CMD_CLEAR_EC_WAKEUP_TIMER		0x39
CMD_ENABLE_RUNIN_DISCHARGE		0x3B
CMD_DISABLE_RUNIN_DISCHARGE		0x3C
CMD_READ_MPPT_ACTIVE			0x3d
CMD_READ_MPPT_LIMIT			0x3e
CMD_SET_MPPT_LIMIT			0x3f
CMD_DISABLE_MPPT			0x40
CMD_ENABLE_MPPT				0x41
CMD_READ_VIN				0x42
CMD_EXT_SCI_QUERY			0x43
CMD_READ_LOCATION			0x44
CMD_WRITE_LOCATION			0x45
CMD_KEYBOARD_CMD			0x46
CMD_TOUCHPAD_CMD			0x47
RSP_KEYBOARD_DATA			0x48
RSP_TOUCHPAD_DATA			0x49
CMD_GET_FW_VER				0x4a
CMD_POWER_CYCLE				0x4b
CMD_POWER_OFF				0x4c
CMD_RESET_EC_SOFT			0x4d
CMD_READ_GUAGE_U16		    	0x4e
CMD_ENABLE_MOUSE                        0x4f
CMD_JUMP_TO_UPDATE                      0x50
CMD_UPDATE_FLASH                        0x51
CMD_ECHO                                0x52


Old Version

XO_1.75_HOST_to_EC_Protocol_-_Strawman documents an earlier idea for this protocol.