/* Copyright 2017 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. */ /* General Purpose SPI master module for MCHP MEC */ #include "common.h" #include "console.h" #include "dma.h" #include "gpio.h" #include "registers.h" #include "spi.h" #include "timer.h" #include "util.h" #include "hooks.h" #include "task.h" #include "spi_chip.h" #include "gpspi_chip.h" #include "tfdp_chip.h" #define CPUTS(outstr) cputs(CC_SPI, outstr) #define CPRINTS(format, args...) cprints(CC_SPI, format, ## args) #define SPI_BYTE_TRANSFER_TIMEOUT_US (3 * MSEC) /* One byte at 12 MHz full duplex = 0.67 us */ #define SPI_BYTE_TRANSFER_POLL_INTERVAL_US 20 /* * GP-SPI */ /** * Return zero based GPSPI controller index given hardware port. * @param hw_port b[7:4]==1 (GPSPI), b[3:0]=0(GPSPI0), 1(GPSPI1) * @return 0(GPSPI0) or 1(GPSPI1) */ static uint8_t gpspi_port_to_ctrl_id(uint8_t hw_port) { return (hw_port & 0x01); } static int gpspi_wait_byte(const int ctrl) { timestamp_t deadline; deadline.val = get_time().val + SPI_BYTE_TRANSFER_TIMEOUT_US; while ((MCHP_SPI_SR(ctrl) & 0x3) != 0x3) { if (timestamp_expired(deadline, NULL)) return EC_ERROR_TIMEOUT; usleep(SPI_BYTE_TRANSFER_POLL_INTERVAL_US); } return EC_SUCCESS; } /* NOTE: auto-read must be disabled before calling this routine! */ static void gpspi_rx_fifo_clean(const int ctrl) { uint8_t unused = 0; /* If ACTIVE and/or RXFF then clean it */ if ((MCHP_SPI_SR(ctrl) & 0x4) == 0x4) unused += MCHP_SPI_RD(ctrl); if ((MCHP_SPI_SR(ctrl) & 0x2) == 0x2) unused += MCHP_SPI_RD(ctrl); } /* * NOTE: auto-read must be disabled before calling this routine! */ #ifndef CONFIG_MCHP_GPSPI_TX_DMA static int gpspi_tx(const int ctrl, const uint8_t *txdata, int txlen) { int i; int ret; uint8_t unused = 0; gpspi_rx_fifo_clean(ctrl); ret = EC_SUCCESS; for (i = 0; i < txlen; ++i) { MCHP_SPI_TD(ctrl) = txdata[i]; ret = gpspi_wait_byte(ctrl); if (ret != EC_SUCCESS) break; unused += MCHP_SPI_RD(ctrl); } return ret; } #endif int gpspi_transaction_async(const struct spi_device_t *spi_device, const uint8_t *txdata, int txlen, uint8_t *rxdata, int rxlen) { int hw_port, ctrl; int ret = EC_SUCCESS; int cs_asserted = 0; const struct dma_option *opdma; #ifdef CONFIG_MCHP_GPSPI_TX_DMA dma_chan_t *chan; #endif if (spi_device == NULL) return EC_ERROR_PARAM1; hw_port = spi_device->port; ctrl = gpspi_port_to_ctrl_id(hw_port); /* Disable auto read */ MCHP_SPI_CR(ctrl) &= ~BIT(5); if ((txdata != NULL) && (txdata != 0)) { #ifdef CONFIG_MCHP_GPSPI_TX_DMA opdma = spi_dma_option(spi_device, SPI_DMA_OPTION_WR); if (opdma == NULL) return EC_ERROR_INVAL; gpspi_rx_fifo_clean(ctrl); dma_prepare_tx(opdma, txlen, txdata); chan = dma_get_channel(opdma->channel); gpio_set_level(spi_device->gpio_cs, 0); cs_asserted = 1; dma_go(chan); ret = dma_wait(opdma->channel); if (ret == EC_SUCCESS) ret = gpspi_wait_byte(ctrl); dma_disable(opdma->channel); dma_clear_isr(opdma->channel); gpspi_rx_fifo_clean(ctrl); #else gpio_set_level(spi_device->gpio_cs, 0); cs_asserted = 1; ret = gpspi_tx(ctrl, txdata, txlen); #endif } if (ret == EC_SUCCESS) if ((rxlen != 0) && (rxdata != NULL)) { ret = EC_ERROR_INVAL; opdma = spi_dma_option(spi_device, SPI_DMA_OPTION_RD); if (opdma != NULL) { if (!cs_asserted) gpio_set_level(spi_device->gpio_cs, 0); /* Enable auto read */ MCHP_SPI_CR(ctrl) |= BIT(5); dma_start_rx(opdma, rxlen, rxdata); MCHP_SPI_TD(ctrl) = 0; ret = EC_SUCCESS; } } return ret; } int gpspi_transaction_flush(const struct spi_device_t *spi_device) { int ctrl, hw_port; int ret; enum dma_channel chan; const struct dma_option *opdma; timestamp_t deadline; if (spi_device == NULL) return EC_ERROR_PARAM1; hw_port = spi_device->port; ctrl = gpspi_port_to_ctrl_id(hw_port); opdma = spi_dma_option(spi_device, SPI_DMA_OPTION_RD); chan = opdma->channel; ret = dma_wait(chan); /* Disable auto read */ MCHP_SPI_CR(ctrl) &= ~BIT(5); deadline.val = get_time().val + SPI_BYTE_TRANSFER_TIMEOUT_US; /* Wait for FIFO empty SPISR_TXBE */ while ((MCHP_SPI_SR(ctrl) & 0x01) != 0x1) { if (timestamp_expired(deadline, NULL)) { ret = EC_ERROR_TIMEOUT; break; } usleep(SPI_BYTE_TRANSFER_POLL_INTERVAL_US); } dma_disable(chan); dma_clear_isr(chan); if (MCHP_SPI_SR(ctrl) & 0x2) hw_port = MCHP_SPI_RD(ctrl); gpio_set_level(spi_device->gpio_cs, 1); return ret; } int gpspi_transaction_wait(const struct spi_device_t *spi_device) { const struct dma_option *opdma; opdma = spi_dma_option(spi_device, SPI_DMA_OPTION_RD); return dma_wait(opdma->channel); } /** * Enable GPSPI controller and MODULE_SPI_CONTROLLER pins * * @param hw_port b[7:4]=1 b[3:0]=0(GPSPI0), 1(GPSPI1) * @param enable * @return EC_SUCCESS or EC_ERROR_INVAL if port is unrecognized * @note called from mec1701/spi.c * */ int gpspi_enable(int hw_port, int enable) { uint32_t ctrl; if ((hw_port != GPSPI0_PORT) && (hw_port != GPSPI1_PORT)) return EC_ERROR_INVAL; gpio_config_module(MODULE_SPI_CONTROLLER, (enable > 0)); ctrl = (uint32_t)hw_port & 0x0f; if (enable) { if (ctrl) MCHP_PCR_SLP_DIS_DEV(MCHP_PCR_GPSPI1); else MCHP_PCR_SLP_DIS_DEV(MCHP_PCR_GPSPI0); /* Set enable bit in SPI_AR */ MCHP_SPI_AR(ctrl) |= 0x1; /* Set SPDIN to 0 -> Full duplex */ MCHP_SPI_CR(ctrl) &= ~(0x3 << 2); /* Set CLKPOL, TCLKPH, RCLKPH to 0 */ MCHP_SPI_CC(ctrl) &= ~0x7; /* Set LSBF to 0 -> MSB first */ MCHP_SPI_CR(ctrl) &= ~0x1; } else { /* soft reset */ MCHP_SPI_CR(ctrl) |= (1u << 4); /* Clear enable bit in SPI_AR */ MCHP_SPI_AR(ctrl) &= ~0x1; if (ctrl) MCHP_PCR_SLP_EN_DEV(MCHP_PCR_GPSPI1); else MCHP_PCR_SLP_EN_DEV(MCHP_PCR_GPSPI0); } return EC_SUCCESS; }