XO 1.75 HOST to EC Protocol

From OLPC
Revision as of 04:26, 25 May 2010 by 66.133.247.196 (talk) (added issues and implementation section)
Jump to: navigation, search

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.

Protocol

  • The EC is the SPI master; the CPU is the SPI slave.
  • Two GPIOs in the CPU to EC direction - CPU_RDY and CMD_RQST.
  • The CPU "speaks only when spoken to".
  • The EC can send data upstream whenever CPU_RDY is high except as described below.
  • 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 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 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.
  • 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

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

Issues

  • 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

CPU EC mux module

Init

  • Attach interrupt handler to SPI transaction done interrupt
  • Init SSC in SPI slave mode

Interrupt handler

  • Copy incoming packet from hardware to memory
  • Clear then set (pulse low) CPU_RDY
  • Switch on 3-bit channel field of first byte
    • Keyboard: Forward packet to keyboard module
    • Mouse: Forward packet to mouse module
    • Event: Forward packet to event module
    • Send Command: Write command code to Tx FIFO, clear CPU_RQST
    • Command Pulled: noop
    • Command Response: Forward packet to Send_Command handler
  • Return from interrupt

Do_EC_Command() function

  • Save command code for use by interrupt handler Send Command case
  • Schedule a timeout for 400 mS (EC unresponsive, shouldn't happen)
  • Set CPU_RQST
  • Sleep, waiting for a wakeup from Command Response case of interrupt handler
  • When response from Command Response received, cancel timeout and return data to caller
  • If timeout occurred, clear CPU_RQST and return error to caller (perhaps retry first?)

EC Behavior

Init

  • Init SDI in host mode
  • Attach CPUisReady() to CPU_RDY rising edge interrupt.
  • Attach CPUCommand() to CPU_RQST toggle interrupt.
  • UpstreamReady = (CPU_RDY is high) ? TRUE : FALSE;

= CPUisReady() Interrupt Handler

  • Set UpstreamReady to TRUE
  • Call TryRunQueue()

QueueInsert(packet)

  • If upstream queue is not full, add packet to it, otherwise drop the packet
  • Call TryRunQueue()

TryRunQueue()

  • If UpstreamReady is FALSE, exit. Otherwise ...
  • 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 upstream queue is not empty, deque packet and call SendPacket(packet).

SendPacket(packet)

  • Set UpstreamReady to FALSE
  • Set SDICS# to low
  • While there are more packet bytes to send:
    • Write next byte of packet to SHITBUF
    • Wait for SHICFG[7] = 1 (not busy) (Alternatively, set a timer interrupt to fire at about the right time)
  • 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

  • If CPU_RQST is high, set CommandIsRequested
  • Else (i.e. CPU_RQST is low) set CommandIsReady
  • Call TryRunQueue()

RunCPUCommand(command_byte)

  • Do whatever is appropriate for the indicated command, calling QueueInsert(response) if a response is necessary.

Alternate protocol with CPU as SPI master

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

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