diff options
Diffstat (limited to 'chip/g/spp.c')
-rw-r--r-- | chip/g/spp.c | 616 |
1 files changed, 616 insertions, 0 deletions
diff --git a/chip/g/spp.c b/chip/g/spp.c new file mode 100644 index 0000000000..377d11d296 --- /dev/null +++ b/chip/g/spp.c @@ -0,0 +1,616 @@ +/* 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 "spp.h" +#include "system.h" +#include "task.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 SPP_FIFO_PTR_MASK ((SPP_FIFO_MASK << 1) | 1) + +#define SPP_TX_FIFO_BASE_ADDR (GBASE(SPS) + 0x1000) +#define SPP_RX_FIFO_BASE_ADDR (SPP_TX_FIFO_BASE_ADDR + SPP_FIFO_SIZE) + +/* SPP Statistic Counters */ +static uint32_t spp_tx_count, spp_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; + +static bool int_ap_extension_enabled_; + +void spp_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 spp_transmit(uint8_t *data, size_t data_size) +{ + volatile uint32_t *spp_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. */ + + spp_tx_fifo = (volatile uint32_t *)SPP_TX_FIFO_BASE_ADDR; + + wptr = GREG32_I(SPS, inst, TXFIFO_WPTR); + rptr = GREG32_I(SPS, inst, TXFIFO_RPTR); + fifo_room = (rptr - wptr - 1) & SPP_FIFO_MASK; + + if (fifo_room < data_size) { + bytes_sent = fifo_room; + data_size = fifo_room; + } else { + bytes_sent = data_size; + } + + spp_tx_fifo += (wptr & SPP_FIFO_MASK) / sizeof(*spp_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 = *spp_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)); + + *spp_tx_fifo++ = fifo_contents; + } else { + /* + * Both fifo wptr and data are aligned and there is + * plenty to send. + */ + *spp_tx_fifo++ = *((uint32_t *)data); + data += 4; + data_size -= 4; + wptr += 4; + } + GREG32_I(SPS, inst, TXFIFO_WPTR) = wptr & SPP_FIFO_PTR_MASK; + + /* Make sure FIFO pointer wraps along with the index. */ + if (!(wptr & SPP_FIFO_MASK)) + spp_tx_fifo = (volatile uint32_t *) + SPP_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); + + spp_tx_count += bytes_sent; + return bytes_sent; +} + +static int spp_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 spp_configure(enum spp_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); +} + +static void enable_cs_assert_irq_(void) +{ + GWRITE_FIELD(SPS, ISTATE_CLR, CS_ASSERT, 1); + GWRITE_FIELD(SPS, ICTRL, CS_ASSERT, 1); + + task_enable_irq(GC_IRQNUM_SPS0_CS_ASSERT_INTR); +} + +/* + * Register and unregister rx_handler. Side effects of registering the handler + * is reinitializing the interface. + */ +static rx_handler_f spp_rx_handler; + +int spp_register_rx_handler(enum spp_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 (int_ap_extension_enabled_) { + task_disable_irq(GC_IRQNUM_SPS0_CS_ASSERT_INTR); + int_ap_extension_stop_pulse(); + } + + if (!rx_handler) + return 0; + + if (!rx_fifo_threshold) + rx_fifo_threshold = 8; /* This is a sensible default. */ + spp_rx_handler = rx_handler; + + spp_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); + + if (int_ap_extension_enabled_) + enable_cs_assert_irq_(); + + return 0; +} + +/* Function that sets up for SPS to enable INT_AP_L extension. */ +static void spp_int_ap_extension_enable_(void) +{ + enable_cs_assert_irq_(); + + int_ap_extension_enabled_ = true; +} + +static void spp_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_SPP); + + /* 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 */ + + /* Configure the SPS_CS_L signal, DIOA12, as wake falling */ + gpio_set_wakepin(GPIO_STRAP_B1, GPIO_HIB_WAKE_FALLING); + + int_ap_register(spp_int_ap_extension_enable_); +} +DECLARE_HOOK(HOOK_INIT, spp_init, HOOK_PRIO_INIT_CR50_BOARD - 1); + +/*****************************************************************************/ +/* 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 spp_check_rx(uint32_t inst, uint8_t **data) +{ + uint32_t write_ptr = GREG32_I(SPS, inst, RXFIFO_WPTR) & SPP_FIFO_MASK; + uint32_t read_ptr = GREG32_I(SPS, inst, RXFIFO_RPTR) & SPP_FIFO_MASK; + + if (read_ptr == write_ptr) + return 0; + + *data = (uint8_t *)(SPP_RX_FIFO_BASE_ADDR + read_ptr); + + if (read_ptr > write_ptr) + return SPP_FIFO_SIZE - read_ptr; + + return write_ptr - read_ptr; +} + +/* Advance RX FIFO read pointer after data has been read from the FIFO. */ +static void spp_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 & SPP_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. + * + * Returns a Boolean indicating if data was seen during the most recent CS + * assertion. When 1, it indicates to the caller that the confirmation + * pulse to the AP needs to be generated. + */ +static uint32_t spp_rx_interrupt(uint32_t port, int cs_deasserted) +{ + uint32_t pulse_needed = 0; + + for (;;) { + uint8_t *received_data = NULL; + size_t data_size; + + data_size = spp_check_rx(port, &received_data); + if (!data_size) + break; + + seen_data = 1; + spp_rx_count += data_size; + + if (spp_rx_handler) + spp_rx_handler(received_data, data_size, 0); + + if (data_size > max_rx_batch) + max_rx_batch = data_size; + + spp_advance_rx(port, data_size); + } + + if (cs_deasserted) { + if (seen_data) { + spp_rx_handler(NULL, 0, 1); + seen_data = 0; + pulse_needed = 1; + } + } + + return pulse_needed; +} + +static void spp_cs_deassert_interrupt(uint32_t port) +{ + uint32_t pulse_needed; + + if (spp_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 (spp_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. */ + pulse_needed = spp_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); + + if (pulse_needed) { + /* + * If assert_int_ap() returns 1, it generated a long + * pulse of INT_AP_L. Then, there is no need to generate + * a short pulse. + */ + if (assert_int_ap()) + return; + + /* + * Signal the AP that this SPI frame processing is + * completed. + */ + gpio_set_level(GPIO_INT_AP_L, 0); + + tick_delay(2); + + gpio_set_level(GPIO_INT_AP_L, 1); + } +} + +void _sps0_interrupt(void) +{ + spp_rx_interrupt(0, 0); +} + +void _sps0_cs_deassert_interrupt(void) +{ + spp_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); + +void sps0_cs_assert_interrupt_(void) +{ + GWRITE_FIELD(SPS, ISTATE_CLR, CS_ASSERT, 1); + + deassert_int_ap(); +} +DECLARE_IRQ(GC_IRQNUM_SPS0_CS_ASSERT_INTR, sps0_cs_assert_interrupt_, 1); + +#ifdef CONFIG_SPP_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 spp_test_rx_state { + spptrx_not_started, + spptrx_receiving, + spptrx_finished +}; + +static enum spp_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 spp_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 == spptrx_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 = spptrx_receiving; + else + /* + * If we won't be able to receive this much, enter the + * 'frame finished' state. + */ + rx_state = spptrx_finished; + } + + if (rx_state == spptrx_finished) { + /* + * If CS was deasserted (transitioned to 1) - prepare to start + * receiving the next frame. + */ + if (cs_status) + rx_state = spptrx_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 = spptrx_finished; /* Frame finished.*/ +} + +static int command_spp(int argc, char **argv) +{ + int count = 0; + int target = 10; /* Expect 10 frames by default.*/ + char *e; + + spp_tx_status(GC_SPS_DUMMY_WORD_DEFAULT); + + rx_state = spptrx_not_started; + spp_register_rx_handler(SPP_GENERIC_MODE, spp_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 != spptrx_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 = spp_transmit + (test_frame + index, 8); + } else { + transmitted = spp_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 == spptrx_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", + spp_rx_count, spp_tx_count, + tx_empty_count, max_rx_batch); + + spp_rx_count = + spp_tx_count = + tx_empty_count = + max_rx_batch = 0; + + return EC_SUCCESS; +} + +DECLARE_CONSOLE_COMMAND(spptest, command_spp, + "<num of frames>", + "Loop back frames (10 by default) back to the host"); +#endif /* CONFIG_SPP_TEST */ |