XO 1.75 HOST to EC Protocol: Difference between revisions

From OLPC
Jump to navigation Jump to search
(Tightened up protocol)
No edit summary
 
(49 intermediate revisions by 9 users not shown)
Line 1: Line 1:
Here is a swag at a reliable, race-free protocol for SPI communication between the KB3930 EC and the Armada 610 CPU.
This is a reliable, race-free protocol for SPI communication between the KB3930 EC and the Armada 610 CPU. It is used for three things:
* Transmitting keyboard and touchpad data upstream from the EC to the CPU
* Invoking EC commands from the CPU
* Reprogramming the EC's private FLASH from the CPU (on systems where that is possible)


== Goals ==
== 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.
* The CPU 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
* 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
* Multiplex multiple "channels" - keyboard, mouse, events, command responses - in the EC-to-CPU "upstream" direction
Line 11: Line 14:
* 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.
* 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.
* 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.
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 wousld 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".
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 ==
== Protocol Description and Specification ==
Line 23: Line 27:
* The CPU is the SPI slave, using the SSP interface in slave 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".
* 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".

==== Meaning of ACK ====

ACK gives the EC - which is the SPI master - permission to run the next SPI transaction. The CPU generates a rising edge on ACK when it is ready for the next SPI transaction. In the usual case where the CPU is waiting for data from the EC, the ACK means that the CPU has primed its SPI transmit FIFO with N null bytes so that the EC can now send N data bytes - the null bytes will be sent downstream while the EC sends its data upstream. In the case where the CPU is sending data, the ACK means that the CPU has loaded its data into its transmit FIFO - the EC will send null bytes in the upstream direction to force the CPU's data to go downstream.

After running an SPI transaction, the EC must wait until the next rising edge on ACK before it is permitted to run another SPI transaction. This keeps the CPU and EC synchronized, so the EC knows that the CPU is ready.

The protocol is described with state machines on each end. At the EC end, state machine transitions occur when the EC receives an ACK (or, if it has already received an ACK that it has not used, when it needs to use it). At the CPU end, state transitions occur when the CPU notices (e.g. via an interrupt from the SPI hardware) that the EC has run the SPI transaction that the last ACK permitted. Both ends may also make transitions after long timeouts, for error recovery and resynchronization.

Both ends always know the expected length of the next SPI transaction. Usually that length is 2 - a short upstream (EC to CPU) packet containing a routing tag and a data byte. In the less frequent command case, the length is 8 - a downstream command packet. Even less frequently, the length is variable, containing either upstream or downstream data associated with a command. But in all cases, the length of the next transaction is known in advance, so the CPU can prepare its hardware and thus avoid FIFO underruns which can cause hardware misbehavior.


==== The Usual Upstream Case ====
==== 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.
* 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" - keyboard, mouse, event, or switch - and the second byte is the data for that channel. (We can define additional channels if necessary.)
* 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.
* 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 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.)
Line 35: Line 49:
==== The Command Case ====
==== The Command Case ====


* When the CPU wants to send a command, it sets the CMD line high.
# 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.
# (EC in ''Upstream'' state) The EC sees (via a rising-edge-triggered interrupt) 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.
# (CPU in ''Upstream'' state) When the CPU receives the "switch" packet it:
* The CPU receives the "switch" packet, loads a 6-byte command packet into its Tx FIFO, changes the interrupt threshold to 10, pulses ACK, and enters ''Switched'' state. The first byte of the 6 is the command code. The second contains two 4-bit field - command length and response length. The remaining bytes are the command argument bytes, padded with zeros as necessary to fill up 4 bytes.
#* Loads an 8-byte command packet (see [[Command_Packet_Format]]) into its Tx FIFO
* When the EC receives the ACK interrupt, it performs a 10-byte SPI transaction with null transmit data, in order to receive the 10-byte command packet. It tnen enters ''Pull Wait'' state.
#* Either clears CMD (if this is the last command in a group - non-sticky mode) or leaves CMD set (if more commands need to be sent in this same group - sticky mode)
* 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.
#* Changes its SPI FIFO interrupt threshold to 8
* 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.
#* Pulses ACK
* 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.
#* Enters ''Command'' state.
* The EC receives the ACK interrupt and returns to ''Upstream'' state.
# (EC in ''SwitchWait'' state) 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, and reads and saves the current state of the CMD pin. It then enters ''Pull Wait'' state.
* Both ends have long timeouts in case the other side fails to respond, as detailed in the complete state diagrams.
# (CPU in ''Command'' state) The CPU learns, via the FIFO interrupt, that the command has been transferred to the EC. The CPU's behavior depends on three factors - the synchronous data length, the synchronous data direction, and whether or not there are more commands in this group (sticky or non-sticky mode).
#* If the synchronous data length is nonzero and the synchronous data direction is EC-to-CPU, the CPU sets the FIFO interrupt threshold to the synchronous data length and enters ''Data'' state.
#* If the synchronous data length is nonzero and the synchronous data direction is CPU-to-EC, the CPU loads the synchronous data into the transmit FIFO, sets the FIFO interrupt threshold to the synchronous data length and enters ''Data'' state.
#* If the synchronous data length for that command is zero and the mode is non-sticky, 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 synchronous data length for that command is zero and the mode is sticky, the CPU sets up the next command in the group by performing the actions of step 3.
# (Ec in ''PullWait'' state) The EC receives the ACK interrupt. Its next behavior depends on the synchronous data length and the saved state of the CMD pin:
#* If the synchronous data length is nonzero, the EC enters ''Wait'' state (waiting for the CPU to be ready for the synchronous data).
#* If the synchronous data length for that command is 0 and the saved CMD state is 0, the EC executes the command and goes to ''Upstream'' state (ready to send more unsolicited data upstream).
#* If the synchronous data length for that command is 0 and the saved CMD state is 1, the EC executes the command and goes to ''SwitchWait'' state (ready to receive the next command).
# (CPU in ''Data'' state)
#* If the synchronous data direction is EC-to-CPU, the CPU receives the data packet and forwards the data to the invoking command. If the direction is CPU-to-EC, the CPU notices that the EC has received the data and notifies the invoking command of completion.
#** In sticky mode (more commands in the group), the CPU sets up the next command by performing the actions of step 3)
#** In non-sticky mode (no more commands), the CPU sets the FIFO interrupt threshold back to 2, pulses ACK, and returns to ''Upstream'' state.
# (EC in ''Wait'' state) When the EC receives the ACK interrupt:
#* If the synchronous data direction is CPU-to-EC, the EC runs the SPI transaction to receive the data, and executes the command with that incoming data. If the saved CMD state is 1, the EC goes to ''SwitchWait'' state, otherwise it goes to ''Upstream'' state.
#* If the synchronous data direction is EC-to-CPU, the EC executes the command and sends the resulting data to the CPU in an SPI transaction. If the saved CMD state is 1, the EC goes to ''SwitchWait'' state, otherwise it goes to ''Upstream'' state.
Both ends have long timeouts in case the other side fails to respond, as detailed in the complete state diagrams.

==== Command Packet Format ====

The 8 bytes of the command packet are:
* Byte 0 - command code
* Byte 1 - number (0..5) of argument (CPU to EC) bytes in bits 3..0, synchronous direction bit in bit 7 (EC-to-CPU=0, CPU-to-EC=1)
* Byte 2 - synchronous data length in bytes
* Bytes 3..7 - argument bytes, right-padded with 0x00 bytes for a total of 5 bytes

There are two major classes of commands - "normal" EC commands and "EC FLASH updater mode".

For normal EC commands, the synchronous data feature is not used, i.e. byte 2 is 0 and the synchronous data direction bit in byte 1 is 0. The command arguments, if any, are sent in bytes 3..7, with the low 4 bits of byte 1 telling how many of those argument bytes are valid. If few than 5 argument bytes are valid, the command must be padded with null bytes to the fixed length of 8 bytes, thus meeting the "known SPI transaction length" requirement. If the EC has response bytes to send as a result of executing the command, those bytes are sent one-at-a-time via the normal upstream method, tagged with the "command_response" channel number (2). The command execution code on both ends must implicitly know - and agree on - how many such response bytes result from each specific command. "Sticky mode", in which multiple commands can be issued without returning to ''Upstream'' state, is not needed for normal EC commands, and in fact is not appropriate for EC commands that return results via ''Upstream'' state.

EC FLASH updater mode uses both synchronous data and sticky mode. Synchronous data permits larger data transfers with less packet overhead, and sticky mode similarly reduces overhead from unnecessary state transitions. The net result is faster transfer of the bulk FLASH image data.


==== Observations ====
==== 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 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.
* The EC command case requires 2 interrupts for the command - one to mark the direction switch (so Linux knows when it is okay to load up the FIFO) and one to clean up after the EC pulls the command. Each response byte from the EC command requires an additional interrupt. EC commands are infrequent, and the interrupt handler is simple and fast, so this does not represent a significant aggregate interrupt load.
* 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.
* 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.
* Different code modules - the Linux kernel driver, OFW, the EC main code, and the EC FLASH updater - can implement only the pertinent protocol subset. In particular, the Linux driver and the EC main code can ignore everything related to synchronous data. The EC FLASH updater can ignore ''Upstream'' state and just cycle through the ''SwitchWait'', ''PullWait'', and ''Wait'' states. OFW must implement the complete protocol.
* 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 ===
=== 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''
State diagrams will go here as soon as I finish drawing them nicely.

{| class="wikitable"
|-
| [[Image: EC_SPI_states.svg | thumb | EC State Diagram]]
| [[Image: CPU_SPI_states.svg | thumb | 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 ==
== Implementation Sketch ==
Line 67: Line 128:
* Init SSP3 in SPI slave mode
* Init SSP3 in SPI slave mode
* Set SSP3 to interrupt when the Rx FIFO contains 2 bytes
* Set SSP3 to interrupt when the Rx FIFO contains 2 bytes
* Write 2 null bytes to SSP Tx FIFO
* Set SSP3 Receive Without Transmit to 1
* Clear CMD to low
* Clear CMD to low
* Set ACK to high
* Set ACK to low


==== SSP Interrupt handler ====
==== SSP Interrupt handler ====


* Clear then set (pulse low) ACK
* Switch on state:
* Switch on state:
** Upstream state: Switch on channel number from first byte
** Upstream state: Write 2 null bytes to Tx FIFO. Switch on channel number from first byte:
*** Keyboard: Forward second byte to keyboard module
*** Keyboard: Forward second byte to keyboard module
*** Mouse: Forward second byte to mouse module
*** Mouse: Forward second byte to mouse module
*** Command_response: Forward second byte to Do_EC_Command
*** EC_debug: Forward second byte to debug logger
*** Event: Forward second byte to event 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
*** Switch: Write 8-byte command frame to Tx FIFO, set FIFO threshold to 8, if !sticky clear CMD to low, set NextState to Command
** Switched state: Set SSP3 Receive Without Transmit to 1
** Command state: Notify Do_EC_Command that command has been sent
*** 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 synchronous data length is 0 and !sticky: Write 2 null bytes to TX FIFO, 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
*** If synchronous data length is 0 and sticky: Write next 8-byte command frame to TX FIFO, set FIFO threshold to 8, set CMD to low, cancel command timeout, set NextState to Command
** 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
*** If synchronous data length N is non0: If synchronous data direction is CPU-to-EC write N data bytes to TxFIFO else write N null bytes to TxFIFO, set FIFO threshold to N, set NextState to Data
** Data state: If synchronous data direction is EC-to-CPU give response data to Do_EC_Command
* Pulse ACK low then high
*** If sticky: Write next 8-byte command frame to TX FIFO, set FIFO threshold to 8, set CMD to low, cancel command timeout, set NextState to Command
*** If !sticky: Write 2 null bytes to TxFIFO, set FIFO threshold to 2, clear CMD to low, cancel command timeout, set NextState to Upstream
* Pulse ACK high then low
* Return from interrupt
* Return from interrupt


==== Do_EC_Command() function ====
==== Do_EC_Command() function ====


* Prepare a command packet containing the command code, expected response length, and command arguments
* Prepare a command packet containing the command code, argument length, and command arguments
* Schedule a one-shot timeout for 1 second (EC unresponsive, shouldn't happen)
* Schedule a one-shot timeout for 1 second (EC unresponsive, shouldn't happen)
* Set CMD to high
* Set CMD to high
* Sleep, waiting for a notification (either response data, no-data command-done, or timeout notification)
* Sleep, waiting for a notification (either command-done or timeout)
* If result bytes are expected, collect them byte-by-byte as they are delivered from upstream packets
* When notification is received, return data or error to caller
* When all bytes are received, return data or error to caller


==== Command Timeout Handler ====
==== Command Timeout Handler ====
Line 106: Line 172:


* Init SDI in host mode
* Init SDI in host mode
* Set "acked" variable to 0.
* Attach Acked() to ACK rising edge interrupt and enable the interrupt.
* Attach CPUCommand() to CMD rising edge interrupt but disable the interrupt.
* Attach Acked() to ACK falling edge interrupt and enable the interrupt.
* If ACK is high, enter Upstream state, otherwise enter CpuOff state
* If ACK is low, enter Upstream state, otherwise enter CpuOff state


==== Acked() Interrupt Handler ====
==== Acked() Interrupt Handler ====


* Set "acked" variable to 1
* Disable timeout
* Acknowledge interrupt
* 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 ====
==== Poll Function in Main Loop ====


* If "acked" is 0 and timeout has not occurred, return
* If "acked" is 0 and timeout has occurred, enter Upstream state if ACK is 1 or CpuOff state if ACK is 0.
* Otherwise, proceed as follows:
* Disable timeout
* Disable timeout
* Advance state machine according to current state:
* NextState = (ACK is high) ? Upstream : CpuOff
** Upstream state:

*** If CMD is 1, send SWITCH packet, clear "acked", and enter SwitchWait state
==== CPUCommand() Interrupt Handler ====
*** If CMD is 0 and upstream queue is empty, return
*** If CMD is 0 and upstream queue is not empty, clear "acked", deque channel# and byte, sent them upstream.
** CpuOff state: clear "acked", set NextState to Upstream
** Wait state: clear "acked". If "sticky" is set, set NextState to SwitchWait, otherwise set NextState to Upstream
** SwitchWait state: SPI_In(8, &command_buf) to get command packet, set "sticky" to the current state of CMD, set NextState to PullWait
** PullWait state: If synchronous data length = 0 and "sticky" = 0, set NextState to Upstream. If synchronous data length != 0 and direction = EC-to-CPU, RunCPUCommand(command_buf), send the result data with SPI_Out(), and set NextState to ("sticky" ? SwitchWait : Upstream). If synchronous data length != 0 and direction = CPU-to-EC, perform an SPI transaction to get the incoming data, RunCPUCommand(command_buf,data), and set NextState to ("sticky" ? SwitchWait : Upstream)


* 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 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) ====
==== SPI_Out(nbytes, &buffer) ====
Line 167: Line 217:
* Set SDICS# to high
* Set SDICS# to high


== OS and OFW Integration ==
==== RunCPUCommand(command_buf) ====

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

* Keyboard
* Mouse
* SCI Events
* Miscellaneous EC Commands
* EC FLASH updating

=== 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.

==== EC FLASH Updating ====

This is somewhat similar to the EC command case, except that it uses synchronous data to transfer the EC image. Look at the OFW code for more information. (This may become obsolete if we switch to a different EC chip.)

=== 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.

== 1.75 Command/Response List ==

In the table below, "Responses" means the number of bytes that the command sends upstream via the command response channel. As noted, some commands may result in data being sent upstream via other channels (keyboard or touchpad) or as synchronous data.

{| class="wikitable"
|-
! Name
! Cmd
! Args
! Responses
! Description
|-
| CMD_GET_API_VERSION
| 0x08
| 0
| 1
| Returns a version number representing the semantics of the current protocol commands.
|-
| CMD_READ_VOLTAGE
| 0x10
| 0
| 2
| Returns the raw battery voltage reading, lsb first.
|-
| CMD_READ_CURRENT
| 0x11
| 0
| 2
| Returns the raw battery current reading (signed, negative for discharge), lsb first.
|-
| CMD_READ_ACR
| 0x12
| 0
| 2
| Returns the current ACR (accumulated charge register) as a signed word, lsb first.
|-
| CMD_READ_BATT_TEMPERATURE
| 0x13
| 0
| 2
| Returns the raw battery temperature, lsb first.
|-
| CMD_READ_AMBIENT_TEMPERATURE
| 0x14
|
|
| ''Unimplemented -- no hardware sensor.''
|-
| CMD_READ_BATTERY_STATUS
| 0x15
| 0
| 1
| Bitmask, representing (in order from lsb to msb) battery present, battery full, battery low, battery error condition, external power supply detected, battery trickle charging, battery discharging, battery charging.
|-
| CMD_READ_SOC
| 0x16
| 0
| 1
| Returns percent State of Charge.
|-
| CMD_READ_GAUGE_ID
| 0x17
| 0
| 8
| Returns 8-byte battery serial number.
|-
| CMD_READ_GAUGE_DATA
| 0x18
| 1
| 1
| Returns contents of the indexed battery gauge byte.
|-
| CMD_READ_BOARD_ID
| 0x19
| 0
| 2
| Returns the 2-byte board ID code, LSB first, then MSB.
|-
| CMD_READ_BATT_ERR_CODE
| 0x1f
| 0
| 1
| Returns current battery error code.
|-
| CMD_SET_DCON_POWER
| 0x26
| 1
| 0
| Enables or disables power to the DCON.
|-
| CMD_RESET_EC
| 0x28
| 0
| 0
| Reboots the embedded controller. Useful after reprogramming the EC's FLASH.
|-
| CMD_READ_BATTERY_TYPE
| 0x2c
| 0
| 1
| Returns value representing type of installed battery. LiFe batteries are 0x22.
|-
| CMD_ENABLE_RUNIN_DISCHARGE
| 0x3B
| 0
| 0
| Put EC into runin test "discharge" mode. External power will be ignored.
|-
| CMD_DISABLE_RUNIN_DISCHARGE
| 0x3C
| 0
| 0
| Cancel runin test "discharge" mode.
|-
| CMD_READ_MPPT_ACTIVE
| 0x3d
| 0
| 1
| Return value indicates whether mppt is idle or not. ''Implemented but untested''
|-
| CMD_READ_MPPT_LIMIT
| 0x3e
| 0
| 2
| Returns raw PWM value which limits input current. ''Implemented but untested''
|-
| CMD_SET_MPPT_LIMIT
| 0x3f
| 2
| 0
| Sets PWM value which limits input current. ''Implemented but untested''
|-
| CMD_DISABLE_MPPT
| 0x40
| 0
| 0
| Disables mppt subsystem. ''Implemented but untested''
|-
| CMD_ENABLE_MPPT
| 0x41
| 0
| 0
| Enables mppt subsystem. ''Implemented but untested''
|-
| CMD_READ_VIN
| 0x42
|
|
| Returns external input voltage, in raw form.
|-
| CMD_GET_FW_VER
| 0x4a
| 0
| 16
| ASCII version string, null padded to 16 bytes.
|-
| CMD_POWER_CYCLE
| 0x4b
| 0
| 0
| Turns off power to the SoC, then turns it on.
|-
| CMD_POWER_OFF
| 0x4c
| 0
| 0
| Turns off power to the SoC.
|-
| CMD_RESET_EC_SOFT
| 0x4d
| 0
| 0
| A soft reset of the EC.
|-
| CMD_JUMP_TO_UPDATE
| 0x50
| 0
| 0*
| A1 boards only. Enters EC SPI FLASH updater mode. No data is returned via the command response channel, but, as specified in the command packet, one byte is returned as synchronous data. The value of that byte is 0x01. When the SoC receives that byte, it means that the updater has started and is ready to receive subsequent CMD_UPDATE_FLASH commands. CMD_JUMP_UPDATE should be sent in "sticky command mode", i.e. with CMD remaining asserted, so the updater does not return to the "Upstream" protocol state between commands.
|-
| CMD_UPDATE_FLASH
| 0x51
| 0-5
| 0*
| A1 boards only. General purpose pass-through command for communicating with the EC SPI FLASH updater. No results are returned via the command response channel, but synchronous data may be sent downstream or returned upstream as specified in the command packet. CMD_UPDATE_FLASH should be sent in "sticky command mode", i.e. with CMD remaining asserted, so the updater does not return to the "Upstream" protocol state between commands. When the sequence is done, the EC should be rebooted by issuing CMD_RESET_EC in non-sticky mode (with CMD deasserted).
|-
| CMD_ECHO
| 0x52
| 0-5
| 0-5
| Returns the N (0-5) argument bytes (via the command response channel).
|-
| CMD_GET_FW_DATE
| 0x53
| 0
| 16
| Build date, in ASCII form "YYYY/MM/DD-HH:MM".
|-
| CMD_GET_FW_USER
| 0x54
| 0
| 16
| ASCII "user" string, null padded to 16 bytes. Currently reports the builder's username, and the compiler used.
|-
| CMD_TURN_OFF_POWER
| 0x55
| 0
| 0
| Power will be shut off. (For a while numeric command was reserved as CMD_GET_FW_HASH, but had to be changed for backward compatibility.)
|-
| CMD_READ_OLS
| 0x56
| 0
| 2
| Returns current Outdoor [[Light Sensor]] (OLS) reading.
|-
| CMD_OLS_SMT_LEDON
| 0x57
| 0
| 0
| Stop the normal function of the OLS, and forward bias the sensor LED, causing it to glow, for testing. The host must use CMD_OLS_SMTTEST_STOP to resume.
|-
| CMD_OLS_SMT_LEDOFF
| 0x58
| 0
| 0
| Reverse bias the OLS LED, causing the LED to stop glowing. Does not change the state of the OLS function.
|-
| CMD_START_OLS_ASSY
| 0x59
| 0
| 0
| Cease the regular blanking of the nearby LED, but without stopping the OLS function. This intentionally distorts the OLS readings, for testing.
|-
| CMD_STOP_OLS_ASSY
| 0x5a
| 0
| 0
| Resume the regular blanking of the nearby LED.
|-
| CMD_OLS_SMTTEST_STOP
| 0x5b
| 0
| 0
| Resume the normal function of the OLS.
|-
| CMD_READ_VIN_SCALED
| 0x5c
| 0
| 2
| Returns the AC input voltage.
|-
| CMD_READ_BAT_MIN_W
| 0x5d
| 0
| 2
| Returns minimum battery power draw seen since last reset
|-
| CMD_READ_BAT_MAX_W
| 0x5e
| 0
| 2
| Returns maximum battery power draw seen since last reset
|-
| CMD_RESET_BAT_MINMAX_W
| 0x5f
| 0
| 0
| Resets min and max battery power draw values.
|-
| CMD_READ_LOCATION
| 0x60
| 4
| 1
| Returns up to 16 bytes of EC memory from either data or xdata space. Args are: 0/1 (xdata/data), addrlo, addrhi, count.
|-
| CMD_WRITE_LOCATION
| 0x61
| 4
| 0
| Writes 1 byte to EC data or xdata space. Args are: 0/1 (xdata/data), addrlo, addrhi, byte to be written.
|-
| CMD_KEYBOARD_CMD
| 0x62
| 1-5
| 0*
| No longer implemented. Sends the argument bytes to the keyboard via the PS/2 interface. No results are returned via the command response channel, but the bytes sent may cause the keyboard to send data upstream via the keyboard channel.
|-
| CMD_TOUCHPAD_CMD
| 0x63
| 1-5
| 0*
| No longer implemented. Sends the argument bytes to the touchpad via the PS/2 interface. No results are returned via the command response channel, but the bytes sent may cause the touchpad to send data upstream via the touchpad channel.
|-
| CMD_GET_FW_HASH
| 0x64
| 0
| 16
| ASCII string representing 7 character git hash, plus "-dirty" if the build tree wasn't clean when the build was done. Null padded to 16 bytes.
|-
| CMD_SUSPEND_HINT
| 0x65
| 1
| 0
| Tells EC we're suspending (input byte 1) or resuming (0). Pre-production boards weren't wired to tell the EC this.
|-
| CMD_ENABLE_WAKE_TIMER
| 0x66
| 1
| 0
| Enable the wakeup timer, value sent previously.
|-
| CMD_SET_WAKE_TIMER
| 0x67
| 4
| 0
| Timer value is a longword of milliseconds.
|-
| CMD_ENABLE_WAKE_AUTORESET
| 0x68
| 1
| 0
| The wakeup timer will autorepeat (arg is 1) or not (arg is 0) after it expires.
|-
| CMD_OLS_SET_LIMITS
| 0x69
| 4
| 0
| Sets the low and high hysteresis limits for the Outdoor [[Light Sensor]].
|-
| CMD_OLS_GET_LIMITS
| 0x6a
| 0
| 4
| Fetches the low and high hysteresis limits for the Outdoor Light Sensor.
|-
| CMD_OLS_SET_CEILING
| 0x6b
| 2
| 0
| Sets the highest OLS reading which will be captured.
|-
| CMD_OLS_GET_CEILING
| 0x6c
| 0
| 2
| Returns the previously set OLS reading limit.
|-
| CMD_SET_USB_POWER
| 0x6d
| 1
| 0
| Sets the USB hub power, 0=off, 1=on.
|}

== Old Version ==


[[XO_1.75_HOST_to_EC_Protocol_-_Strawman]] documents an earlier idea for this protocol.
* Do whatever is appropriate for the indicated command, calling SPI_Out(response_len, &response) if a response is called for.

Latest revision as of 02:23, 6 May 2016

This is a reliable, race-free protocol for SPI communication between the KB3930 EC and the Armada 610 CPU. It is used for three things:

  • Transmitting keyboard and touchpad data upstream from the EC to the CPU
  • Invoking EC commands from the CPU
  • Reprogramming the EC's private FLASH from the CPU (on systems where that is possible)

Goals and Assumptions

  • The CPU 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".

Meaning of ACK

ACK gives the EC - which is the SPI master - permission to run the next SPI transaction. The CPU generates a rising edge on ACK when it is ready for the next SPI transaction. In the usual case where the CPU is waiting for data from the EC, the ACK means that the CPU has primed its SPI transmit FIFO with N null bytes so that the EC can now send N data bytes - the null bytes will be sent downstream while the EC sends its data upstream. In the case where the CPU is sending data, the ACK means that the CPU has loaded its data into its transmit FIFO - the EC will send null bytes in the upstream direction to force the CPU's data to go downstream.

After running an SPI transaction, the EC must wait until the next rising edge on ACK before it is permitted to run another SPI transaction. This keeps the CPU and EC synchronized, so the EC knows that the CPU is ready.

The protocol is described with state machines on each end. At the EC end, state machine transitions occur when the EC receives an ACK (or, if it has already received an ACK that it has not used, when it needs to use it). At the CPU end, state transitions occur when the CPU notices (e.g. via an interrupt from the SPI hardware) that the EC has run the SPI transaction that the last ACK permitted. Both ends may also make transitions after long timeouts, for error recovery and resynchronization.

Both ends always know the expected length of the next SPI transaction. Usually that length is 2 - a short upstream (EC to CPU) packet containing a routing tag and a data byte. In the less frequent command case, the length is 8 - a downstream command packet. Even less frequently, the length is variable, containing either upstream or downstream data associated with a command. But in all cases, the length of the next transaction is known in advance, so the CPU can prepare its hardware and thus avoid FIFO underruns which can cause hardware misbehavior.

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

  1. When the CPU wants to send a command, it sets the CMD line high.
  2. (EC in Upstream state) The EC sees (via a rising-edge-triggered interrupt) 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.
  3. (CPU in Upstream state) When the CPU receives the "switch" packet it:
    • Loads an 8-byte command packet (see Command_Packet_Format) into its Tx FIFO
    • Either clears CMD (if this is the last command in a group - non-sticky mode) or leaves CMD set (if more commands need to be sent in this same group - sticky mode)
    • Changes its SPI FIFO interrupt threshold to 8
    • Pulses ACK
    • Enters Command state.
  4. (EC in SwitchWait state) 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, and reads and saves the current state of the CMD pin. It then enters Pull Wait state.
  5. (CPU in Command state) The CPU learns, via the FIFO interrupt, that the command has been transferred to the EC. The CPU's behavior depends on three factors - the synchronous data length, the synchronous data direction, and whether or not there are more commands in this group (sticky or non-sticky mode).
    • If the synchronous data length is nonzero and the synchronous data direction is EC-to-CPU, the CPU sets the FIFO interrupt threshold to the synchronous data length and enters Data state.
    • If the synchronous data length is nonzero and the synchronous data direction is CPU-to-EC, the CPU loads the synchronous data into the transmit FIFO, sets the FIFO interrupt threshold to the synchronous data length and enters Data state.
    • If the synchronous data length for that command is zero and the mode is non-sticky, 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 synchronous data length for that command is zero and the mode is sticky, the CPU sets up the next command in the group by performing the actions of step 3.
  6. (Ec in PullWait state) The EC receives the ACK interrupt. Its next behavior depends on the synchronous data length and the saved state of the CMD pin:
    • If the synchronous data length is nonzero, the EC enters Wait state (waiting for the CPU to be ready for the synchronous data).
    • If the synchronous data length for that command is 0 and the saved CMD state is 0, the EC executes the command and goes to Upstream state (ready to send more unsolicited data upstream).
    • If the synchronous data length for that command is 0 and the saved CMD state is 1, the EC executes the command and goes to SwitchWait state (ready to receive the next command).
  7. (CPU in Data state)
    • If the synchronous data direction is EC-to-CPU, the CPU receives the data packet and forwards the data to the invoking command. If the direction is CPU-to-EC, the CPU notices that the EC has received the data and notifies the invoking command of completion.
      • In sticky mode (more commands in the group), the CPU sets up the next command by performing the actions of step 3)
      • In non-sticky mode (no more commands), the CPU sets the FIFO interrupt threshold back to 2, pulses ACK, and returns to Upstream state.
  8. (EC in Wait state) When the EC receives the ACK interrupt:
    • If the synchronous data direction is CPU-to-EC, the EC runs the SPI transaction to receive the data, and executes the command with that incoming data. If the saved CMD state is 1, the EC goes to SwitchWait state, otherwise it goes to Upstream state.
    • If the synchronous data direction is EC-to-CPU, the EC executes the command and sends the resulting data to the CPU in an SPI transaction. If the saved CMD state is 1, the EC goes to SwitchWait state, otherwise it goes to Upstream state.

Both ends have long timeouts in case the other side fails to respond, as detailed in the complete state diagrams.

Command Packet Format

The 8 bytes of the command packet are:

  • Byte 0 - command code
  • Byte 1 - number (0..5) of argument (CPU to EC) bytes in bits 3..0, synchronous direction bit in bit 7 (EC-to-CPU=0, CPU-to-EC=1)
  • Byte 2 - synchronous data length in bytes
  • Bytes 3..7 - argument bytes, right-padded with 0x00 bytes for a total of 5 bytes

There are two major classes of commands - "normal" EC commands and "EC FLASH updater mode".

For normal EC commands, the synchronous data feature is not used, i.e. byte 2 is 0 and the synchronous data direction bit in byte 1 is 0. The command arguments, if any, are sent in bytes 3..7, with the low 4 bits of byte 1 telling how many of those argument bytes are valid. If few than 5 argument bytes are valid, the command must be padded with null bytes to the fixed length of 8 bytes, thus meeting the "known SPI transaction length" requirement. If the EC has response bytes to send as a result of executing the command, those bytes are sent one-at-a-time via the normal upstream method, tagged with the "command_response" channel number (2). The command execution code on both ends must implicitly know - and agree on - how many such response bytes result from each specific command. "Sticky mode", in which multiple commands can be issued without returning to Upstream state, is not needed for normal EC commands, and in fact is not appropriate for EC commands that return results via Upstream state.

EC FLASH updater mode uses both synchronous data and sticky mode. Synchronous data permits larger data transfers with less packet overhead, and sticky mode similarly reduces overhead from unnecessary state transitions. The net result is faster transfer of the bulk FLASH image data.

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 EC command case requires 2 interrupts for the command - one to mark the direction switch (so Linux knows when it is okay to load up the FIFO) and one to clean up after the EC pulls the command. Each response byte from the EC command requires an additional interrupt. EC commands are infrequent, and the interrupt handler is simple and fast, so this does not represent a significant aggregate interrupt load.
  • 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.
  • Different code modules - the Linux kernel driver, OFW, the EC main code, and the EC FLASH updater - can implement only the pertinent protocol subset. In particular, the Linux driver and the EC main code can ignore everything related to synchronous data. The EC FLASH updater can ignore Upstream state and just cycle through the SwitchWait, PullWait, and Wait states. OFW must implement the complete protocol.

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
  • Write 2 null bytes to SSP Tx FIFO
  • Clear CMD to low
  • Set ACK to low

SSP Interrupt handler

  • Switch on state:
    • Upstream state: Write 2 null bytes to Tx FIFO. Switch on channel number from first byte:
      • Keyboard: Forward second byte to keyboard module
      • Mouse: Forward second byte to mouse module
      • Command_response: Forward second byte to Do_EC_Command
      • EC_debug: Forward second byte to debug logger
      • Event: Forward second byte to event module
      • Switch: Write 8-byte command frame to Tx FIFO, set FIFO threshold to 8, if !sticky clear CMD to low, set NextState to Command
    • Command state: Notify Do_EC_Command that command has been sent
      • If synchronous data length is 0 and !sticky: Write 2 null bytes to TX FIFO, set FIFO threshold to 2, clear CMD to low, cancel command timeout, set NextState to Upstream
      • If synchronous data length is 0 and sticky: Write next 8-byte command frame to TX FIFO, set FIFO threshold to 8, set CMD to low, cancel command timeout, set NextState to Command
      • If synchronous data length N is non0: If synchronous data direction is CPU-to-EC write N data bytes to TxFIFO else write N null bytes to TxFIFO, set FIFO threshold to N, set NextState to Data
    • Data state: If synchronous data direction is EC-to-CPU give response data to Do_EC_Command
      • If sticky: Write next 8-byte command frame to TX FIFO, set FIFO threshold to 8, set CMD to low, cancel command timeout, set NextState to Command
      • If !sticky: Write 2 null bytes to TxFIFO, set FIFO threshold to 2, clear CMD to low, cancel command timeout, set NextState to Upstream
  • Pulse ACK high then low
  • Return from interrupt

Do_EC_Command() function

  • Prepare a command packet containing the command code, argument 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 command-done or timeout)
  • If result bytes are expected, collect them byte-by-byte as they are delivered from upstream packets
  • When all bytes are 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
  • Set "acked" variable to 0.
  • Attach Acked() to ACK falling edge interrupt and enable the interrupt.
  • If ACK is low, enter Upstream state, otherwise enter CpuOff state

Acked() Interrupt Handler

  • Set "acked" variable to 1
  • Acknowledge interrupt

Poll Function in Main Loop

  • If "acked" is 0 and timeout has not occurred, return
  • If "acked" is 0 and timeout has occurred, enter Upstream state if ACK is 1 or CpuOff state if ACK is 0.
  • Otherwise, proceed as follows:
  • Disable timeout
  • Advance state machine according to current state:
    • Upstream state:
      • If CMD is 1, send SWITCH packet, clear "acked", and enter SwitchWait state
      • If CMD is 0 and upstream queue is empty, return
      • If CMD is 0 and upstream queue is not empty, clear "acked", deque channel# and byte, sent them upstream.
    • CpuOff state: clear "acked", set NextState to Upstream
    • Wait state: clear "acked". If "sticky" is set, set NextState to SwitchWait, otherwise set NextState to Upstream
    • SwitchWait state: SPI_In(8, &command_buf) to get command packet, set "sticky" to the current state of CMD, set NextState to PullWait
    • PullWait state: If synchronous data length = 0 and "sticky" = 0, set NextState to Upstream. If synchronous data length != 0 and direction = EC-to-CPU, RunCPUCommand(command_buf), send the result data with SPI_Out(), and set NextState to ("sticky" ? SwitchWait : Upstream). If synchronous data length != 0 and direction = CPU-to-EC, perform an SPI transaction to get the incoming data, RunCPUCommand(command_buf,data), and set NextState to ("sticky" ? SwitchWait : Upstream)


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

OS and OFW Integration

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

  • Keyboard
  • Mouse
  • SCI Events
  • Miscellaneous EC Commands
  • EC FLASH updating

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.

EC FLASH Updating

This is somewhat similar to the EC command case, except that it uses synchronous data to transfer the EC image. Look at the OFW code for more information. (This may become obsolete if we switch to a different EC chip.)

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.

1.75 Command/Response List

In the table below, "Responses" means the number of bytes that the command sends upstream via the command response channel. As noted, some commands may result in data being sent upstream via other channels (keyboard or touchpad) or as synchronous data.

Name Cmd Args Responses Description
CMD_GET_API_VERSION 0x08 0 1 Returns a version number representing the semantics of the current protocol commands.
CMD_READ_VOLTAGE 0x10 0 2 Returns the raw battery voltage reading, lsb first.
CMD_READ_CURRENT 0x11 0 2 Returns the raw battery current reading (signed, negative for discharge), lsb first.
CMD_READ_ACR 0x12 0 2 Returns the current ACR (accumulated charge register) as a signed word, lsb first.
CMD_READ_BATT_TEMPERATURE 0x13 0 2 Returns the raw battery temperature, lsb first.
CMD_READ_AMBIENT_TEMPERATURE 0x14 Unimplemented -- no hardware sensor.
CMD_READ_BATTERY_STATUS 0x15 0 1 Bitmask, representing (in order from lsb to msb) battery present, battery full, battery low, battery error condition, external power supply detected, battery trickle charging, battery discharging, battery charging.
CMD_READ_SOC 0x16 0 1 Returns percent State of Charge.
CMD_READ_GAUGE_ID 0x17 0 8 Returns 8-byte battery serial number.
CMD_READ_GAUGE_DATA 0x18 1 1 Returns contents of the indexed battery gauge byte.
CMD_READ_BOARD_ID 0x19 0 2 Returns the 2-byte board ID code, LSB first, then MSB.
CMD_READ_BATT_ERR_CODE 0x1f 0 1 Returns current battery error code.
CMD_SET_DCON_POWER 0x26 1 0 Enables or disables power to the DCON.
CMD_RESET_EC 0x28 0 0 Reboots the embedded controller. Useful after reprogramming the EC's FLASH.
CMD_READ_BATTERY_TYPE 0x2c 0 1 Returns value representing type of installed battery. LiFe batteries are 0x22.
CMD_ENABLE_RUNIN_DISCHARGE 0x3B 0 0 Put EC into runin test "discharge" mode. External power will be ignored.
CMD_DISABLE_RUNIN_DISCHARGE 0x3C 0 0 Cancel runin test "discharge" mode.
CMD_READ_MPPT_ACTIVE 0x3d 0 1 Return value indicates whether mppt is idle or not. Implemented but untested
CMD_READ_MPPT_LIMIT 0x3e 0 2 Returns raw PWM value which limits input current. Implemented but untested
CMD_SET_MPPT_LIMIT 0x3f 2 0 Sets PWM value which limits input current. Implemented but untested
CMD_DISABLE_MPPT 0x40 0 0 Disables mppt subsystem. Implemented but untested
CMD_ENABLE_MPPT 0x41 0 0 Enables mppt subsystem. Implemented but untested
CMD_READ_VIN 0x42 Returns external input voltage, in raw form.
CMD_GET_FW_VER 0x4a 0 16 ASCII version string, null padded to 16 bytes.
CMD_POWER_CYCLE 0x4b 0 0 Turns off power to the SoC, then turns it on.
CMD_POWER_OFF 0x4c 0 0 Turns off power to the SoC.
CMD_RESET_EC_SOFT 0x4d 0 0 A soft reset of the EC.
CMD_JUMP_TO_UPDATE 0x50 0 0* A1 boards only. Enters EC SPI FLASH updater mode. No data is returned via the command response channel, but, as specified in the command packet, one byte is returned as synchronous data. The value of that byte is 0x01. When the SoC receives that byte, it means that the updater has started and is ready to receive subsequent CMD_UPDATE_FLASH commands. CMD_JUMP_UPDATE should be sent in "sticky command mode", i.e. with CMD remaining asserted, so the updater does not return to the "Upstream" protocol state between commands.
CMD_UPDATE_FLASH 0x51 0-5 0* A1 boards only. General purpose pass-through command for communicating with the EC SPI FLASH updater. No results are returned via the command response channel, but synchronous data may be sent downstream or returned upstream as specified in the command packet. CMD_UPDATE_FLASH should be sent in "sticky command mode", i.e. with CMD remaining asserted, so the updater does not return to the "Upstream" protocol state between commands. When the sequence is done, the EC should be rebooted by issuing CMD_RESET_EC in non-sticky mode (with CMD deasserted).
CMD_ECHO 0x52 0-5 0-5 Returns the N (0-5) argument bytes (via the command response channel).
CMD_GET_FW_DATE 0x53 0 16 Build date, in ASCII form "YYYY/MM/DD-HH:MM".
CMD_GET_FW_USER 0x54 0 16 ASCII "user" string, null padded to 16 bytes. Currently reports the builder's username, and the compiler used.
CMD_TURN_OFF_POWER 0x55 0 0 Power will be shut off. (For a while numeric command was reserved as CMD_GET_FW_HASH, but had to be changed for backward compatibility.)
CMD_READ_OLS 0x56 0 2 Returns current Outdoor Light Sensor (OLS) reading.
CMD_OLS_SMT_LEDON 0x57 0 0 Stop the normal function of the OLS, and forward bias the sensor LED, causing it to glow, for testing. The host must use CMD_OLS_SMTTEST_STOP to resume.
CMD_OLS_SMT_LEDOFF 0x58 0 0 Reverse bias the OLS LED, causing the LED to stop glowing. Does not change the state of the OLS function.
CMD_START_OLS_ASSY 0x59 0 0 Cease the regular blanking of the nearby LED, but without stopping the OLS function. This intentionally distorts the OLS readings, for testing.
CMD_STOP_OLS_ASSY 0x5a 0 0 Resume the regular blanking of the nearby LED.
CMD_OLS_SMTTEST_STOP 0x5b 0 0 Resume the normal function of the OLS.
CMD_READ_VIN_SCALED 0x5c 0 2 Returns the AC input voltage.
CMD_READ_BAT_MIN_W 0x5d 0 2 Returns minimum battery power draw seen since last reset
CMD_READ_BAT_MAX_W 0x5e 0 2 Returns maximum battery power draw seen since last reset
CMD_RESET_BAT_MINMAX_W 0x5f 0 0 Resets min and max battery power draw values.
CMD_READ_LOCATION 0x60 4 1 Returns up to 16 bytes of EC memory from either data or xdata space. Args are: 0/1 (xdata/data), addrlo, addrhi, count.
CMD_WRITE_LOCATION 0x61 4 0 Writes 1 byte to EC data or xdata space. Args are: 0/1 (xdata/data), addrlo, addrhi, byte to be written.
CMD_KEYBOARD_CMD 0x62 1-5 0* No longer implemented. Sends the argument bytes to the keyboard via the PS/2 interface. No results are returned via the command response channel, but the bytes sent may cause the keyboard to send data upstream via the keyboard channel.
CMD_TOUCHPAD_CMD 0x63 1-5 0* No longer implemented. Sends the argument bytes to the touchpad via the PS/2 interface. No results are returned via the command response channel, but the bytes sent may cause the touchpad to send data upstream via the touchpad channel.
CMD_GET_FW_HASH 0x64 0 16 ASCII string representing 7 character git hash, plus "-dirty" if the build tree wasn't clean when the build was done. Null padded to 16 bytes.
CMD_SUSPEND_HINT 0x65 1 0 Tells EC we're suspending (input byte 1) or resuming (0). Pre-production boards weren't wired to tell the EC this.
CMD_ENABLE_WAKE_TIMER 0x66 1 0 Enable the wakeup timer, value sent previously.
CMD_SET_WAKE_TIMER 0x67 4 0 Timer value is a longword of milliseconds.
CMD_ENABLE_WAKE_AUTORESET 0x68 1 0 The wakeup timer will autorepeat (arg is 1) or not (arg is 0) after it expires.
CMD_OLS_SET_LIMITS 0x69 4 0 Sets the low and high hysteresis limits for the Outdoor Light Sensor.
CMD_OLS_GET_LIMITS 0x6a 0 4 Fetches the low and high hysteresis limits for the Outdoor Light Sensor.
CMD_OLS_SET_CEILING 0x6b 2 0 Sets the highest OLS reading which will be captured.
CMD_OLS_GET_CEILING 0x6c 0 2 Returns the previously set OLS reading limit.
CMD_SET_USB_POWER 0x6d 1 0 Sets the USB hub power, 0=off, 1=on.

Old Version

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