Files
OpenCellular/chip/g/sps.c
Vadim Bendebury 5a6bb19a88 tpm: reset communications channels when resetting TPM
TPM resets happen asynchronously, conceivably there is some interface
(i2cs or sps) activity under way when TPM is reset.

Sps driver provides a means of disconnecting the client of the driver,
while the i2cs driver does not. Come to think of it, there is no real
need to provide a special function to disconnect a client, this makes
API simpler and allows to add driver initialization to the client
registration function.

To make tpm_registers.c more flexible - allow to register a callback
for interface initialization, this way when TPM is reset, the
interface can be also re-initialized and is guaranteed to start from
scratch after reset.

BRANCH=none
BUG=chrome-os-partner:52366
TEST=both firmware_TPMExtend and firmware_TPMKernelVersion autotests
     pass

Change-Id: I212166a23f9cd512d8f75315377d1f5620aea070
Signed-off-by: Vadim Bendebury <vbendeb@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/388886
Reviewed-by: Randall Spangler <rspangler@chromium.org>
Reviewed-by: Bill Richardson <wfrichar@chromium.org>
2016-09-26 22:16:45 -07:00

511 lines
14 KiB
C

/* Copyright 2015 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "common.h"
#include "console.h"
#include "hooks.h"
#include "pmu.h"
#include "registers.h"
#include "sps.h"
#include "system.h"
#include "task.h"
#include "timer.h"
#include "watchdog.h"
/*
* This file is a driver for the CR50 SPS (SPI slave) controller. The
* controller deploys a 2KB buffer split evenly between receive and transmit
* directions.
*
* Each one kilobyte of memory is organized into a FIFO with read
* and write pointers. RX FIFO write and TX FIFO read pointers are managed by
* hardware. RX FIFO read and TX FIFO write pointers are managed by
* software.
*
* As of time of writing, TX fifo allows only 32 bit wide write accesses,
* which makes the function feeding the FIFO unnecessarily complicated.
*
* Even though both FIFOs are 1KByte in size, the hardware pointers
* controlling access to the FIFOs are 11 bits in size, this is another issue
* requiring special software handling.
*
* The driver API includes three functions:
*
* - transmit a packet of a certain size, runs on the task context and can
* exit before the entire packet is transmitted.,
*
* - register a receive callback. The callback is running in interrupt
* context. Registering the callback (re)initializes the interface.
*
* - unregister receive callback.
*/
/*
* Hardware pointers use one extra bit, which means that indexing FIFO and
* values written into the pointers have to have dfferent sizes. Tracked under
* http://b/20894690
*/
#define SPS_FIFO_PTR_MASK ((SPS_FIFO_MASK << 1) | 1)
#define SPS_TX_FIFO_BASE_ADDR (GBASE(SPS) + 0x1000)
#define SPS_RX_FIFO_BASE_ADDR (SPS_TX_FIFO_BASE_ADDR + SPS_FIFO_SIZE)
/* SPS Statistic Counters */
static uint32_t sps_tx_count, sps_rx_count, tx_empty_count, max_rx_batch;
/* Console output macros */
#define CPUTS(outstr) cputs(CC_SPS, outstr)
#define CPRINTS(format, args...) cprints(CC_SPS, format, ## args)
void sps_tx_status(uint8_t byte)
{
GREG32(SPS, DUMMY_WORD) = byte;
}
unsigned sps_rx_fifo_wrptr(void)
{
return GREG32_I(SPS, 0, RXFIFO_WPTR) & SPS_FIFO_MASK;
}
/*
* Push data to the SPS TX FIFO
* @param data Pointer to 8-bit data
* @param data_size Number of bytes to transmit
* @return : actual number of bytes placed into tx fifo
*/
int sps_transmit(uint8_t *data, size_t data_size)
{
volatile uint32_t *sps_tx_fifo;
uint32_t rptr;
uint32_t wptr;
uint32_t fifo_room;
int bytes_sent;
int inst = 0;
if (GREAD_FIELD_I(SPS, inst, ISTATE, TXFIFO_EMPTY))
tx_empty_count++; /* Inside packet this means underrun. */
sps_tx_fifo = (volatile uint32_t *)SPS_TX_FIFO_BASE_ADDR;
wptr = GREG32_I(SPS, inst, TXFIFO_WPTR);
rptr = GREG32_I(SPS, inst, TXFIFO_RPTR);
fifo_room = (rptr - wptr - 1) & SPS_FIFO_MASK;
if (fifo_room < data_size) {
bytes_sent = fifo_room;
data_size = fifo_room;
} else {
bytes_sent = data_size;
}
sps_tx_fifo += (wptr & SPS_FIFO_MASK) / sizeof(*sps_tx_fifo);
while (data_size) {
if ((wptr & 3) || (data_size < 4) || ((uintptr_t)data & 3)) {
/*
* Either we have less then 4 bytes to send, or one of
* the pointers is not 4 byte aligned. Need to go byte
* by byte.
*/
uint32_t fifo_contents;
int bit_shift;
fifo_contents = *sps_tx_fifo;
do {
/*
* CR50 SPS controller does not allow byte
* accesses for writes into the FIFO, so read
* modify/write is requred. Tracked uder
* http://b/20894727
*/
bit_shift = 8 * (wptr & 3);
fifo_contents &= ~(0xff << bit_shift);
fifo_contents |=
(((uint32_t)(*data++)) << bit_shift);
data_size--;
wptr++;
} while (data_size && (wptr & 3));
*sps_tx_fifo++ = fifo_contents;
} else {
/*
* Both fifo wptr and data are aligned and there is
* plenty to send.
*/
*sps_tx_fifo++ = *((uint32_t *)data);
data += 4;
data_size -= 4;
wptr += 4;
}
GREG32_I(SPS, inst, TXFIFO_WPTR) = wptr & SPS_FIFO_PTR_MASK;
/* Make sure FIFO pointer wraps along with the index. */
if (!(wptr & SPS_FIFO_MASK))
sps_tx_fifo = (volatile uint32_t *)
SPS_TX_FIFO_BASE_ADDR;
}
/*
* Start TX if necessary. This happens after FIFO is primed, which
* helps aleviate TX underrun problems but introduces delay before
* data starts coming out.
*/
if (!GREAD_FIELD(SPS, FIFO_CTRL, TXFIFO_EN))
GWRITE_FIELD(SPS, FIFO_CTRL, TXFIFO_EN, 1);
sps_tx_count += bytes_sent;
return bytes_sent;
}
/** Configure the data transmission format
*
* @param mode Clock polarity and phase mode (0 - 3)
*
*/
static void sps_configure(enum sps_mode mode, enum spi_clock_mode clk_mode,
unsigned rx_fifo_threshold)
{
/* Disable All Interrupts */
GREG32(SPS, ICTRL) = 0;
GWRITE_FIELD(SPS, CTRL, MODE, mode);
GWRITE_FIELD(SPS, CTRL, IDLE_LVL, 0);
GWRITE_FIELD(SPS, CTRL, CPHA, clk_mode & 1);
GWRITE_FIELD(SPS, CTRL, CPOL, (clk_mode >> 1) & 1);
GWRITE_FIELD(SPS, CTRL, TXBITOR, 1); /* MSB first */
GWRITE_FIELD(SPS, CTRL, RXBITOR, 1); /* MSB first */
/* xfer 0xff when tx fifo is empty */
GREG32(SPS, DUMMY_WORD) = GC_SPS_DUMMY_WORD_DEFAULT;
/* [5,4,3] [2,1,0]
* RX{DIS, EN, RST} TX{DIS, EN, RST}
*/
GREG32(SPS, FIFO_CTRL) = 0x9;
/* wait for reset to self clear. */
while (GREG32(SPS, FIFO_CTRL) & 9)
;
/* Do not enable TX FIFO until we have something to send. */
GWRITE_FIELD(SPS, FIFO_CTRL, RXFIFO_EN, 1);
GREG32(SPS, RXFIFO_THRESHOLD) = rx_fifo_threshold;
GWRITE_FIELD(SPS, ICTRL, RXFIFO_LVL, 1);
/* Use CS_DEASSERT to retrieve all remaining bytes from RX FIFO. */
GWRITE_FIELD(SPS, ISTATE_CLR, CS_DEASSERT, 1);
GWRITE_FIELD(SPS, ICTRL, CS_DEASSERT, 1);
}
/*
* Register and unregister rx_handler. Side effects of registering the handler
* is reinitializing the interface.
*/
static rx_handler_f sps_rx_handler;
int sps_register_rx_handler(enum sps_mode mode, rx_handler_f rx_handler,
unsigned rx_fifo_threshold)
{
task_disable_irq(GC_IRQNUM_SPS0_RXFIFO_LVL_INTR);
task_disable_irq(GC_IRQNUM_SPS0_CS_DEASSERT_INTR);
if (!rx_fifo_threshold)
rx_fifo_threshold = 8; /* This is a sensible default. */
sps_rx_handler = rx_handler;
sps_configure(mode, SPI_CLOCK_MODE0, rx_fifo_threshold);
task_enable_irq(GC_IRQNUM_SPS0_RXFIFO_LVL_INTR);
task_enable_irq(GC_IRQNUM_SPS0_CS_DEASSERT_INTR);
return 0;
}
static void sps_init(void)
{
/*
* Check to see if slave SPI interface is required by the board before
* initializing it. If SPI option is not set, then just return.
*/
if (!(system_get_board_properties() & BOARD_SLAVE_CONFIG_SPI))
return;
pmu_clock_en(PERIPH_SPS);
/* The pinmux connections are preset, but we have to set IN/OUT */
GWRITE_FIELD(PINMUX, DIOA2_CTL, IE, 1); /* SPS_MOSI */
GWRITE_FIELD(PINMUX, DIOA6_CTL, IE, 1); /* SPS_CLK */
GWRITE_FIELD(PINMUX, DIOA10_CTL, IE, 0); /* SPS_MISO */
GWRITE_FIELD(PINMUX, DIOA12_CTL, IE, 1); /* SPS_CS_L */
/* Allow SPS_CS_L to wake from sleep */
GWRITE_FIELD(PINMUX, EXITEN0, DIOA12, 1); /* enable powerdown exit */
GWRITE_FIELD(PINMUX, EXITEDGE0, DIOA12, 1); /* edge sensitive */
GWRITE_FIELD(PINMUX, EXITINV0, DIOA12, 1); /* wake on low */
}
DECLARE_HOOK(HOOK_INIT, sps_init, HOOK_PRIO_DEFAULT);
/*****************************************************************************/
/* Interrupt handler stuff */
/*
* Check how much data is available in RX FIFO and return pointer to the
* available data and its size.
*
* @param inst Interface number
* @param data - pointer to set to the beginning of data in the fifo
* @return number of available bytes and the sets the pointer if number of
* bytes is non zero
*/
static int sps_check_rx(uint32_t inst, uint8_t **data)
{
uint32_t write_ptr = GREG32_I(SPS, inst, RXFIFO_WPTR) & SPS_FIFO_MASK;
uint32_t read_ptr = GREG32_I(SPS, inst, RXFIFO_RPTR) & SPS_FIFO_MASK;
if (read_ptr == write_ptr)
return 0;
*data = (uint8_t *)(SPS_RX_FIFO_BASE_ADDR + read_ptr);
if (read_ptr > write_ptr)
return SPS_FIFO_SIZE - read_ptr;
return write_ptr - read_ptr;
}
/* Advance RX FIFO read pointer after data has been read from the FIFO. */
static void sps_advance_rx(int port, int data_size)
{
uint32_t read_ptr = GREG32_I(SPS, port, RXFIFO_RPTR) + data_size;
GREG32_I(SPS, port, RXFIFO_RPTR) = read_ptr & SPS_FIFO_PTR_MASK;
}
/*
* Actual receive interrupt processing function. Invokes the callback passing
* it a pointer to the linear space in the RX FIFO and the number of bytes
* availabe at that address.
*
* If RX fifo is wrapping around, the callback will be called twice with two
* flat pointers.
*
* If the CS has been deasserted, after all remaining RX FIFO data has been
* passed to the callback, the callback is called one last time with zero data
* size and the CS indication, this allows the client to delineate received
* packets.
*/
static void sps_rx_interrupt(uint32_t port, int cs_deasserted)
{
for (;;) {
uint8_t *received_data = NULL;
size_t data_size;
data_size = sps_check_rx(port, &received_data);
if (!data_size)
break;
sps_rx_count += data_size;
if (sps_rx_handler)
sps_rx_handler(received_data, data_size, 0);
if (data_size > max_rx_batch)
max_rx_batch = data_size;
sps_advance_rx(port, data_size);
}
if (cs_deasserted)
sps_rx_handler(NULL, 0, 1);
}
static void sps_cs_deassert_interrupt(uint32_t port)
{
/* Make sure the receive FIFO is drained. */
sps_rx_interrupt(port, 1);
GWRITE_FIELD(SPS, ISTATE_CLR, CS_DEASSERT, 1);
GWRITE_FIELD(SPS, FIFO_CTRL, TXFIFO_EN, 0);
/*
* And transmit FIFO is emptied, so the next transaction doesn't start
* by clocking out any bytes left over from this one.
*/
GREG32(SPS, TXFIFO_WPTR) = GREG32(SPS, TXFIFO_RPTR);
}
void _sps0_interrupt(void)
{
sps_rx_interrupt(0, 0);
}
void _sps0_cs_deassert_interrupt(void)
{
sps_cs_deassert_interrupt(0);
}
DECLARE_IRQ(GC_IRQNUM_SPS0_CS_DEASSERT_INTR, _sps0_cs_deassert_interrupt, 1);
DECLARE_IRQ(GC_IRQNUM_SPS0_RXFIFO_LVL_INTR, _sps0_interrupt, 1);
#ifdef CONFIG_SPS_TEST
/* Function to test SPS driver. It expects the host to send SPI frames of size
* <size> (not exceeding 1100) of the following format:
*
* <size/256> <size%256> [<size> bytes of payload]
*
* Once the frame is received, it is sent back. The host can receive it and
* compare with the original.
*/
/*
* Receive callback implemets a simple state machine, it could be in one of
* three states: not started, receiving frame, frame finished.
*/
enum sps_test_rx_state {
spstrx_not_started,
spstrx_receiving,
spstrx_finished
};
static enum sps_test_rx_state rx_state;
static uint8_t test_frame[1100]; /* Storage for the received frame. */
/*
* To verify different alignment cases, the frame is saved in the buffer
* starting with a certain offset (in range 0..3).
*/
static size_t frame_base;
/*
* This is the index of the next location where received data will be added
* to. Points to the end of the received frame once it has been pulled in.
*/
static size_t frame_index;
static void sps_receive_callback(uint8_t *data, size_t data_size, int cs_status)
{
static size_t frame_size; /* Total size of the frame being received. */
size_t to_go; /* Number of bytes still to receive. */
if (rx_state == spstrx_not_started) {
if (data_size < 2)
return; /* Something went wrong.*/
frame_size = data[0] * 256 + data[1] + 2;
frame_base = (frame_base + 1) % 3;
frame_index = frame_base;
if ((frame_index + frame_size) <= sizeof(test_frame))
/* Enter 'receiving frame' state. */
rx_state = spstrx_receiving;
else
/*
* If we won't be able to receve this much, enter the
* 'frame finished' state.
*/
rx_state = spstrx_finished;
}
if (rx_state == spstrx_finished) {
/*
* If CS was deasserted (transitioned to 1) - prepare to start
* receiving the next frame.
*/
if (cs_status)
rx_state = spstrx_not_started;
return;
}
if (frame_size > data_size)
to_go = data_size;
else
to_go = frame_size;
memcpy(test_frame + frame_index, data, to_go);
frame_index += to_go;
frame_size -= to_go;
if (!frame_size)
rx_state = spstrx_finished; /* Frame finished.*/
}
static int command_sps(int argc, char **argv)
{
int count = 0;
int target = 10; /* Expect 10 frames by default.*/
char *e;
sps_tx_status(GC_SPS_DUMMY_WORD_DEFAULT);
rx_state = spstrx_not_started;
sps_register_rx_handler(SPS_GENERIC_MODE, sps_receive_callback, 0);
if (argc > 1) {
target = strtoi(argv[1], &e, 10);
if (*e)
return EC_ERROR_PARAM1;
}
while (count++ < target) {
size_t transmitted;
size_t to_go;
size_t index;
/* Wait for a frame to be received.*/
while (rx_state != spstrx_finished) {
watchdog_reload();
usleep(10);
}
/* Transmit the frame back to the host.*/
index = frame_base;
to_go = frame_index - frame_base;
do {
if ((index == frame_base) && (to_go > 8)) {
/*
* This is the first transmit attempt for this
* frame. Send a little just to prime the
* transmit FIFO.
*/
transmitted = sps_transmit
(test_frame + index, 8);
} else {
transmitted = sps_transmit
(test_frame + index, to_go);
}
index += transmitted;
to_go -= transmitted;
} while (to_go);
/*
* Wait for receive state machine to transition out of 'frame
* finised' state.
*/
while (rx_state == spstrx_finished) {
watchdog_reload();
usleep(10);
}
}
ccprintf("Processed %d frames\n", count - 1);
ccprintf("rx count %d, tx count %d, tx_empty %d, max rx batch %d\n",
sps_rx_count, sps_tx_count,
tx_empty_count, max_rx_batch);
sps_rx_count =
sps_tx_count =
tx_empty_count =
max_rx_batch = 0;
return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(spstest, command_sps,
"<num of frames>",
"Loop back frames (10 by default) back to the host");
#endif /* CONFIG_SPS_TEST */