| /* Copyright 2015 The ChromiumOS Authors |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "common.h" |
| #include "gpio.h" |
| #include "hooks.h" |
| #include "registers.h" |
| #include "spi.h" |
| #include "spi_controller.h" |
| #include "task.h" |
| #include "timer.h" |
| #include "util.h" |
| |
| #ifdef CONFIG_STREAM_SIGNATURE |
| #include "signing.h" |
| #endif |
| |
| /* This timeout should allow a full buffer transaction at the lowest SPI speed |
| * by using the largest uint8_t clock divider of 256 (~235kHz). */ |
| #define SPI_TRANSACTION_TIMEOUT_USEC (5 * MSEC) |
| |
| /* There are two SPI controllers or ports on this chip. */ |
| #define SPI_NUM_PORTS 2 |
| |
| static struct mutex spi_mutex[SPI_NUM_PORTS]; |
| static enum spi_clock_mode clock_mode[SPI_NUM_PORTS]; |
| |
| /* |
| * The Cr50 SPI controller is not DMA auto-fill/drain capable, so async and |
| * flush are not defined on purpose. |
| */ |
| int spi_sub_transaction(const struct spi_device_t *spi_device, |
| const uint8_t *txdata, int txlen, uint8_t *rxdata, |
| int rxlen, bool deassert_cs) |
| { |
| static bool cs_asserted; |
| |
| int port = spi_device->port; |
| int rv = EC_SUCCESS; |
| timestamp_t timeout; |
| int transaction_size = 0; |
| int rxoffset = 0; |
| |
| /* |
| * If SPI0's passthrough is enabled, SPI0 is not available unless the |
| * SPP's BUSY bit is set. |
| */ |
| if (port == 0) { |
| if (GREAD_FIELD_I(SPI, port, CTRL, ENPASSTHRU) && |
| !GREAD(SPS, EEPROM_BUSY_STATUS)) |
| return EC_ERROR_BUSY; |
| } |
| |
| if (rxlen == SPI_READBACK_ALL) { |
| /* Bidirectional SPI sends and receives a bit for each clock. |
| * We'll need to make sure the buffers for RX and TX are equal |
| * and return a bit received for every bit sent. |
| */ |
| if (txlen > SPI_BUF_SIZE) |
| return EC_ERROR_INVAL; |
| rxlen = txlen; |
| transaction_size = txlen; |
| rxoffset = 0; |
| } else { |
| /* Ensure it'll fit inside of the RX and TX buffers. Note that |
| * although the buffers are separate, the total transmission |
| * size must fit in the rx buffer. |
| */ |
| if (txlen + rxlen > SPI_BUF_SIZE) |
| return EC_ERROR_INVAL; |
| transaction_size = rxlen + txlen; |
| rxoffset = txlen; |
| } |
| |
| if (!cs_asserted) |
| /* Grab the port's mutex. */ |
| mutex_lock(&spi_mutex[port]); |
| |
| #ifdef CONFIG_STREAM_SIGNATURE |
| /* |
| * This hook allows mn50 to sniff data written to target |
| * manufactured H1 devices. |
| */ |
| sig_append(stream_spiflash, txdata, txlen); |
| #endif |
| |
| /* Copy the txdata into the 128B Transmit Buffer. */ |
| memmove((uint8_t *)GREG32_ADDR_I(SPI, port, TX_DATA), txdata, txlen); |
| |
| if (!cs_asserted) { |
| #ifndef CONFIG_SPI_CONTROLLER_NO_CS_GPIOS |
| /* Drive chip select low. */ |
| gpio_set_level(spi_device->gpio_cs, 0); |
| #endif |
| cs_asserted = true; |
| } |
| |
| /* Initiate the transaction. */ |
| GWRITE_FIELD_I(SPI, port, ISTATE_CLR, TXDONE, 1); |
| GWRITE_FIELD_I(SPI, port, XACT, SIZE, transaction_size - 1); |
| GWRITE_FIELD_I(SPI, port, XACT, START, 1); |
| |
| /* Wait for the SPI controller to finish the transaction. */ |
| timeout.val = get_time().val + SPI_TRANSACTION_TIMEOUT_USEC; |
| while (!GREAD_FIELD_I(SPI, port, ISTATE, TXDONE)) { |
| /* Give up if the deadline has been exceeded. */ |
| if (get_time().val > timeout.val) { |
| /* Might have been pre-empted by other task. |
| * Check ISTATE.TXDONE again for legit timeout. |
| */ |
| if (GREAD_FIELD_I(SPI, port, ISTATE, TXDONE)) |
| break; |
| rv = EC_ERROR_TIMEOUT; |
| goto err_cs_high; |
| } |
| } |
| GWRITE_FIELD_I(SPI, port, ISTATE_CLR, TXDONE, 1); |
| |
| /* Copy the result. */ |
| memmove(rxdata, |
| &((uint8_t *)GREG32_ADDR_I(SPI, port, RX_DATA))[rxoffset], |
| rxlen); |
| |
| err_cs_high: |
| if ((rv != EC_SUCCESS) || deassert_cs) { |
| #ifndef CONFIG_SPI_CONTROLLER_NO_CS_GPIOS |
| /* Drive chip select high. */ |
| gpio_set_level(spi_device->gpio_cs, 1); |
| #endif /* CONFIG_SPI_CONTROLLER_NO_CS_GPIOS */ |
| cs_asserted = false; |
| /* Release the port's mutex. */ |
| mutex_unlock(&spi_mutex[port]); |
| } |
| return rv; |
| } |
| |
| int spi_transaction(const struct spi_device_t *spi_device, |
| const uint8_t *txdata, int txlen, uint8_t *rxdata, |
| int rxlen) |
| { |
| return spi_sub_transaction(spi_device, txdata, txlen, rxdata, rxlen, |
| true); |
| } |
| |
| /* |
| * Configure the SPI port's clock mode. The SPI port must be re-enabled after |
| * changing the clocking mode. |
| */ |
| void set_spi_clock_mode(int port, enum spi_clock_mode mode) |
| { |
| clock_mode[port] = mode; |
| } |
| |
| /* |
| * Configure the SPI0 controller's passthrough mode. Note: |
| * 1) This must be called after the SPI port is enabled. |
| * 2) Passthrough cannot be safely disabled while the SPI peripheral port is |
| * active and the SPI peripheral port's status register's BUSY bit is not |
| * set. |
| */ |
| void configure_spi0_passthrough(int enable) |
| { |
| int port = 0; |
| |
| /* Grab the port's mutex. */ |
| mutex_lock(&spi_mutex[port]); |
| |
| GWRITE_FIELD_I(SPI, port, CTRL, ENPASSTHRU, enable); |
| |
| /* Release the port's mutex. */ |
| mutex_unlock(&spi_mutex[port]); |
| } |
| |
| int spi_enable(int port, int enable) |
| { |
| int i; |
| |
| if (enable) { |
| int spi_device_found = 0; |
| uint8_t max_div = 0; |
| |
| #ifndef CONFIG_SPI_CONTROLLER_NO_CS_GPIOS |
| gpio_config_module(MODULE_SPI, 1); |
| #endif /* CONFIG_SPI_CONTROLLER_NO_CS_GPIOS */ |
| for (i = 0; i < spi_devices_used; i++) { |
| if (spi_devices[i].port != port) |
| continue; |
| |
| spi_device_found = 1; |
| |
| #ifndef CONFIG_SPI_CONTROLLER_NO_CS_GPIOS |
| /* Deassert CS# */ |
| gpio_set_flags(spi_devices[i].gpio_cs, GPIO_OUTPUT); |
| gpio_set_level(spi_devices[i].gpio_cs, 1); |
| #endif /* CONFIG_SPI_CONTROLLER_NO_CS_GPIOS */ |
| |
| /* Find the port's largest DIV (lowest frequency). */ |
| if (spi_devices[i].div > max_div) |
| max_div = spi_devices[i].div; |
| } |
| |
| /* Ensure there is at least one device behind the SPI port. */ |
| if (!spi_device_found) |
| return EC_ERROR_INVAL; |
| |
| /* configure the SPI clock mode */ |
| GWRITE_FIELD_I(SPI, port, CTRL, CPOL, |
| (clock_mode[port] == SPI_CLOCK_MODE2) || |
| (clock_mode[port] == SPI_CLOCK_MODE3)); |
| GWRITE_FIELD_I(SPI, port, CTRL, CPHA, |
| (clock_mode[port] == SPI_CLOCK_MODE1) || |
| (clock_mode[port] == SPI_CLOCK_MODE3)); |
| |
| /* Enforce the default setup and hold times. */ |
| GWRITE_FIELD_I(SPI, port, CTRL, CSBSU, 0); |
| GWRITE_FIELD_I(SPI, port, CTRL, CSBHLD, 0); |
| |
| /* Set the clock divider, where freq / (div + 1). */ |
| GWRITE_FIELD_I(SPI, port, CTRL, IDIV, max_div); |
| |
| /* Controller's CS is active low. */ |
| GWRITE_FIELD_I(SPI, port, CTRL, CSBPOL, 0); |
| |
| /* Byte 0 bit 7 is first in each double word in the buffers. */ |
| GWRITE_FIELD_I(SPI, port, CTRL, TXBITOR, 1); |
| GWRITE_FIELD_I(SPI, port, CTRL, TXBYTOR, 0); |
| GWRITE_FIELD_I(SPI, port, CTRL, RXBITOR, 1); |
| GWRITE_FIELD_I(SPI, port, CTRL, RXBYTOR, 0); |
| |
| /* Disable passthrough by default. */ |
| if (port == 0) |
| configure_spi0_passthrough(0); |
| |
| /* Disable the TXDONE interrupt, we'll busy poll instead. */ |
| GWRITE_FIELD_I(SPI, port, ICTRL, TXDONE, 0); |
| |
| } else { |
| for (i = 0; i < spi_devices_used; i++) { |
| if (spi_devices[i].port != port) |
| continue; |
| |
| #ifndef CONFIG_SPI_CONTROLLER_NO_CS_GPIOS |
| /* Make sure CS# is deasserted and disabled. */ |
| gpio_set_level(spi_devices[i].gpio_cs, 1); |
| gpio_set_flags(spi_devices[i].gpio_cs, GPIO_ODR_HIGH); |
| #endif /* CONFIG_SPI_CONTROLLER_NO_CS_GPIOS */ |
| } |
| |
| /* Disable passthrough. */ |
| if (port == 0) |
| configure_spi0_passthrough(0); |
| |
| gpio_config_module(MODULE_SPI, 1); |
| } |
| |
| return EC_SUCCESS; |
| } |
| |
| /******************************************************************************/ |
| /* Hooks */ |
| |
| static void spi_init(void) |
| { |
| size_t i; |
| |
| #ifdef CONFIG_SPI_CONTROLLER_CONFIGURE_GPIOS |
| /* Set SPI_MISO as an input */ |
| GWRITE_FIELD(PINMUX, DIOA11_CTL, IE, 1); /* SPI_MISO */ |
| #endif |
| |
| for (i = 0; i < SPI_NUM_PORTS; i++) { |
| /* Configure the SPI ports to default to mode0. */ |
| set_spi_clock_mode(i, SPI_CLOCK_MODE0); |
| |
| /* |
| * Ensure the SPI ports are disabled to prevent us from |
| * interfering with the main chipset when we're not explicitly |
| * using the SPI bus. |
| */ |
| spi_enable(i, 0); |
| } |
| } |
| DECLARE_HOOK(HOOK_INIT, spi_init, HOOK_PRIO_DEFAULT); |