summaryrefslogtreecommitdiff
path: root/chip/g/sps.c
diff options
context:
space:
mode:
Diffstat (limited to 'chip/g/sps.c')
-rw-r--r--chip/g/sps.c486
1 files changed, 486 insertions, 0 deletions
diff --git a/chip/g/sps.c b/chip/g/sps.c
new file mode 100644
index 0000000000..9d8ef5df3e
--- /dev/null
+++ b/chip/g/sps.c
@@ -0,0 +1,486 @@
+/* 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 "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.
+ */
+
+#define SPS_FIFO_SIZE (1 << 10)
+#define SPS_FIFO_MASK (SPS_FIFO_SIZE - 1)
+/*
+ * 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_SPI, outstr)
+#define CPRINTS(format, args...) cprints(CC_SPI, format, ## args)
+
+/*
+ * Push data to the SPS TX FIFO
+ * @param inst Interface number
+ * @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(uint32_t inst, 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;
+
+ if (GREAD_FIELD_I(SPS, inst, ISTATE, TXFIFO_EMPTY))
+ tx_empty_count++; /* Inside packet this means uderrun. */
+
+ 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 in 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)
+{
+ /* 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) = 0xff;
+
+ /* [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) = 8;
+
+ 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)
+{
+ if (sps_rx_handler)
+ return -1;
+
+ sps_rx_handler = rx_handler;
+ sps_configure(mode, SPI_CLOCK_MODE0);
+ task_enable_irq(GC_IRQNUM_SPS0_RXFIFO_LVL_INTR);
+ task_enable_irq(GC_IRQNUM_SPS0_CS_DEASSERT_INTR);
+
+ return 0;
+}
+
+int sps_unregister_rx_handler(void)
+{
+ if (!sps_rx_handler)
+ return -1;
+
+ task_disable_irq(GC_IRQNUM_SPS0_RXFIFO_LVL_INTR);
+ task_disable_irq(GC_IRQNUM_SPS0_CS_DEASSERT_INTR);
+
+ sps_rx_handler = NULL;
+ return 0;
+}
+
+static void sps_init(void)
+{
+ pmu_clock_en(PERIPH_SPS);
+}
+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 deasseted, 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;
+ 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(port, 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(port, 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);
+}
+
+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(uint32_t inst, 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;
+
+ rx_state = spstrx_not_started;
+ sps_register_rx_handler(SPS_GENERIC_MODE, sps_receive_callback);
+
+ 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
+ (0, test_frame + index, 8);
+ } else {
+ transmitted = sps_transmit
+ (0, 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);
+ }
+ }
+
+ sps_unregister_rx_handler();
+
+ 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",
+ NULL);
+#endif /* CONFIG_SPS_TEST */