/* ***************************************************************************** * Copyright (C) 2016 Maxim Integrated Products, Inc., All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL MAXIM INTEGRATED BE LIABLE FOR ANY CLAIM, DAMAGES * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * Except as contained in this notice, the name of Maxim Integrated * Products, Inc. shall not be used except as stated in the Maxim Integrated * Products, Inc. Branding Policy. * * The mere transfer of this software does not imply any licenses * of trade secrets, proprietary technology, copyrights, patents, * trademarks, maskwork rights, or any other form of intellectual * property whatsoever. Maxim Integrated Products, Inc. retains all * ownership rights. * * $Date: 2019-06-25 10:15:10 -0500 (Tue, 25 Jun 2019) $ * $Revision: 44277 $ * **************************************************************************** */ /* **** Includes **** */ #include #include "mxc_config.h" #include "mxc_assert.h" #include "mxc_sys.h" #include "tmr_utils.h" #include "mxc_lock.h" #include "spi17y.h" /* **** Definitions **** */ /* **** Globals **** */ typedef struct { spi17y_req_t *req; int started; unsigned last_size; unsigned deass; } spi17y_req_state_t; static spi17y_req_state_t states[MXC_SPI17Y_INSTANCES]; /* **** Functions **** */ static int SPI17Y_TransSetup(mxc_spi17y_regs_t *spi, spi17y_req_t *req, int master); static int SPI17Y_MasterTransHandler(mxc_spi17y_regs_t *spi, spi17y_req_t *req, uint8_t async); static int SPI17Y_TransHandler(mxc_spi17y_regs_t *spi, spi17y_req_t *req, uint8_t async); static int SPI17Y_SlaveTransHandler(mxc_spi17y_regs_t *spi, spi17y_req_t *req, uint8_t async); /* ************************************************************************** */ int SPI17Y_Init(mxc_spi17y_regs_t *spi, unsigned int mode, unsigned int freq, const sys_cfg_spi17y_t* sys_cfg) { uint32_t freq_div; int spi_num, error, hi_clk, lo_clk, scale; spi_num = MXC_SPI17Y_GET_IDX(spi); MXC_ASSERT(spi_num >= 0); if (mode > 3) { return E_BAD_PARAM; } if ((error = SYS_SPI17Y_Init(spi, sys_cfg)) != E_NO_ERROR) { return error; } states[spi_num].req = NULL; states[spi_num].last_size = 0; states[spi_num].deass = 1; // Enable SPI17Y spi->ctrl0 = (MXC_F_SPI17Y_CTRL0_EN); spi->ss_time = ((0x1 << MXC_F_SPI17Y_SS_TIME_PRE_POS) | (0x1 << MXC_F_SPI17Y_SS_TIME_POST_POS) | (0x1 << MXC_F_SPI17Y_SS_TIME_INACT_POS)); // Check if frequency is too high if (freq > PeripheralClock) { return E_BAD_PARAM; } // Set the clock high and low freq_div = PeripheralClock/ (freq); hi_clk = freq_div/2; lo_clk = freq_div/2; scale = 0; if (freq_div %2) { hi_clk +=1; } while (hi_clk > 16 && scale < 9) { hi_clk /= 2; lo_clk /=2; scale ++; } spi->clk_cfg = ((lo_clk << MXC_F_SPI17Y_CLK_CFG_LO_POS) | (hi_clk << MXC_F_SPI17Y_CLK_CFG_HI_POS)); MXC_SETFIELD(spi->clk_cfg, MXC_F_SPI17Y_CLK_CFG_SCALE, (scale << MXC_F_SPI17Y_CLK_CFG_SCALE_POS)); // Set the mode spi->ctrl2 = (mode << MXC_F_SPI17Y_CTRL2_CPHA_POS); // Clear the interrupts spi->int_fl = spi->int_fl; return E_NO_ERROR; } /* ************************************************************************* */ int SPI17Y_Shutdown(mxc_spi17y_regs_t *spi) { int spi_num, err; spi17y_req_t *temp_req; // Disable and clear interrupts spi->int_en = 0; spi->int_fl = spi->int_fl; // Disable SPI17Y and FIFOS spi->ctrl0 &= ~(MXC_F_SPI17Y_CTRL0_EN); spi->dma &= ~(MXC_F_SPI17Y_DMA_TX_FIFO_EN | MXC_F_SPI17Y_DMA_RX_FIFO_EN); // Call all of the pending callbacks for this SPI17Y spi_num = MXC_SPI17Y_GET_IDX(spi); if (states[spi_num].req != NULL) { // Save the request temp_req = states[spi_num].req; // Unlock this SPI17Y mxc_free_lock((uint32_t*)&states[spi_num].req); // Callback if not NULL if (temp_req->callback != NULL) { temp_req->callback(temp_req, E_SHUTDOWN); } } // Clear registers spi->ctrl0 = 0; spi->ctrl1 = 0; spi->ctrl2 = 0; spi->ss_time = 0; // Clear system level configurations if ((err = SYS_SPI17Y_Shutdown(spi)) != E_NO_ERROR) { return err; } return E_NO_ERROR; } /* ************************************************************************** */ int SPI17Y_TransSetup(mxc_spi17y_regs_t *spi, spi17y_req_t *req, int master) { int spi_num; if ((req->tx_data == NULL) && (req->rx_data == NULL)) { return E_BAD_PARAM; } if ((req->width > SPI17Y_WIDTH_1) && (req->tx_data != NULL) && (req->rx_data != NULL)) { return E_BAD_PARAM; } // HW has problem with these two character sizes if (req->bits == 1 || req->bits == 9) { return E_BAD_PARAM; } spi_num = MXC_SPI17Y_GET_IDX(spi); MXC_ASSERT(spi_num >= 0); MXC_ASSERT(req->ssel < MXC_SPI17Y_SS_INSTANCES); req->tx_num = 0; req->rx_num = 0; if (req->len == 0) { return E_NO_ERROR; } states[spi_num].req = req; states[spi_num].started = 0; // HW requires disabling/renabling SPI block at end of each transaction (when SS is inactive). if (states[spi_num].deass == 1) { spi->ctrl0 &= ~(MXC_F_SPI17Y_CTRL0_EN); } if (master) { // Enable master mode spi->ctrl0 |= MXC_F_SPI17Y_CTRL0_MASTER; // Setup the slave select MXC_SETFIELD(spi->ctrl0, MXC_F_SPI17Y_CTRL0_SS, ((0x1 << req->ssel) << MXC_F_SPI17Y_CTRL0_SS_POS)); spi->ctrl2 |= ((req->ssel_pol << req->ssel)<ctrl0 &= ~MXC_F_SPI17Y_CTRL0_MASTER; // Setup the slave select spi->ctrl2 |= ((req->ssel_pol << 0)<bits != states[spi_num].last_size)) { // Setup the character size // Master should only change character size at the end of a transaction. No restrictions on when slave can change. if (!master || (!(spi->stat & MXC_F_SPI17Y_STAT_BUSY) && (states[spi_num].deass == 1)) || !(spi->ctrl0 & MXC_F_SPI17Y_CTRL0_EN)) { //disable spi to change transfer size spi->ctrl0 &= ~(MXC_F_SPI17Y_CTRL0_EN); // set bit size states[spi_num].last_size = req->bits; if (req->bits <16) { MXC_SETFIELD(spi->ctrl2, MXC_F_SPI17Y_CTRL2_NUMBITS, req->bits << MXC_F_SPI17Y_CTRL2_NUMBITS_POS); } else { MXC_SETFIELD(spi->ctrl2, MXC_F_SPI17Y_CTRL2_NUMBITS, 0 << MXC_F_SPI17Y_CTRL2_NUMBITS_POS); } } else { // cant change transfer size while spi is busy return E_BAD_STATE; } } // Setup the data width if (req->width == SPI17Y_WIDTH_4) { MXC_SETFIELD(spi->ctrl2, MXC_F_SPI17Y_CTRL2_DATA_WIDTH, MXC_S_SPI17Y_CTRL2_DATA_WIDTH_QUAD); } else if (req->width == SPI17Y_WIDTH_2) { MXC_SETFIELD(spi->ctrl2, MXC_F_SPI17Y_CTRL2_DATA_WIDTH, MXC_S_SPI17Y_CTRL2_DATA_WIDTH_DUAL); } else { MXC_SETFIELD(spi->ctrl2, MXC_F_SPI17Y_CTRL2_DATA_WIDTH, MXC_S_SPI17Y_CTRL2_DATA_WIDTH_MONO); } // Setup the number of characters to transact if (req->len > (MXC_F_SPI17Y_CTRL1_TX_NUM_CHAR >> MXC_F_SPI17Y_CTRL1_TX_NUM_CHAR_POS)) { return E_BAD_PARAM; } if (req->rx_data != NULL) { // The TX_NUM field is used for both RX and TX length when in 4-wire mode. if(req->width == SPI17Y_WIDTH_1) { MXC_SETFIELD(spi->ctrl1, MXC_F_SPI17Y_CTRL1_TX_NUM_CHAR, req->len << MXC_F_SPI17Y_CTRL1_TX_NUM_CHAR_POS); } else { MXC_SETFIELD(spi->ctrl1, MXC_F_SPI17Y_CTRL1_RX_NUM_CHAR, req->len << MXC_F_SPI17Y_CTRL1_RX_NUM_CHAR_POS); } spi->dma |= MXC_F_SPI17Y_DMA_RX_FIFO_EN; } else { spi->ctrl1 &= ~(MXC_F_SPI17Y_CTRL1_RX_NUM_CHAR); spi->dma &= ~(MXC_F_SPI17Y_DMA_RX_FIFO_EN); } // Must use TXFIFO and NUM in full duplex if (req->width == SPI17Y_WIDTH_1 && !((spi->ctrl2 & MXC_F_SPI17Y_CTRL2_THREE_WIRE)>> MXC_F_SPI17Y_CTRL2_THREE_WIRE_POS)) { if (req->tx_data == NULL) { // Must have something to send, so we'll use the rx_data buffer initialized to 0. memset(req->rx_data, 0, (req->bits > 8 ? req->len << 1 : req->len)); req->tx_data = req->rx_data; req->tx_num = 0; } } if(req->tx_data != NULL) { MXC_SETFIELD(spi->ctrl1, MXC_F_SPI17Y_CTRL1_TX_NUM_CHAR, req->len << MXC_F_SPI17Y_CTRL1_TX_NUM_CHAR_POS); spi->dma |= MXC_F_SPI17Y_DMA_TX_FIFO_EN; } else { spi->dma &= ~(MXC_F_SPI17Y_DMA_TX_FIFO_EN); } spi->dma |= MXC_F_SPI17Y_DMA_TX_FIFO_CLEAR | MXC_F_SPI17Y_DMA_RX_FIFO_CLEAR; spi->ctrl0 |= (MXC_F_SPI17Y_CTRL0_EN); states[spi_num].deass = req->deass; // Clear master done flag spi->int_fl = MXC_F_SPI17Y_INT_FL_M_DONE; return E_NO_ERROR; } /* ************************************************************************** */ void SPI17Y_Handler(mxc_spi17y_regs_t *spi) { int spi_num, rx_avail; uint32_t flags; // Clear the interrupt flags spi->int_en = 0; flags = spi->int_fl; spi->int_fl = flags; spi_num = MXC_SPI17Y_GET_IDX(spi); // Figure out if this SPI17Y has an active request if ((states[spi_num].req != NULL) && (flags)) { if ((spi->ctrl0 & MXC_F_SPI17Y_CTRL0_MASTER)>> MXC_F_SPI17Y_CTRL0_MASTER_POS) { do { SPI17Y_MasterTransHandler(spi, states[spi_num].req, 1); rx_avail = (spi->dma & MXC_F_SPI17Y_DMA_RX_FIFO_CNT) >> MXC_F_SPI17Y_DMA_RX_FIFO_CNT_POS; } while ((states[spi_num].req->rx_data != NULL) && (rx_avail > (spi->dma & MXC_F_SPI17Y_DMA_RX_FIFO_LEVEL) >>MXC_F_SPI17Y_DMA_RX_FIFO_LEVEL_POS)); } else { do { SPI17Y_SlaveTransHandler(spi, states[spi_num].req, 1); rx_avail = (spi->dma & MXC_F_SPI17Y_DMA_RX_FIFO_CNT) >> MXC_F_SPI17Y_DMA_RX_FIFO_CNT_POS; } while ((states[spi_num].req->rx_data != NULL) && (rx_avail > (spi->dma & MXC_F_SPI17Y_DMA_RX_FIFO_LEVEL) >>MXC_F_SPI17Y_DMA_RX_FIFO_LEVEL_POS)); } } } /* ************************************************************************** */ int SPI17Y_MasterTrans(mxc_spi17y_regs_t *spi,spi17y_req_t *req) { int error; if ((error =SPI17Y_TransSetup(spi, req, 1)) != E_NO_ERROR) { return error; } req->callback = NULL; while (SPI17Y_MasterTransHandler(spi,req,0)==0) { } while (!(spi->int_fl & MXC_F_SPI17Y_INT_FL_M_DONE)) { } return E_NO_ERROR; } /* ************************************************************************** */ int SPI17Y_SlaveTrans(mxc_spi17y_regs_t *spi, spi17y_req_t *req) { int error; if ((error =SPI17Y_TransSetup(spi, req,0)) != E_NO_ERROR) { return error; } req->callback = NULL; while (SPI17Y_SlaveTransHandler(spi,req,0)==0) { } return E_NO_ERROR; } /* ************************************************************************** */ int SPI17Y_MasterTransAsync(mxc_spi17y_regs_t *spi, spi17y_req_t *req) { int error; if ((error =SPI17Y_TransSetup(spi, req, 1))!= E_NO_ERROR) { return error; } SPI17Y_MasterTransHandler(spi,req, 1); return E_NO_ERROR; } /* ************************************************************************** */ int SPI17Y_SlaveTransAsync(mxc_spi17y_regs_t *spi, spi17y_req_t *req) { int error; if ((error =SPI17Y_TransSetup(spi, req, 0)) != E_NO_ERROR) { return error; } SPI17Y_SlaveTransHandler(spi,req, 1); return E_NO_ERROR; } /* ************************************************************************** */ int SPI17Y_MasterTransHandler(mxc_spi17y_regs_t *spi, spi17y_req_t *req,uint8_t async) { int retval; int spi_num; spi_num = MXC_SPI17Y_GET_IDX(spi); // Leave slave select asserted at the end of the transaction if (!req->deass) { spi->ctrl0 |= MXC_F_SPI17Y_CTRL0_SS_CTRL; } retval = SPI17Y_TransHandler(spi,req, async); if (!states[spi_num].started) { spi->ctrl0 |= MXC_F_SPI17Y_CTRL0_START; states[spi_num].started = 1; } // Deassert slave select at the end of the transaction if (req->deass) { spi->ctrl0 &= ~MXC_F_SPI17Y_CTRL0_SS_CTRL; } return retval; } /* ************************************************************************** */ int SPI17Y_SlaveTransHandler(mxc_spi17y_regs_t *spi, spi17y_req_t *req, uint8_t async) { return SPI17Y_TransHandler(spi,req, async); } /* ************************************************************************** */ // Returns non-zero if transactions is complete, or 0 if not. int SPI17Y_TransHandler(mxc_spi17y_regs_t *spi, spi17y_req_t *req, uint8_t async) { unsigned tx_avail, rx_avail; int remain, spi_num; uint32_t int_en =0; uint32_t length =0; spi_num = MXC_SPI17Y_GET_IDX(spi); // Read/write 2x number of bytes if larger character size if (req->bits > 8) { length = req->len*2; } else { length = req->len; } if (req->tx_data != NULL) { // Need to know when all bytes are transmitted, so the callback can be triggered. int_en |= MXC_F_SPI17Y_INT_EN_TX_EMPTY; // Calculate how many bytes we can write to the FIFO tx_avail = MXC_SPI17Y_FIFO_DEPTH - ((spi->dma & MXC_F_SPI17Y_DMA_TX_FIFO_CNT) >> MXC_F_SPI17Y_DMA_TX_FIFO_CNT_POS); if ((length - req->tx_num) < tx_avail) { tx_avail = (length - req->tx_num); } if (req->bits > 8) { tx_avail &= ~(unsigned)0x1; } // Write the FIFO while (tx_avail) { if (tx_avail > 3) { memcpy((void*)&spi->data32,&((uint8_t*)req->tx_data)[req->tx_num], 4); tx_avail -= 4; req->tx_num += 4; } else if (tx_avail > 1) { memcpy((void*)&spi->data16[0],&((uint8_t*)req->tx_data)[req->tx_num], 2); tx_avail -= 2; req->tx_num += 2; } else if (req->bits<=8) { spi->data8[0] = ((uint8_t*)req->tx_data)[req->tx_num++]; tx_avail -= 1; } } } remain = length - req->tx_num; // Set the TX interrupts if (remain) { if (remain > MXC_SPI17Y_FIFO_DEPTH) { // Set the TX FIFO almost empty interrupt if we have to refill spi->dma = ((spi->dma & ~MXC_F_SPI17Y_DMA_TX_FIFO_LEVEL) | ((MXC_SPI17Y_FIFO_DEPTH) << MXC_F_SPI17Y_DMA_TX_FIFO_LEVEL_POS)); } else { spi->dma = ((spi->dma & ~MXC_F_SPI17Y_DMA_TX_FIFO_LEVEL) | ((remain) << MXC_F_SPI17Y_DMA_TX_FIFO_LEVEL_POS)); } int_en |= MXC_F_SPI17Y_INT_EN_TX_THRESH; } // Break out if we've transmitted all the bytes and not receiving if ((req->rx_data == NULL) && (req->tx_num == length) && ((spi->dma & MXC_F_SPI17Y_DMA_TX_FIFO_CNT) == 0)) { spi->int_en = 0; int_en = 0; mxc_free_lock((uint32_t*)&states[spi_num].req); // Callback if not NULL if (req->callback != NULL) { req->callback(req, E_NO_ERROR); } return 1; } // Read the RX FIFO if (req->rx_data != NULL) { // Wait for there to be data in the RX FIFO rx_avail = (spi->dma & MXC_F_SPI17Y_DMA_RX_FIFO_CNT) >> MXC_F_SPI17Y_DMA_RX_FIFO_CNT_POS; if ((length - req->rx_num) < rx_avail) { rx_avail = (length - req->rx_num); } if (req->bits <= 8 || rx_avail >= 2) { // Read from the FIFO while (rx_avail) { if (rx_avail > 3) { memcpy(&((uint8_t*)req->rx_data)[req->rx_num], (void*)&spi->data32, 4); rx_avail -= 4; req->rx_num += 4; } else if (rx_avail > 1) { memcpy(&((uint8_t*)req->rx_data)[req->rx_num], (void*)&spi->data16[0], 2); rx_avail -= 2; req->rx_num += 2; } else { ((uint8_t*)req->rx_data)[req->rx_num++] = spi->data8[0]; rx_avail -= 1; } // Don't read less than 2 bytes if we are using greater than 8 bit characters if (rx_avail == 1 && req->bits > 8) { break; } } } remain = length - req->rx_num; if (remain) { if (remain > MXC_SPI17Y_FIFO_DEPTH) { spi->dma = ((spi->dma & ~MXC_F_SPI17Y_DMA_RX_FIFO_LEVEL) | ((2) << MXC_F_SPI17Y_DMA_RX_FIFO_LEVEL_POS)); } else { spi->dma = ((spi->dma & ~MXC_F_SPI17Y_DMA_RX_FIFO_LEVEL) | ((remain-1) << MXC_F_SPI17Y_DMA_RX_FIFO_LEVEL_POS)); } int_en |= MXC_F_SPI17Y_INT_EN_RX_THRESH; } // Break out if we've received all the bytes and we're not transmitting if ((req->tx_data == NULL) && (req->rx_num == length)) { spi->int_en = 0; int_en = 0; mxc_free_lock((uint32_t*)&states[spi_num].req); // Callback if not NULL if (req->callback != NULL) { req->callback(req, E_NO_ERROR); } return 1; } } // Break out once we've transmitted and received all of the data if ((req->rx_num == length) && (req->tx_num == length) && ((spi->dma & MXC_F_SPI17Y_DMA_TX_FIFO_CNT) == 0)) { spi->int_en = 0; int_en = 0; mxc_free_lock((uint32_t*)&states[spi_num].req); // Callback if not NULL if (req->callback != NULL) { req->callback(req, E_NO_ERROR); } return 1; } if(async){ spi->int_en = int_en; } return 0; } /* ************************************************************************* */ int SPI17Y_AbortAsync(spi17y_req_t *req) { int spi_num; mxc_spi17y_regs_t *spi; // Check the input parameters if (req == NULL) { return E_BAD_PARAM; } // Find the request, set to NULL for (spi_num = 0; spi_num < MXC_SPI17Y_INSTANCES; spi_num++) { if (req == states[spi_num].req) { spi = MXC_SPI17Y_GET_SPI17Y(spi_num); // Disable interrupts, clear the flags spi->int_en = 0; spi->int_fl = spi->int_fl; // Reset the SPI17Y to cancel the on ongoing transaction spi->ctrl0 &= ~(MXC_F_SPI17Y_CTRL0_EN); spi->ctrl0 |= (MXC_F_SPI17Y_CTRL0_EN); // Unlock this SPI17Y mxc_free_lock((uint32_t*)&states[spi_num].req); // Callback if not NULL if (req->callback != NULL) { req->callback(req, E_ABORT); } return E_NO_ERROR; } } return E_BAD_PARAM; } // ***************************************************************************** void SPI17Y_Enable(mxc_spi17y_regs_t* spi) { spi->ctrl0 |= (MXC_F_SPI17Y_CTRL0_EN); } // ***************************************************************************** void SPI17Y_Disable(mxc_spi17y_regs_t* spi) { spi->ctrl0 &= ~(MXC_F_SPI17Y_CTRL0_EN); } // ***************************************************************************** void SPI17Y_Clear_fifo(mxc_spi17y_regs_t* spi) { spi->dma |= MXC_F_SPI17Y_DMA_TX_FIFO_CLEAR | MXC_F_SPI17Y_DMA_RX_FIFO_CLEAR; }