coreboot-libre-fam15h-rdimm/3rdparty/chromeec/chip/g/sps.c

562 lines
15 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 "gpio.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 different 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)
/* Flag indicating if there has been any data received while CS was asserted. */
static uint8_t seen_data;
void sps_tx_status(uint8_t byte)
{
GREG32(SPS, DUMMY_WORD) = byte;
}
/*
* 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 required. Tracked under
* 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 alleviate 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;
}
static int sps_cs_asserted(void)
{
/*
* Read the current value on the SPS CS line and return the iversion
* of it (CS is active low).
*/
return !GREAD_FIELD(SPS, VAL, CSB);
}
/** 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);
seen_data = 0;
/* 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_handler)
return 0;
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 (!board_tpm_uses_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
* available 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;
seen_data = 1;
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) {
if (seen_data) {
sps_rx_handler(NULL, 0, 1);
/*
* Signal the AP that this SPI frame processing is
* completed.
*/
gpio_set_level(GPIO_INT_AP_L, 0);
gpio_set_level(GPIO_INT_AP_L, 1);
seen_data = 0;
}
}
}
static void sps_cs_deassert_interrupt(uint32_t port)
{
if (sps_cs_asserted()) {
/*
* we must have been slow, this is the next CS assertion after
* the 'wake up' pulse, but we have not processed the wake up
* interrupt yet.
*
* There would be no other out of order CS assertions, as all
* the 'real' ones (as opposed to the wake up pulses) are
* confirmed by the H1 pulsing the AP interrupt line
*/
/*
* Make sure we react to the next deassertion when it
* happens.
*/
GWRITE_FIELD(SPS, ISTATE_CLR, CS_DEASSERT, 1);
GWRITE_FIELD(SPS, FIFO_CTRL, TXFIFO_EN, 0);
if (sps_cs_asserted())
return;
/*
* The CS went away while we were processing this interrupt,
* this was the 'real' CS, need to process data.
*/
}
/* 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 implements 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 receive 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
* finished' 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 */