diff options
-rw-r--r-- | board/cr50/board.h | 3 | ||||
-rw-r--r-- | chip/g/build.mk | 2 | ||||
-rw-r--r-- | chip/g/sps.c | 412 | ||||
-rw-r--r-- | chip/g/sps.h | 63 | ||||
-rw-r--r-- | include/config.h | 2 | ||||
-rw-r--r-- | include/sps.h | 47 |
6 files changed, 159 insertions, 370 deletions
diff --git a/board/cr50/board.h b/board/cr50/board.h index a42f6a789f..42fcaa74a8 100644 --- a/board/cr50/board.h +++ b/board/cr50/board.h @@ -24,6 +24,9 @@ #define CONFIG_USB_PID 0x5014 +/* Enable SPI Slave (SPS) module */ +#define CONFIG_SPI + #endif /* * Allow dangerous commands all the time, since we don't have a write protect diff --git a/chip/g/build.mk b/chip/g/build.mk index ff1c607881..64db09f961 100644 --- a/chip/g/build.mk +++ b/chip/g/build.mk @@ -18,7 +18,7 @@ CPPFLAGS+= -DGC_REVISION="$(ver_str)" # Required chip modules chip-y=clock.o gpio.o hwtimer.o jtag.o system.o uart.o chip-y+= pmu.o -chip-y+= sps.o +chip-$(CONFIG_SPI)+= sps.o chip-$(CONFIG_WATCHDOG)+=watchdog.o chip-$(CONFIG_USB)+=usb.o usb_endpoints.o diff --git a/chip/g/sps.c b/chip/g/sps.c index 9d8ef5df3e..eb802cdc6d 100644 --- a/chip/g/sps.c +++ b/chip/g/sps.c @@ -4,42 +4,20 @@ */ #include "common.h" -#include "console.h" #include "hooks.h" #include "pmu.h" #include "registers.h" +#include "spi.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. - */ +/* SPS Control Mode */ +enum sps_mode { + SPS_GENERIC_MODE = 0, + SPS_SWETLAND_MODE = 1, + SPS_ROM_MODE = 2, + SPS_UNDEF_MODE = 3, +}; #define SPS_FIFO_SIZE (1 << 10) #define SPS_FIFO_MASK (SPS_FIFO_SIZE - 1) @@ -53,21 +31,12 @@ #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) +void sps_tx_status(uint8_t byte) +{ + GREG32(SPS, DUMMY_WORD) = byte; +} -/* - * 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) +int sps_transmit(uint8_t *data, size_t data_size) { volatile uint32_t *sps_tx_fifo; uint32_t rptr; @@ -75,13 +44,10 @@ int sps_transmit(uint32_t inst, uint8_t *data, size_t data_size) 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); + wptr = GREG32(SPS, TXFIFO_WPTR); + rptr = GREG32(SPS, TXFIFO_RPTR); fifo_room = (rptr - wptr - 1) & SPS_FIFO_MASK; if (fifo_room < data_size) { @@ -98,7 +64,7 @@ int sps_transmit(uint32_t inst, uint8_t *data, size_t 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 + * the pointers is not 4 byte aligned. Need to go byte * by byte. */ uint32_t fifo_contents; @@ -132,7 +98,7 @@ int sps_transmit(uint32_t inst, uint8_t *data, size_t data_size) data_size -= 4; wptr += 4; } - GREG32_I(SPS, inst, TXFIFO_WPTR) = wptr & SPS_FIFO_PTR_MASK; + GREG32(SPS, TXFIFO_WPTR) = wptr & SPS_FIFO_PTR_MASK; /* Make sure FIFO pointer wraps along with the index. */ if (!(wptr & SPS_FIFO_MASK)) @@ -140,25 +106,17 @@ int sps_transmit(uint32_t inst, uint8_t *data, size_t data_size) 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) - * +/* + * Disable interrupts, clear and reset the HW FIFOs. */ -static void sps_configure(enum sps_mode mode, enum spi_clock_mode clk_mode) +static void sps_reset(void) { + enum sps_mode mode = SPS_GENERIC_MODE; + enum spi_clock_mode clk_mode = SPI_CLOCK_MODE0; + /* Disable All Interrupts */ GREG32(SPS, ICTRL) = 0; @@ -168,85 +126,59 @@ static void sps_configure(enum sps_mode mode, enum spi_clock_mode clk_mode) 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} + /* Default dummy word */ + sps_tx_status(0xff); + + /* + * Reset both FIFOs + * [5, 4, 3] [2, 1, 0] + * RX{AUTO_DIS, EN, RST} TX{AUTO_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. */ +/* + * Following a reset, resume listening for and handling incoming bytes. + */ +static void sps_enable(void) +{ + /* Enable the FIFOs */ GWRITE_FIELD(SPS, FIFO_CTRL, RXFIFO_EN, 1); + GWRITE_FIELD(SPS, FIFO_CTRL, TXFIFO_EN, 1); + /* + * Wait until we have a few bytes in the FIFO before waking up. Note + * that if the master wants to read bytes from us, it may have to clock + * in at least RXFIFO_THRESHOLD + 1 bytes before we notice that it's + * asking. + */ GREG32(SPS, RXFIFO_THRESHOLD) = 8; - GWRITE_FIELD(SPS, ICTRL, RXFIFO_LVL, 1); - /* Use CS_DEASSERT to retrieve all remaining bytes from RX FIFO. */ + /* Also wake up when the master has finished talking to us, so we can + * drain any remaining bytes in the RX FIFO. Too late for TX, of + * course. */ 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 + * Check how much data is available in the RX FIFO and return a 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 + * @param data pointer to set to the beginning of data in the fifo + * @return number of available bytes + * zero */ -static int sps_check_rx(uint32_t inst, uint8_t **data) +static int sps_check_rx(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; + uint32_t write_ptr = GREG32(SPS, RXFIFO_WPTR) & SPS_FIFO_MASK; + uint32_t read_ptr = GREG32(SPS, RXFIFO_RPTR) & SPS_FIFO_MASK; if (read_ptr == write_ptr) return 0; @@ -260,227 +192,65 @@ static int sps_check_rx(uint32_t inst, uint8_t **data) } /* Advance RX FIFO read pointer after data has been read from the FIFO. */ -static void sps_advance_rx(int port, int data_size) +static void sps_advance_rx(int data_size) { - uint32_t read_ptr = GREG32_I(SPS, port, RXFIFO_RPTR) + data_size; + uint32_t read_ptr = GREG32(SPS, RXFIFO_RPTR) + data_size; - GREG32_I(SPS, port, RXFIFO_RPTR) = read_ptr & SPS_FIFO_PTR_MASK; + GREG32(SPS, 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) +/* RX FIFO handler. If NULL, incoming bytes are silently discarded. */ +static rx_handler_fn sps_rx_handler; + +static void sps_rx_interrupt(int cs_enabled) { - for (;;) { + size_t data_size; + do { 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; - + data_size = sps_check_rx(&received_data); 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); + sps_rx_handler(received_data, data_size, cs_enabled); + sps_advance_rx(data_size); + } while (data_size); } void _sps0_interrupt(void) { - sps_rx_interrupt(0, 0); + sps_rx_interrupt(1); + /* The RXFIFO_LVL interrupt clears itself when the level drops */ } +DECLARE_IRQ(GC_IRQNUM_SPS0_RXFIFO_LVL_INTR, _sps0_interrupt, 1); void _sps0_cs_deassert_interrupt(void) { - sps_cs_deassert_interrupt(0); + /* Make sure the receive FIFO is drained. */ + sps_rx_interrupt(0); + /* Clear the interrupt bit */ + GWRITE_FIELD(SPS, ISTATE_CLR, CS_DEASSERT, 1); } 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) +void sps_unregister_rx_handler(void) { - 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.*/ + task_disable_irq(GC_IRQNUM_SPS0_RXFIFO_LVL_INTR); + task_disable_irq(GC_IRQNUM_SPS0_CS_DEASSERT_INTR); + sps_reset(); + sps_rx_handler = NULL; } -static int command_sps(int argc, char **argv) +void sps_register_rx_handler(rx_handler_fn func) { - 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; + sps_rx_handler = func; + sps_enable(); + task_enable_irq(GC_IRQNUM_SPS0_RXFIFO_LVL_INTR); + task_enable_irq(GC_IRQNUM_SPS0_CS_DEASSERT_INTR); } -DECLARE_CONSOLE_COMMAND(spstest, command_sps, - "<num of frames>", - "Loop back frames (10 by default) back to the host", - NULL); -#endif /* CONFIG_SPS_TEST */ +/* At reset the SPS module is initialized, but not enabled */ +static void sps_init(void) +{ + pmu_clock_en(PERIPH_SPS); + sps_unregister_rx_handler(); +} +DECLARE_HOOK(HOOK_INIT, sps_init, HOOK_PRIO_INIT_CHIPSET); diff --git a/chip/g/sps.h b/chip/g/sps.h new file mode 100644 index 0000000000..f92d083ae8 --- /dev/null +++ b/chip/g/sps.h @@ -0,0 +1,63 @@ +/* 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. + */ +#ifndef __CROS_EC_SPS_H +#define __CROS_EC_SPS_H + +/* + * API 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. + */ + +#include <stdint.h> +#include <stddef.h> + +/** + * Every RX byte simultaneously sends a TX byte, no matter what. This + * specifies the TX byte to send when there's no data in the TX FIFO. + * + * @param byte dummy byte to send (default is 0xFF) + */ +void sps_tx_status(uint8_t byte); + +/** + * Add data to the SPS TX FIFO + * + * @param data Pointer to 8-bit data + * @param data_size Number of bytes to transmit + * @return Number of bytes placed into the TX FIFO + */ +int sps_transmit(uint8_t *data, size_t data_size); + +/** + * The RX handler function is called in interrupt context to process incoming + * bytes. It is passed a pointer to the linear space in the RX FIFO and the + * number of bytes available at that address. + * + * If the RX FIFO wraps around, the RX FIFO handler may be called twice during + * one interrupt. + * + * The handler is also called when the chip select deasserts, in case any + * cleanup is required. + */ +typedef void (*rx_handler_fn)(uint8_t *data, size_t data_size, int cs_status); + +/** + * Register the RX handler function. This will reset and disable the RX FIFO, + * replace any previous handler, then enable the RX FIFO. + * + * @param func RX handler function + */ +void sps_register_rx_handler(rx_handler_fn func); + +/** + * Unregister the RX handler. This will reset and disable the RX FIFO. + */ +void sps_unregister_rx_handler(void); + +#endif /* __CROS_EC_SPS_H */ diff --git a/include/config.h b/include/config.h index 05459bfb10..d628e5d5d7 100644 --- a/include/config.h +++ b/include/config.h @@ -1272,7 +1272,7 @@ /* Support smbus interface */ #undef CONFIG_SMBUS -/* Support SPI interfaces */ +/* Support SPI (slave) interfaces */ #undef CONFIG_SPI /* Define SPI chip select GPIO pin. */ diff --git a/include/sps.h b/include/sps.h deleted file mode 100644 index e60f43d96f..0000000000 --- a/include/sps.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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. - */ - -#ifndef __CROS_EC_SPS_H -#define __CROS_EC_SPS_H - -#include "spi.h" -#include "util.h" - -/* SPS Control Mode */ -enum sps_mode { - SPS_GENERIC_MODE = 0, - SPS_SWETLAND_MODE = 1, - SPS_ROM_MODE = 2, - SPS_UNDEF_MODE = 3, -}; - -/* - * Tx interrupt callback function prototype. This function returns a portion - * of the received SPI data and current status of the CS line. When CS is - * deasserted, this function is called with data_size of zero and a non-zero - * cs_status. This allows the recipient to delineate the SPS frames. - */ -typedef void (*rx_handler_f)(uint32_t inst, uint8_t *data, - size_t data_size, int cs_status); - -/* - * 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); - -/* - * These functions return zero on success or non-zero on failure (attempt to - * register a callback on top of existing one, or attempt to unregister - * non-exitisng callback. - */ -int sps_register_rx_handler(enum sps_mode mode, rx_handler_f rx_handler); -int sps_unregister_rx_handler(void); - -#endif |