XO 1.75 HOST to EC Protocol: Difference between revisions

From OLPC
Jump to navigation Jump to search
(Flow control mumbling)
(Tightened up protocol)
Line 1: Line 1:
Here is a swag at a reliable SPI protocol without races. This depends on the fact that "unsolicited" EC-to-CPU data (keyboard events) is more frequent than commands from the CPU, so the optimization should be for CPU load in the upstream (EC to CPU) case.
Here is a swag at a reliable, race-free protocol for SPI communication between the KB3930 EC and the Armada 610 CPU.


== Goals ==
The hard thing about SPI is flow control.


* 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.
How does the master know when it is okay to send more data - i.e. when the slave processor has accepted the previous data from the slave hardware interface and has thus freed up the hardware to accept more? The standard SPI signal set has no "backpressure" signal for flow control.
* 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.


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 packets are short - say 2 bytes - and the setup is EC master / CPU slave, it mostly won't be a problem, since the Rx FIFO has 16 entries, but nevertheless you are just hoping that the CPU is always keeping up rather than guaranteeing it by design.


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 CPU is master, the same problem exists and is potentially worse because the EC slave FIFO is only 4 bytes.


== Protocol Description and Specification ==
So we either have to guarantee that the data rate cannot exceed a rate that the receiver can keep up with - which is hard because characterizing the receiver's worst case behavior is difficult - or else add flow control using some other means.


==== Hardware Setup ====
The following protocol has these positive attributes:


* The EC is the SPI master, using the SDI interface in host mode.
* Guaranteed flow control
* The CPU is the SPI slave, using the SSP interface in slave mode.
* CPU only takes one interrupt per upstream packet in the usual case of keyboard and mouse data
* 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 ====
It has one disadvantage - complexity in the downstream case where the CPU is issuing a command to the EC. But despite the complexity, it should be entirely predictable and reliable.


* 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.)
* 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.


=== Protocol ===
==== The Command Case ====


* The EC is the SPI master; the CPU is the SPI slave.
* 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.
* Two GPIOs in the CPU to EC direction - CPU_RDY and CMD_RQST.
* 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.
* The CPU "speaks only when spoken to".
* 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.
* The EC can send data upstream whenever CPU_RDY is high except as described below.
* 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.
* An upstream data packet can be from 1 to 16 bytes (the CPU's RxFIFO is sixteen 32-bit entries, so maybe it could go up to 64 bytes, but 16 is probably more than enough).
* 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 first byte identifies the "data channel" - keyboard, mouse, event, send_command or command_response - and the number of following bytes.
* The CPU puts the SPI interface in device mode, sets up to interrupt when a completed frame is received, and sets CPU_RDY to let the EC know that it can fire at will.
* 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.
* The interrupt handler forwards the ustream frame to the appropriate module (keyboard, mouse, command, or event) depending on the data channel identifier in the first byte. The handler then clears CPU_RDY and immediately sets it, generating a rising edge.
* Both ends have long timeouts in case the other side fails to respond, as detailed in the complete state diagrams.
* When the EC sees the rising edge on CPU_RDY, it knows that the data has been received and can then send more data later. If the RDY edge is not received within 30 ms, but RDY is still high, the EC pretends that an edge was received and returns to the "ready to send more data" state.

Commands from the CPU to the EC require a 3-stage exchange, but are still reliable and predictable:

* When the CPU wants to send a command, it sets CMD_RQST.
* The EC sees the rising edge and responds by sending an upstream packet to the send_command channel. The EC enters "waiting for CMD" state and waits for either a falling edge on CMD_RQST or a 100 mS timeout.
* The CPU must respond within 100 mS by putting a command packet in its transmit FIFO and clearing CMD_RQST.
* When the EC sees CMD_RQST go low, it performs an SPI transaction to read the command packet. If CMD_RQST does not go low within 100 mS, the EC sends a "command aborted" response to the command channel and returns to "ready to send" state.
* The EC sends its response, if any, as a normal upstream packet addressed to the command channel.


==== 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 command case seems more complicated, but the Linux implementation is actually rather clean. The command module just tells the muxer module (interrupt handler) which command it wants to send. The muxer module sets CMD_RQST and then exits. When an interrupt happens and the upstream command is "please send", the muxer stuffs the command code in the Tx FIFO, clears CMD_RQST, and exits. Eventually another interrupt happens, and the muxer forwards the response to the command module.
* It's unnecessary to use the complex 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.
* 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.


==== Issues ====
=== State Diagrams ===

State diagrams will go here as soon as I finish drawing them nicely.


* In order to send multi-byte commands from the CPU (i.e. commands with arguments), it's necessary to either always send the maximum length packet from the CPU to the EC during the command sending phase, or to perform the CPU_RQST dance several times.
* I don't see an easy way to setup the CPU to interrupt at the end of a frame.
* The EC's SPI host interface doesn't appear to have a FIFO, so the EC has to send each upstream byte individually.


== Implementation Sketch ==
== Implementation Sketch ==


=== CPU EC mux module ===
=== CPU End ===


==== Init ====
==== Init ====


* Attach interrupt handler to SPI transaction done interrupt
* Attach interrupt handler to SPI transaction done interrupt
* Init SSC in SPI slave mode
* 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


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


* Clear then set (pulse low) ACK
* Copy incoming packet from hardware to memory
* Switch on state:
* Clear then set (pulse low) CPU_RDY
* Switch on 3-bit channel field of first byte
** Upstream state: Switch on channel number from first byte
** Keyboard: Forward packet to keyboard module
*** Keyboard: Forward second byte to keyboard module
** Mouse: Forward packet to mouse module
*** Mouse: Forward second byte to mouse module
** Event: Forward packet to event module
*** Event: Forward second byte to event module
** Send Command: Write command code to Tx FIFO, clear CPU_RQST
*** 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
** Command Pulled: noop
*** 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
** Command Response: Forward packet to Send_Command handler
*** 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
* 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
* Save command code for use by interrupt handler Send Command case
* Schedule a timeout for 400 mS (EC unresponsive, shouldn't happen)
* Schedule a one-shot timeout for 1 second (EC unresponsive, shouldn't happen)
* Set CPU_RQST
* Set CMD to high
* Sleep, waiting for a wakeup from Command Response case of interrupt handler
* Sleep, waiting for a notification (either response data, no-data command-done, or timeout notification)
* When response from Command Response received, cancel timeout and return data to caller
* When notification is received, return data or error to caller
* If timeout occurred, clear CPU_RQST and return error to caller (perhaps retry first?)


=== EC Behavior ===
==== Command Timeout Handler ====

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

=== EC End ===


==== Init ====
==== Init ====


* Init SDI in host mode
* Init SDI in host mode
* Attach CPUisReady() to CPU_RDY rising edge interrupt.
* Attach Acked() to ACK rising edge interrupt and enable the interrupt.
* Attach CPUCommand() to CPU_RQST toggle interrupt.
* Attach CPUCommand() to CMD rising edge interrupt but disable the interrupt.
* If ACK is high, enter Upstream state, otherwise enter CpuOff state
* UpstreamReady = (CPU_RDY is high) ? TRUE : FALSE;


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


* Disable timeout
* Set UpstreamReady to TRUE
* Advance state machine according to current state:
* Call TryRunQueue()
** 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);


==== QueueInsert(packet) ====
==== Timeout Interrupt Handler ====


* Disable timeout
* If upstream queue is not full, add packet to it, otherwise drop the packet
* 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()
* Call TryRunQueue()


==== TryRunQueue() ====
==== TryRunQueue() ====


* If UpstreamReady is FALSE, exit. Otherwise ...
* If queue is empty, exit. Otherwise ...
* Disable interrupts
* If CommandIsRequested is TRUE, call SendPacket(SEND_COMMAND_PACKET), schedule CPUCommandTimeout(), and exit. Otherwise ...
* If CommandIsReady is TRUE, call SendPacket(PULL_COMMAND_PACKET) and exit. Otherwise ...
* If state is not Upstream, reenable interrupts and exit. Otherwise ...
* Disable CMD rising edge interrupt
* If upstream queue is not empty, deque packet and call SendPacket(packet).
* Reenable interrupts
* Deque channel to outbuf[0], data to outbuf[1]
* Set NextState to Wait
* SPI_Out(2,outbuf)


==== SendPacket(packet) ====
==== SPI_Out(nbytes, &buffer) ====


* Enable timeout
* Set UpstreamReady to FALSE
* Set SDICS# to low
* Set SDICS# to low
* While there are more packet bytes to send:
* While nbyes-- :
** Write next byte of packet to SHITBUF
** Write next byte to SHITBUF
** Wait for SHICFG[7] = 1 (not busy) (Alternatively, set a timer interrupt to fire at about the right time)
** Wait for SHICFG[7] = 1 (not busy) (wait time should be about 2 uS)
* Set SDICS# to high
* Set SDICS# to high
* If the packet byte was PULL_COMMAND, read the CPU command byte from SHIRBUF and call RunCPUCommand(command_byte).


==== CPUCommand() Interrupt Handler ====
==== SPI_In(nbytes, &buffer) ====


* Enable timeout
* If CPU_RQST is high, set CommandIsRequested
* Set SDICS# to low
* Else (i.e. CPU_RQST is low) set CommandIsReady
* While nbytes-- :
* Call TryRunQueue()
** *SHITBUF = 0;

** Wait for SHICFG[7] = 1 (not busy) (wait time should be about 2 uS)
==== RunCPUCommand(command_byte) ====
** *buffer++ = *SHIRBUF;

* Set SDICS# to high
* Do whatever is appropriate for the indicated command, calling QueueInsert(response) if a response is necessary.

== Alternate protocol with CPU as SPI master ==


==== RunCPUCommand(command_buf) ====
This protocol is a bit simpler in the command case, but has more CPU overhead in the common case of keyboard/mouse events.


* Do whatever is appropriate for the indicated command, calling SPI_Out(response_len, &response) if a response is called for.
* The CPU is the SPI master, the EC is the slave.
* Two GPIOs in the EC-to-CPU direction - EC_ACK and EC_RQST
* The CPU can send a command to the EC at any time except when it is waiting for the EC's response to a previous command.
* Each command from the CPU to the EC is from 1 to 4 bytes, so it will always fit in the EC FIFO.
* Each command has a response from the EC, so the CPU can determine when it is okay to send another one.
* The EC uses its SDCS# rising edge interrupt (SDIRS[4]) to trigger its "incoming command" handler. That interrupt occurs after all command bytes have already been received and are thus waiting in the FIFO.
* The EC-to-CPU response format consists of from 0 to 3 data bytes. The CPU knows how many bytes to expect because each command has implicit response count. The entire response group fits entirely in the FIFO.
* When the EC has finished writing an upstream group into the FIFO, it clears EC_ACK then sets it, thus generating an edge. The CPU sees the edge detect status (either by polling or interrupting), then reads the 0 to 4 upstream data bytes.
* If the EC has unsolicited data to send to the CPU, it asserts EC_RQST. The CPU sees the rising edge and responds by sending a "ReceiveReady" command. The EC sends the unsolicited data as the response to that command.

Revision as of 00:28, 28 May 2010

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

Goals

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

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

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" - keyboard, mouse, event, or switch - 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 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.
  • 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.
  • 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

State diagrams will go here as soon as I finish drawing them nicely.


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

  • Clear then set (pulse low) ACK
  • 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 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.