/* * Copyright (c) 2016, Intel Corporation * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * 3. Neither the name of the Intel Corporation nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE INTEL CORPORATION OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "qm_ss_spi.h" #define FIFO_SIZE (8) /* Maximum size of RX or TX FIFO */ #define FIFO_RX_W_MARK (6) /* Interrupt mark to read RX FIFO */ #define FIFO_TX_W_MARK (3) /* Interrupt mark to write TX FIFO */ #define BYTES_PER_FRAME(reg_data) \ (((reg_data & QM_SS_SPI_CTRL_DFS_MASK) >> 3) + 1) static uint32_t base[QM_SS_SPI_NUM] = {QM_SS_SPI_0_BASE, QM_SS_SPI_1_BASE}; static const qm_ss_spi_async_transfer_t *spi_async_transfer[QM_SS_SPI_NUM]; static uint32_t rx_c[QM_SS_SPI_NUM]; static uint32_t tx_c[QM_SS_SPI_NUM]; static const uint16_t dummy_frame = 0; /* Private Functions */ static void spi_disable(const qm_ss_spi_t spi) { /* Disable SPI device */ QM_SS_REG_AUX_NAND(base[spi] + QM_SS_SPI_SPIEN, QM_SS_SPI_SPIEN_EN); /* MASK all interrupts. */ __builtin_arc_sr(0, base[spi] + QM_SS_SPI_INTR_MASK); /* Clear all interrupts */ __builtin_arc_sr(QM_SS_SPI_INTR_ALL, base[spi] + QM_SS_SPI_CLR_INTR); } static __inline__ void fifo_write(const qm_ss_spi_t spi, const void *data, uint8_t size) { uint32_t dr; if (size == 1) { dr = *(uint8_t *)data; } else { dr = *(uint16_t *)data; } dr |= QM_SS_SPI_DR_W_MASK; __builtin_arc_sr(dr, base[spi] + QM_SS_SPI_DR); } static __inline__ void fifo_read(const qm_ss_spi_t spi, void *data, uint8_t size) { __builtin_arc_sr(QM_SS_SPI_DR_R_MASK, base[spi] + QM_SS_SPI_DR); if (size == 1) { *(uint8_t *)data = __builtin_arc_lr(base[spi] + QM_SS_SPI_DR); } else { *(uint16_t *)data = __builtin_arc_lr(base[spi] + QM_SS_SPI_DR); } } /* Public Functions */ int qm_ss_spi_set_config(const qm_ss_spi_t spi, const qm_ss_spi_config_t *const cfg) { QM_CHECK(spi < QM_SS_SPI_NUM, -EINVAL); QM_CHECK(cfg, -EINVAL); /* Configuration can be changed only when SPI is disabled */ if (0 != (__builtin_arc_lr(base[spi] + QM_SS_SPI_SPIEN) & QM_SS_SPI_SPIEN_EN)) { return -EBUSY; } uint32_t ctrl = __builtin_arc_lr(QM_SS_SPI_0_BASE + QM_SS_SPI_CTRL); ctrl &= QM_SS_SPI_CTRL_CLK_ENA; ctrl |= cfg->frame_size << QM_SS_SPI_CTRL_DFS_OFFS; ctrl |= cfg->transfer_mode << QM_SS_SPI_CTRL_TMOD_OFFS; ctrl |= cfg->bus_mode << QM_SS_SPI_CTRL_BMOD_OFFS; __builtin_arc_sr(ctrl, base[spi] + QM_SS_SPI_CTRL); __builtin_arc_sr(cfg->clk_divider, base[spi] + QM_SS_SPI_TIMING); return 0; } int qm_ss_spi_slave_select(const qm_ss_spi_t spi, const qm_ss_spi_slave_select_t ss) { QM_CHECK(spi < QM_SS_SPI_NUM, -EINVAL); /* Check if the device reports as busy. */ if (__builtin_arc_lr(base[spi] + QM_SS_SPI_SR) & QM_SS_SPI_SR_BUSY) { return -EBUSY; } uint32_t spien = __builtin_arc_lr(base[spi] + QM_SS_SPI_SPIEN); spien &= ~QM_SS_SPI_SPIEN_SER_MASK; spien |= (ss << QM_SS_SPI_SPIEN_SER_OFFS); __builtin_arc_sr(spien, base[spi] + QM_SS_SPI_SPIEN); return 0; } int qm_ss_spi_get_status(const qm_ss_spi_t spi, qm_ss_spi_status_t *const status) { QM_CHECK(spi < QM_SS_SPI_NUM, -EINVAL); QM_CHECK(status, -EINVAL); if (__builtin_arc_lr(base[spi] + QM_SS_SPI_SR) & QM_SS_SPI_SR_BUSY) { *status = QM_SS_SPI_BUSY; } else { *status = QM_SS_SPI_IDLE; } return 0; } int qm_ss_spi_transfer(const qm_ss_spi_t spi, const qm_ss_spi_transfer_t *const xfer, qm_ss_spi_status_t *const status) { QM_CHECK(spi < QM_SS_SPI_NUM, -EINVAL); QM_CHECK(xfer, -EINVAL); uint32_t ctrl = __builtin_arc_lr(base[spi] + QM_SS_SPI_CTRL); uint8_t tmode = (uint8_t)((ctrl & QM_SS_SPI_CTRL_TMOD_MASK) >> QM_SS_SPI_CTRL_TMOD_OFFS); QM_CHECK(tmode == QM_SS_SPI_TMOD_TX_RX ? (xfer->tx_len == xfer->rx_len) : 1, -EINVAL); QM_CHECK(tmode == QM_SS_SPI_TMOD_TX ? (xfer->rx_len == 0) : 1, -EINVAL); QM_CHECK(tmode == QM_SS_SPI_TMOD_EEPROM_READ ? (xfer->rx_len > 0) : 1, -EINVAL); QM_CHECK(tmode == QM_SS_SPI_TMOD_RX ? (xfer->rx_len > 0) : 1, -EINVAL); QM_CHECK(tmode == QM_SS_SPI_TMOD_RX ? (xfer->tx_len == 0) : 1, -EINVAL); uint32_t tx_cnt = xfer->tx_len; uint32_t rx_cnt = xfer->rx_len; uint8_t *rx_buffer = xfer->rx; uint8_t *tx_buffer = xfer->tx; int ret = 0; /* Disable all SPI interrupts */ __builtin_arc_sr(0, base[spi] + QM_SS_SPI_INTR_MASK); /* Set NDF (Number of Data Frames) in RX or EEPROM Read mode. (-1) */ if (tmode == QM_SS_SPI_TMOD_RX || tmode == QM_SS_SPI_TMOD_EEPROM_READ) { ctrl &= ~QM_SS_SPI_CTRL_NDF_MASK; ctrl |= ((xfer->rx_len - 1) << QM_SS_SPI_CTRL_NDF_OFFS) & QM_SS_SPI_CTRL_NDF_MASK; __builtin_arc_sr(ctrl, base[spi] + QM_SS_SPI_CTRL); } /* RX only transfers need a dummy frame to be sent. */ if (tmode == QM_SS_SPI_TMOD_RX) { tx_buffer = (uint8_t *)&dummy_frame; tx_cnt = 1; } /* Calculate number of bytes per frame (1 or 2)*/ uint8_t bytes = BYTES_PER_FRAME(ctrl); /* Enable SPI device */ QM_SS_REG_AUX_OR(base[spi] + QM_SS_SPI_SPIEN, QM_SS_SPI_SPIEN_EN); while (tx_cnt || rx_cnt) { uint32_t sr = __builtin_arc_lr(base[spi] + QM_SS_SPI_SR); /* Break and report error if RX FIFO has overflown */ if (__builtin_arc_lr(base[spi] + QM_SS_SPI_INTR_STAT) & QM_SS_SPI_INTR_RXOI) { ret = -EIO; if (status) { *status |= QM_SS_SPI_RX_OVERFLOW; } break; } /* Copy data to buffer as long RX-FIFO is not empty */ if (sr & QM_SS_SPI_SR_RFNE && rx_cnt) { fifo_read(spi, rx_buffer, bytes); rx_buffer += bytes; rx_cnt--; } /* Copy data from buffer as long TX-FIFO is not full. */ if (sr & QM_SS_SPI_SR_TFNF && tx_cnt) { fifo_write(spi, tx_buffer, bytes); tx_buffer += bytes; tx_cnt--; } } /* Wait for last byte transferred */ while (__builtin_arc_lr(base[spi] + QM_SS_SPI_SR) & QM_SS_SPI_SR_BUSY) ; spi_disable(spi); return ret; } /* Interrupt related functions. */ int qm_ss_spi_irq_transfer(const qm_ss_spi_t spi, const qm_ss_spi_async_transfer_t *const xfer) { QM_CHECK(spi < QM_SS_SPI_NUM, -EINVAL); QM_CHECK(xfer, -EINVAL); /* Load and save initial control register */ uint32_t ctrl = __builtin_arc_lr(base[spi] + QM_SS_SPI_CTRL); uint8_t tmode = (uint8_t)((ctrl & QM_SS_SPI_CTRL_TMOD_MASK) >> QM_SS_SPI_CTRL_TMOD_OFFS); uint8_t bytes = BYTES_PER_FRAME(ctrl); QM_CHECK(tmode == QM_SS_SPI_TMOD_TX_RX ? (xfer->tx_len == xfer->rx_len) : 1, -EINVAL); spi_async_transfer[spi] = xfer; tx_c[spi] = xfer->tx_len; rx_c[spi] = xfer->rx_len; /* Set NDF (Number of Data Frames) in RX or EEPROM Read mode. (-1) */ if (tmode == QM_SS_SPI_TMOD_RX || tmode == QM_SS_SPI_TMOD_EEPROM_READ) { ctrl &= ~QM_SS_SPI_CTRL_NDF_MASK; ctrl |= ((xfer->rx_len - 1) << QM_SS_SPI_CTRL_NDF_OFFS) & QM_SS_SPI_CTRL_NDF_MASK; __builtin_arc_sr(ctrl, base[spi] + QM_SS_SPI_CTRL); } uint32_t ftlr = (((FIFO_RX_W_MARK < xfer->rx_len ? FIFO_RX_W_MARK : xfer->rx_len) - 1) << QM_SS_SPI_FTLR_RFT_OFFS) & QM_SS_SPI_FTLR_RFT_MASK; __builtin_arc_sr(ftlr, base[spi] + QM_SS_SPI_FTLR); /* Unmask all interrupts */ __builtin_arc_sr(QM_SS_SPI_INTR_ALL, base[spi] + QM_SS_SPI_INTR_MASK); /* Enable SPI device */ QM_SS_REG_AUX_OR(base[spi] + QM_SS_SPI_SPIEN, QM_SS_SPI_SPIEN_EN); /* RX only transfers need a dummy frame byte to be sent. */ if (tmode == QM_SS_SPI_TMOD_RX) { fifo_write(spi, (uint8_t *)&dummy_frame, bytes); } return 0; } int qm_ss_spi_transfer_terminate(const qm_ss_spi_t spi) { QM_CHECK(spi < QM_SS_SPI_NUM, -EINVAL); const qm_ss_spi_async_transfer_t *const transfer = spi_async_transfer[spi]; spi_disable(spi); if (transfer->callback) { uint32_t len = 0; uint32_t ctrl = __builtin_arc_lr(base[spi] + QM_SS_SPI_CTRL); uint8_t tmode = (uint8_t)((ctrl & QM_SS_SPI_CTRL_TMOD_MASK) >> QM_SS_SPI_CTRL_TMOD_OFFS); if (tmode == QM_SS_SPI_TMOD_TX || tmode == QM_SS_SPI_TMOD_TX_RX) { len = transfer->tx_len - tx_c[spi]; } else { len = transfer->rx_len - rx_c[spi]; } /* * NOTE: change this to return controller-specific code * 'user aborted'. */ transfer->callback(transfer->callback_data, -ECANCELED, QM_SS_SPI_IDLE, (uint16_t)len); } return 0; } static void handle_spi_err_interrupt(const qm_ss_spi_t spi) { uint32_t intr_stat = __builtin_arc_lr(base[spi] + QM_SS_SPI_INTR_STAT); const qm_ss_spi_async_transfer_t *const transfer = spi_async_transfer[spi]; spi_disable(spi); QM_ASSERT((intr_stat & QM_SS_SPI_INTR_STAT_TXOI) == 0); QM_ASSERT((intr_stat & QM_SS_SPI_INTR_STAT_RXUI) == 0); if ((intr_stat & QM_SS_SPI_INTR_RXOI) && transfer->callback) { transfer->callback(transfer->callback_data, -EIO, QM_SS_SPI_RX_OVERFLOW, transfer->rx_len - rx_c[spi]); } } static void handle_spi_tx_interrupt(const qm_ss_spi_t spi) { /* Clear Transmit Fifo Emtpy interrupt */ __builtin_arc_sr(QM_SS_SPI_INTR_TXEI, base[spi] + QM_SS_SPI_CLR_INTR); uint32_t ctrl = __builtin_arc_lr(base[spi] + QM_SS_SPI_CTRL); /* Calculate number of bytes per frame (1 or 2)*/ uint8_t bytes = BYTES_PER_FRAME(ctrl); uint8_t tmode = (uint8_t)((ctrl & QM_SS_SPI_CTRL_TMOD_MASK) >> QM_SS_SPI_CTRL_TMOD_OFFS); const qm_ss_spi_async_transfer_t *const transfer = spi_async_transfer[spi]; /* Jump to the right position of TX buffer. * If no bytes were transmitted before, we start from the beginning, * otherwise we jump to the next frame to be sent. */ const uint8_t *tx_buffer = transfer->tx + ((transfer->tx_len - tx_c[spi]) * bytes); if (tx_c[spi] == 0 && !(__builtin_arc_lr(base[spi] + QM_SS_SPI_SR) & QM_SS_SPI_SR_BUSY)) { if (tmode == QM_SS_SPI_TMOD_TX) { spi_disable(spi); if (transfer->callback) { transfer->callback(transfer->callback_data, 0, QM_SS_SPI_IDLE, transfer->tx_len); } } else { QM_SS_REG_AUX_NAND(base[spi] + QM_SS_SPI_INTR_MASK, QM_SS_SPI_INTR_TXEI); } return; } /* Make sure RX fifo does not overflow */ uint32_t rxflr = __builtin_arc_lr(base[spi] + QM_SS_SPI_RXFLR); uint32_t txflr = __builtin_arc_lr(base[spi] + QM_SS_SPI_TXFLR); int32_t cnt = FIFO_SIZE - rxflr - txflr - 1; while (tx_c[spi] && cnt > 0) { fifo_write(spi, tx_buffer, bytes); tx_buffer += bytes; tx_c[spi]--; cnt--; } } static void handle_spi_rx_interrupt(const qm_ss_spi_t spi) { /* Clear RX-FIFO FULL interrupt */ __builtin_arc_sr(QM_SS_SPI_INTR_RXFI, base[spi] + QM_SS_SPI_CLR_INTR); uint32_t ctrl = __builtin_arc_lr(base[spi] + QM_SS_SPI_CTRL); /* Calculate number of bytes per frame (1 or 2)*/ uint8_t bytes = BYTES_PER_FRAME(ctrl); const qm_ss_spi_async_transfer_t *const transfer = spi_async_transfer[spi]; /* * Jump to the right position of RX buffer. * If no bytes were received before, we start from the beginning, * otherwise we jump to the next available frame position. */ uint8_t *rx_buffer = transfer->rx + ((transfer->rx_len - rx_c[spi]) * bytes); while (__builtin_arc_lr(base[spi] + QM_SS_SPI_SR) & QM_SS_SPI_SR_RFNE && rx_c[spi]) { fifo_read(spi, rx_buffer, bytes); rx_buffer += bytes; rx_c[spi]--; } /* Set new FIFO threshold or complete transfer */ uint32_t new_irq_level = (FIFO_RX_W_MARK < rx_c[spi] ? FIFO_RX_W_MARK : rx_c[spi]); if (rx_c[spi]) { new_irq_level--; uint32_t ftlr = __builtin_arc_lr(base[spi] + QM_SS_SPI_FTLR); ftlr &= ~QM_SS_SPI_FTLR_RFT_MASK; ftlr |= (new_irq_level << QM_SS_SPI_FTLR_RFT_OFFS); __builtin_arc_sr(ftlr, base[spi] + QM_SS_SPI_FTLR); } else { spi_disable(spi); if (transfer->callback) { transfer->callback(transfer->callback_data, 0, QM_SS_SPI_IDLE, transfer->rx_len); } } } QM_ISR_DECLARE(qm_ss_spi_0_error_isr) { handle_spi_err_interrupt(QM_SS_SPI_0); } QM_ISR_DECLARE(qm_ss_spi_1_error_isr) { handle_spi_err_interrupt(QM_SS_SPI_1); } QM_ISR_DECLARE(qm_ss_spi_0_rx_avail_isr) { handle_spi_rx_interrupt(QM_SS_SPI_0); } QM_ISR_DECLARE(qm_ss_spi_1_rx_avail_isr) { handle_spi_rx_interrupt(QM_SS_SPI_1); } QM_ISR_DECLARE(qm_ss_spi_0_tx_req_isr) { handle_spi_tx_interrupt(QM_SS_SPI_0); } QM_ISR_DECLARE(qm_ss_spi_1_tx_req_isr) { handle_spi_tx_interrupt(QM_SS_SPI_1); } #if (ENABLE_RESTORE_CONTEXT) int qm_ss_spi_save_context(const qm_ss_spi_t spi, qm_ss_spi_context_t *const ctx) { const uint32_t controller = base[spi]; QM_CHECK(spi < QM_SS_SPI_NUM, -EINVAL); QM_CHECK(ctx != NULL, -EINVAL); ctx->spi_timing = __builtin_arc_lr(controller + QM_SS_SPI_TIMING); ctx->spi_spien = __builtin_arc_lr(controller + QM_SS_SPI_SPIEN); ctx->spi_ctrl = __builtin_arc_lr(controller + QM_SS_SPI_CTRL); return 0; } int qm_ss_spi_restore_context(const qm_ss_spi_t spi, const qm_ss_spi_context_t *const ctx) { const uint32_t controller = base[spi]; QM_CHECK(spi < QM_SS_SPI_NUM, -EINVAL); QM_CHECK(ctx != NULL, -EINVAL); __builtin_arc_sr(ctx->spi_timing, controller + QM_SS_SPI_TIMING); __builtin_arc_sr(ctx->spi_spien, controller + QM_SS_SPI_SPIEN); __builtin_arc_sr(ctx->spi_ctrl, controller + QM_SS_SPI_CTRL); return 0; } #endif /* ENABLE_RESTORE_CONTEXT */