t
Fork of mbed-dev by
targets/TARGET_Maxim/TARGET_MAX32625/mxc/spim.c
- Committer:
- amithy
- Date:
- 2017-11-09
- Revision:
- 178:c26431f84b0d
- Parent:
- 150:02e0a0aed4ec
File content as of revision 178:c26431f84b0d:
/******************************************************************************* * 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: 2016-07-26 08:41:27 -0500 (Tue, 26 Jul 2016) $ * $Revision: 23789 $ * ******************************************************************************/ /** * @file spim.c * @brief SPI Master driver source. */ /***** Includes *****/ #include <stddef.h> #include <string.h> #include "mxc_config.h" #include "mxc_assert.h" #include "mxc_lock.h" #include "spim.h" /***** Definitions *****/ #define SPIM_MAX_BYTE_LEN 32 #define SPIM_MAX_PAGE_LEN 32 /***** Globals *****/ // Saves the state of the non-blocking requests typedef struct { spim_req_t *req; unsigned head_rem; } spim_req_head_t; static spim_req_head_t states[MXC_CFG_SPIM_INSTANCES]; /***** Functions *****/ static unsigned SPIM_ReadRXFIFO(mxc_spim_regs_t *spim, mxc_spim_fifo_regs_t *fifo, uint8_t *data, unsigned len); static uint32_t SPIM_TransHandler(mxc_spim_regs_t *spim, spim_req_t *req, int spim_num); /******************************************************************************/ int SPIM_Init(mxc_spim_regs_t *spim, const spim_cfg_t *cfg, const sys_cfg_spim_t *sys_cfg) { int err, spim_num; uint32_t spim_clk, clocks; spim_num = MXC_SPIM_GET_IDX(spim); MXC_ASSERT(spim_num >= 0); // Check the input parameters if(cfg == NULL) return E_NULL_PTR; if(cfg->baud == 0) return E_BAD_PARAM; // Set system level configurations if ((err = SYS_SPIM_Init(spim, cfg, sys_cfg)) != E_NO_ERROR) { return err; } /* Configure the baud, make sure the SPIM clk is enabled and the baud is less than the maximum */ spim_clk = SYS_SPIM_GetFreq(spim); if((spim_clk == 0) || ((spim_clk == SystemCoreClock) && ((spim_clk/2) < cfg->baud))) { return E_BAD_PARAM; } // Initialize state pointers states[spim_num].req = NULL; states[spim_num].head_rem = 0; // Drain the FIFOs, enable SPIM, enable SCK Feedback spim->gen_ctrl = 0; spim->gen_ctrl = (MXC_F_SPIM_GEN_CTRL_SPI_MSTR_EN | MXC_F_SPIM_GEN_CTRL_TX_FIFO_EN | MXC_F_SPIM_GEN_CTRL_RX_FIFO_EN | MXC_F_SPIM_GEN_CTRL_ENABLE_SCK_FB_MODE); // Set mode and page size spim->mstr_cfg = (((cfg->mode << MXC_F_SPIM_MSTR_CFG_SPI_MODE_POS) & MXC_F_SPIM_MSTR_CFG_SPI_MODE) | MXC_S_SPIM_MSTR_CFG_PAGE_32B | (0x2 << MXC_F_SPIM_MSTR_CFG_ACT_DELAY_POS)); // Configure the SSEL polarity spim->ss_sr_polarity = ((cfg->ssel_pol << MXC_F_SPIM_SS_SR_POLARITY_SS_POLARITY_POS) & MXC_F_SPIM_SS_SR_POLARITY_SS_POLARITY); #if(MXC_SPIM_REV == 0) // Disable the feedback clock in modes 1 and 2 if((cfg->mode == 1) || (cfg->mode == 2)) { spim->gen_ctrl &= ~MXC_F_SPIM_GEN_CTRL_ENABLE_SCK_FB_MODE; spim->mstr_cfg |= (0x1 << MXC_F_SPIM_MSTR_CFG_SDIO_SAMPLE_POINT_POS); } #else // Increase the RX FIFO margin MXC_SPIM1->spcl_ctrl = ((MXC_SPIM1->spcl_ctrl & ~(MXC_F_SPIM_SPCL_CTRL_RX_FIFO_MARGIN)) | (0x3 << MXC_F_SPIM_SPCL_CTRL_RX_FIFO_MARGIN_POS)); #endif // Calculate the hi/lo clock setting if(spim_clk/2 > cfg->baud) { /* Disable the feedback mode and use the sample mode with an appropriate hi/lo clk to achieve the lower baud rate */ spim->gen_ctrl &= ~MXC_F_SPIM_GEN_CTRL_ENABLE_SCK_FB_MODE; clocks = (spim_clk / (2*cfg->baud)); if(clocks == 0 || clocks > 0x10) { return E_BAD_PARAM; } // 0 => 16 in the 4 bit field for HI_CLK and LO_CLK if(clocks == 0x10) { clocks = 0; } } else { // Continue to use feedback mode and set hi/lo clk to 1 clocks = 1; } spim->mstr_cfg |= (((clocks << MXC_F_SPIM_MSTR_CFG_SCK_HI_CLK_POS) & MXC_F_SPIM_MSTR_CFG_SCK_HI_CLK) | ((clocks << MXC_F_SPIM_MSTR_CFG_SCK_LO_CLK_POS) & MXC_F_SPIM_MSTR_CFG_SCK_LO_CLK)); return E_NO_ERROR; } /******************************************************************************/ int SPIM_Shutdown(mxc_spim_regs_t *spim) { int spim_num, err; spim_req_t *temp_req; // Disable and clear interrupts spim->inten = 0; spim->intfl = spim->intfl; // Disable SPIM and FIFOS spim->gen_ctrl &= ~(MXC_F_SPIM_GEN_CTRL_SPI_MSTR_EN | MXC_F_SPIM_GEN_CTRL_TX_FIFO_EN | MXC_F_SPIM_GEN_CTRL_RX_FIFO_EN); // Call all of the pending callbacks for this SPIM spim_num = MXC_SPIM_GET_IDX(spim); if(states[spim_num].req != NULL) { // Save the request temp_req = states[spim_num].req; // Unlock this SPIM mxc_free_lock((uint32_t*)&states[spim_num].req); // Callback if not NULL if(temp_req->callback != NULL) { temp_req->callback(temp_req, E_SHUTDOWN); } } // Clear system level configurations if ((err = SYS_SPIM_Shutdown(spim)) != E_NO_ERROR) { return err; } return E_NO_ERROR; } /******************************************************************************/ int SPIM_Clocks(mxc_spim_regs_t *spim, uint32_t len, uint8_t ssel, uint8_t deass) { int spim_num; mxc_spim_fifo_regs_t *fifo; uint16_t header = 0x1; uint32_t num = len; // Make sure the SPIM has been initialized if((spim->gen_ctrl & MXC_F_SPIM_GEN_CTRL_SPI_MSTR_EN) == 0) return E_UNINITIALIZED; if(!(len > 0)) { return E_NO_ERROR; } // Check the previous transaction if we're switching the slave select if((ssel != ((spim->mstr_cfg & MXC_F_SPIM_MSTR_CFG_SLAVE_SEL) >> MXC_F_SPIM_MSTR_CFG_SLAVE_SEL_POS)) && (spim->gen_ctrl & MXC_F_SPIM_GEN_CTRL_BB_SS_IN_OUT)) { // Return E_BUSY if the slave select is still asserted return E_BUSY; } // Attempt to lock this SPIM spim_num = MXC_SPIM_GET_IDX(spim); if(mxc_get_lock((uint32_t*)&states[spim_num].req, 1) != E_NO_ERROR) { return E_BUSY; } // Set which slave select we are using spim->mstr_cfg = ((spim->mstr_cfg & ~MXC_F_SPIM_MSTR_CFG_SLAVE_SEL) | ((ssel << MXC_F_SPIM_MSTR_CFG_SLAVE_SEL_POS) & MXC_F_SPIM_MSTR_CFG_SLAVE_SEL)); //force deass to a 1 or 0 deass = !!deass; #if(MXC_SPIM_REV == 0) // Wait for all of the data to transmit while(spim->fifo_ctrl & MXC_F_SPIM_FIFO_CTRL_TX_FIFO_USED) {} // Disable the feedback clock, save state uint32_t gen_ctrl = spim->gen_ctrl; spim->gen_ctrl &= ~MXC_F_SPIM_GEN_CTRL_ENABLE_SCK_FB_MODE; #endif // Get the TX and RX FIFO for this SPIM fifo = MXC_SPIM_GET_SPIM_FIFO(spim_num); // Send the headers to transmit the clocks while(len > 32) { fifo->trans_16[0] = header; fifo->trans_16[0] = 0xF000; fifo->trans_16[0] = 0xF000; len -= 32; } if(len) { if(len < 32) { header |= (len << 4); } header |= (deass << 13); fifo->trans_16[0] = header; if(len > 16) { fifo->trans_16[0] = 0xF000; } fifo->trans_16[0] = 0xF000; } #if(MXC_SPIM_REV == 0) // Wait for all of the data to transmit while(spim->fifo_ctrl & MXC_F_SPIM_FIFO_CTRL_TX_FIFO_USED) {} // Restore feedback clock setting spim->gen_ctrl |= (gen_ctrl & MXC_F_SPIM_GEN_CTRL_ENABLE_SCK_FB_MODE); #endif // Unlock this SPIM mxc_free_lock((uint32_t*)&states[spim_num].req); return num; } /******************************************************************************/ int SPIM_Trans(mxc_spim_regs_t *spim, spim_req_t *req) { int spim_num; // Make sure the SPIM has been initialized if((spim->gen_ctrl & MXC_F_SPIM_GEN_CTRL_SPI_MSTR_EN) == 0) return E_UNINITIALIZED; // Check the input parameters if(req == NULL) return E_NULL_PTR; if((req->rx_data == NULL) && (req->tx_data == NULL)) return E_NULL_PTR; if(!(req->len > 0)) { return E_NO_ERROR; } // Check the previous transaction if we're switching the slave select if((req->ssel != ((spim->mstr_cfg & MXC_F_SPIM_MSTR_CFG_SLAVE_SEL) >> MXC_F_SPIM_MSTR_CFG_SLAVE_SEL_POS)) && (spim->gen_ctrl & MXC_F_SPIM_GEN_CTRL_BB_SS_IN_OUT)) { // Return E_BUSY if the slave select is still asserted return E_BUSY; } // Attempt to register this write request spim_num = MXC_SPIM_GET_IDX(spim); if(mxc_get_lock((uint32_t*)&states[spim_num].req, (uint32_t)req) != E_NO_ERROR) { return E_BUSY; } // Set which slave select we are using spim->mstr_cfg = ((spim->mstr_cfg & ~MXC_F_SPIM_MSTR_CFG_SLAVE_SEL) | ((req->ssel << MXC_F_SPIM_MSTR_CFG_SLAVE_SEL_POS) & MXC_F_SPIM_MSTR_CFG_SLAVE_SEL)); //force deass to a 1 or 0 req->deass = !!req->deass; // Clear the number of bytes counter req->read_num = 0; req->write_num = 0; req->callback = NULL; states[spim_num].head_rem = 0; // Start the transaction, keep calling the handler until complete while(SPIM_TransHandler(spim, req, spim_num) != 0); if(req->tx_data == NULL) { return req->read_num; } return req->write_num; } /******************************************************************************/ int SPIM_TransAsync(mxc_spim_regs_t *spim, spim_req_t *req) { int spim_num; // Make sure the SPIM has been initialized if((spim->gen_ctrl & MXC_F_SPIM_GEN_CTRL_SPI_MSTR_EN) == 0) return E_UNINITIALIZED; // Check the input parameters if(req == NULL) return E_NULL_PTR; if((req->rx_data == NULL) && (req->tx_data == NULL)) return E_NULL_PTR; if(!(req->len > 0)) { return E_NO_ERROR; } // Check the previous transaction if we're switching the slave select if((req->ssel != ((spim->mstr_cfg & MXC_F_SPIM_MSTR_CFG_SLAVE_SEL) >> MXC_F_SPIM_MSTR_CFG_SLAVE_SEL_POS)) && (spim->gen_ctrl & MXC_F_SPIM_GEN_CTRL_BB_SS_IN_OUT)) { // Return E_BUSY if the slave select is still asserted return E_BUSY; } // Attempt to register this write request spim_num = MXC_SPIM_GET_IDX(spim); if(mxc_get_lock((uint32_t*)&states[spim_num].req, (uint32_t)req) != E_NO_ERROR) { return E_BUSY; } // Set which slave select we are using spim->mstr_cfg = ((spim->mstr_cfg & ~MXC_F_SPIM_MSTR_CFG_SLAVE_SEL) | ((req->ssel << MXC_F_SPIM_MSTR_CFG_SLAVE_SEL_POS) & MXC_F_SPIM_MSTR_CFG_SLAVE_SEL)); //force deass to a 1 or 0 req->deass = !!req->deass; // Clear the number of bytes counter req->read_num = 0; req->write_num = 0; states[spim_num].head_rem = 0; // Start the transaction, enable the interrupts spim->inten = SPIM_TransHandler(spim, req, spim_num); return E_NO_ERROR; } /******************************************************************************/ int SPIM_AbortAsync(spim_req_t *req) { int spim_num; mxc_spim_regs_t *spim; // Check the input parameters if(req == NULL) { return E_BAD_PARAM; } // Find the request, set to NULL for(spim_num = 0; spim_num < MXC_CFG_SPIM_INSTANCES; spim_num++) { if(req == states[spim_num].req) { spim = MXC_SPIM_GET_SPIM(spim_num); // Disable interrupts, clear the flags spim->inten = 0; spim->intfl = spim->intfl; // Reset the SPIM to cancel the on ongoing transaction spim->gen_ctrl &= ~(MXC_F_SPIM_GEN_CTRL_SPI_MSTR_EN); spim->gen_ctrl |= (MXC_F_SPIM_GEN_CTRL_SPI_MSTR_EN); // Unlock this SPIM mxc_free_lock((uint32_t*)&states[spim_num].req); // Callback if not NULL if(req->callback != NULL) { req->callback(req, E_ABORT); } return E_NO_ERROR; } } return E_BAD_PARAM; } /******************************************************************************/ void SPIM_Handler(mxc_spim_regs_t *spim) { int spim_num; uint32_t flags; // Clear the interrupt flags spim->inten = 0; flags = spim->intfl; spim->intfl = flags; spim_num = MXC_SPIM_GET_IDX(spim); // Figure out if this SPIM has an active request if((states[spim_num].req != NULL) && (flags)) { spim->inten = SPIM_TransHandler(spim, states[spim_num].req, spim_num); } } /******************************************************************************/ int SPIM_Busy(mxc_spim_regs_t *spim) { // Check to see if there are any ongoing transactions if((states[MXC_SPIM_GET_IDX(spim)].req == NULL) && !(spim->fifo_ctrl & MXC_F_SPIM_FIFO_CTRL_TX_FIFO_USED)) { return E_NO_ERROR; } return E_BUSY; } /******************************************************************************/ int SPIM_PrepForSleep(mxc_spim_regs_t *spim) { if(SPIM_Busy(spim) != E_NO_ERROR) { return E_BUSY; } // Disable interrupts spim->inten = 0; return E_NO_ERROR; } /******************************************************************************/ static unsigned SPIM_ReadRXFIFO(mxc_spim_regs_t *spim, mxc_spim_fifo_regs_t *fifo, uint8_t *data, unsigned len) { unsigned num = 0; unsigned avail = ((spim->fifo_ctrl & MXC_F_SPIM_FIFO_CTRL_RX_FIFO_USED) >> MXC_F_SPIM_FIFO_CTRL_RX_FIFO_USED_POS); // Get data from the RXFIFO while(avail && (len - num)) { if((avail >= 4) && ((len-num) >= 4)) { // Save data from the RXFIFO uint32_t temp = fifo->rslts_32[0]; data[num+0] = ((temp & 0x000000FF) >> 0); data[num+1] = ((temp & 0x0000FF00) >> 8); data[num+2] = ((temp & 0x00FF0000) >> 16); data[num+3] = ((temp & 0xFF000000) >> 24); num+=4; avail-=4; } else if ((avail >= 2) && ((len-num) >= 2)) { // Save data from the RXFIFO uint16_t temp = fifo->rslts_16[0]; data[num+0] = ((temp & 0x00FF) >> 0); data[num+1] = ((temp & 0xFF00) >> 8); num+=2; avail-=2; } else { // Save data from the RXFIFO data[num] = fifo->rslts_8[0]; num+=1; avail-=1; } // Check to see if there is more data in the FIFO if(avail == 0) { avail = ((spim->fifo_ctrl & MXC_F_SPIM_FIFO_CTRL_RX_FIFO_USED) >> MXC_F_SPIM_FIFO_CTRL_RX_FIFO_USED_POS); } } return num; } uint16_t header_save; /******************************************************************************/ static uint32_t SPIM_TransHandler(mxc_spim_regs_t *spim, spim_req_t *req, int spim_num) { uint8_t read, write; uint16_t header; uint32_t pages, bytes, inten; unsigned remain, bytes_read, head_rem_temp, avail; mxc_spim_fifo_regs_t *fifo; inten = 0; // Get the FIFOS for this UART fifo = MXC_SPIM_GET_SPIM_FIFO(spim_num); // Figure out if we're reading if(req->rx_data != NULL) { read = 1; } else { read = 0; } // Figure out if we're writing if(req->tx_data != NULL) { write = 1; } else { write = 0; } // Read byte from the FIFO if we are reading if(read) { // Read all of the data in the RXFIFO, or until we don't need anymore bytes_read = SPIM_ReadRXFIFO(spim, fifo, &req->rx_data[req->read_num], (req->len - req->read_num)); req->read_num += bytes_read; // Adjust head_rem if we are only reading if(!write && (states[spim_num].head_rem > 0)) { states[spim_num].head_rem -= bytes_read; } // Figure out how many byte we have left to read if(states[spim_num].head_rem > 0) { remain = states[spim_num].head_rem; } else { remain = req->len - req->read_num; } if(remain) { // Set the RX interrupts if (remain > MXC_CFG_SPIM_FIFO_DEPTH) { spim->fifo_ctrl = ((spim->fifo_ctrl & ~MXC_F_SPIM_FIFO_CTRL_RX_FIFO_AF_LVL) | ((MXC_CFG_SPIM_FIFO_DEPTH - 2) << MXC_F_SPIM_FIFO_CTRL_RX_FIFO_AF_LVL_POS)); } else { spim->fifo_ctrl = ((spim->fifo_ctrl & ~MXC_F_SPIM_FIFO_CTRL_RX_FIFO_AF_LVL) | ((remain - 1) << MXC_F_SPIM_FIFO_CTRL_RX_FIFO_AF_LVL_POS)); } inten |= MXC_F_SPIM_INTEN_RX_FIFO_AF; } } // Figure out how many bytes we have left to send headers for if(write) { remain = req->len - req->write_num; } else { remain = req->len - req->read_num; } // See if we need to send a new header if(states[spim_num].head_rem <= 0 && remain) { // Set the transaction configuration in the header header = ((write << 0) | (read << 1) | (req->width << 9)); if(remain >= SPIM_MAX_BYTE_LEN) { // Send a 32 byte header if(remain == SPIM_MAX_BYTE_LEN) { header |= ((0x1 << 2) | (req->deass << 13)); // Save the number of bytes we need to write to the FIFO bytes = SPIM_MAX_BYTE_LEN; } else { // Send in increments of 32 byte pages header |= (0x2 << 2); pages = remain / SPIM_MAX_PAGE_LEN; if(pages >= 32) { // 0 maps to 32 in the header bytes = 32 * SPIM_MAX_PAGE_LEN; } else { header |= (pages << 4); bytes = pages * SPIM_MAX_PAGE_LEN; } // Check if this is the last header we will send if((remain - bytes) == 0) { header |= (req->deass << 13); } } header_save = header; fifo->trans_16[0] = header; // Save the number of bytes we need to write to the FIFO states[spim_num].head_rem = bytes; } else { // Send final header with the number of bytes remaining and if // we want to de-assert the SS at the end of the transaction header |= ((0x1 << 2) | (remain << 4) | (req->deass << 13)); fifo->trans_16[0] = header; states[spim_num].head_rem = remain; } } // Put data into the FIFO if we are writing remain = req->len - req->write_num; head_rem_temp = states[spim_num].head_rem; if(write && head_rem_temp) { // Fill the FIFO avail = (MXC_CFG_SPIM_FIFO_DEPTH - ((spim->fifo_ctrl & MXC_F_SPIM_FIFO_CTRL_TX_FIFO_USED) >> MXC_F_SPIM_FIFO_CTRL_TX_FIFO_USED_POS)); // Use memcpy for everything except the last byte in odd length transactions while((avail >= 2) && (head_rem_temp >= 2)) { unsigned length; if(head_rem_temp < avail) { length = head_rem_temp; } else { length = avail; } // Only memcpy even numbers length = ((length / 2) * 2); memcpy((void*)fifo->trans_32, &(req->tx_data[req->write_num]), length); head_rem_temp -= length; req->write_num += length; avail = (MXC_CFG_SPIM_FIFO_DEPTH - ((spim->fifo_ctrl & MXC_F_SPIM_FIFO_CTRL_TX_FIFO_USED) >> MXC_F_SPIM_FIFO_CTRL_TX_FIFO_USED_POS)); } // Copy the last byte and pad with 0xF0 to not get confused as header if((avail >= 1) && (head_rem_temp == 1)) { // Write the last byte fifo->trans_16[0] = (0xF000 | req->tx_data[req->write_num]); avail -= 1; req->write_num += 1; head_rem_temp -= 1; } states[spim_num].head_rem = head_rem_temp; remain = req->len - req->write_num; // Set the TX interrupts if(remain) { // Set the TX FIFO almost empty interrupt if we have to refill spim->fifo_ctrl = ((spim->fifo_ctrl & ~MXC_F_SPIM_FIFO_CTRL_TX_FIFO_AE_LVL) | ((MXC_CFG_SPIM_FIFO_DEPTH - 2) << MXC_F_SPIM_FIFO_CTRL_TX_FIFO_AE_LVL_POS)); inten |= MXC_F_SPIM_INTEN_TX_FIFO_AE; } } // Check to see if we've finished reading and writing if(((read && (req->read_num == req->len)) || !read) && ((req->write_num == req->len) || !write)) { // Disable interrupts spim->inten = 0; // Unlock this SPIM mxc_free_lock((uint32_t*)&states[spim_num].req); // Callback if not NULL if(req->callback != NULL) { req->callback(req, E_NO_ERROR); } } // Enable the SPIM interrupts return inten; }