/**************************************************************************** * arch/arm/src/stm32h7/stm32_qspi.c * * Copyright (C) 2016-2017, 2019 Gregory Nutt. All rights reserved. * Author: dev@ziggurat29.com * * 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 NuttX 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 * COPYRIGHT OWNER 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. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include "stm32_qspi.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "arm_internal.h" #include "arm_arch.h" #include "barriers.h" #include "stm32_gpio.h" #include "stm32_dma.h" #include "stm32_rcc.h" #include "hardware/stm32_qspi.h" #ifdef CONFIG_STM32H7_QUADSPI /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /* QSPI memory synchronization */ #define MEMORY_SYNC() do { ARM_DSB(); ARM_ISB(); } while (0) /* Ensure that the DMA buffers are word-aligned. */ #define ALIGN_SHIFT 2 #define ALIGN_MASK 3 #define ALIGN_UP(n) (((n)+ALIGN_MASK) & ~ALIGN_MASK) #define IS_ALIGNED(n) (((uint32_t)(n) & ALIGN_MASK) == 0) /* Debug ********************************************************************/ /* Check if QSPI debug is enabled */ #ifndef CONFIG_DEBUG_DMA # undef CONFIG_STM32H7_QSPI_DMADEBUG #endif #define DMA_INITIAL 0 #define DMA_AFTER_SETUP 1 #define DMA_AFTER_START 2 #define DMA_CALLBACK 3 #define DMA_TIMEOUT 3 #define DMA_END_TRANSFER 4 #define DMA_NSAMPLES 5 /* Can't have both interrupt-driven QSPI and DMA QSPI */ #if defined(CONFIG_STM32H7_QSPI_INTERRUPTS) && defined(CONFIG_STM32H7_QSPI_DMA) # error "Cannot enable both interrupt mode and DMA mode for QSPI" #endif /* Sanity check that board.h defines requisite QSPI pinmap options for */ #if (!defined(GPIO_QSPI_CS) || !defined(GPIO_QSPI_IO0) || !defined(GPIO_QSPI_IO1) || \ !defined(GPIO_QSPI_IO2) || !defined(GPIO_QSPI_IO3) || !defined(GPIO_QSPI_SCK)) # error you must define QSPI pinmapping options for GPIO_QSPI_CS GPIO_QSPI_IO0 \ GPIO_QSPI_IO1 GPIO_QSPI_IO2 GPIO_QSPI_IO3 GPIO_QSPI_SCK in your board.h #endif #ifdef CONFIG_STM32H7_QSPI_DMA # ifdef DMAMAP_QUADSPI /* QSPI DMA Channel/Stream selection. There * are multiple DMA stream options that must be dis-ambiguated in the board.h * file. */ # define DMACHAN_QUADSPI DMAMAP_QUADSPI # endif # if defined(CONFIG_STM32H7_QSPI_DMAPRIORITY_LOW) # define QSPI_DMA_PRIO DMA_SCR_PRILO # elif defined(CONFIG_STM32H7_QSPI_DMAPRIORITY_MEDIUM) # define QSPI_DMA_PRIO DMA_SCR_PRIMED # elif defined(CONFIG_STM32H7_QSPI_DMAPRIORITY_HIGH) # define QSPI_DMA_PRIO DMA_SCR_PRIHI # elif defined(CONFIG_STM32H7_QSPI_DMAPRIORITY_VERYHIGH) # define QSPI_DMA_PRIO DMA_SCR_PRIVERYHI # else # define QSPI_DMA_PRIO DMA_SCR_PRIMED # endif #endif /* CONFIG_STM32H7_QSPI_DMA */ #ifndef STM32_SYSCLK_FREQUENCY # error your board.h needs to define the value of STM32_SYSCLK_FREQUENCY #endif #if !defined(CONFIG_STM32H7_QSPI_FLASH_SIZE) || 0 == CONFIG_STM32H7_QSPI_FLASH_SIZE # error you must specify a positive flash size via CONFIG_STM32H7_QSPI_FLASH_SIZE #endif /* DMA timeout. The value is not critical; we just don't want the system to * hang in the event that a DMA does not finish. */ #define DMA_TIMEOUT_MS (800) #define DMA_TIMEOUT_TICKS MSEC2TICK(DMA_TIMEOUT_MS) /* Clocking *****************************************************************/ /* The board.h file may choose a different clock source for QUADSPI * peripherial by defining the BOARD_QSPI_CLK macro to one of the * RCC_D1CCIPR_QSPISEL_XXX values (XXX = HCLK, PLL1, PLL2, PER). * QUADSPI clock defaults to HCLK. */ #ifndef BOARD_QSPI_CLK /* Clock QUADSPI from HCLK by default */ # define BOARD_QSPI_CLK RCC_D1CCIPR_QSPISEL_HCLK #endif /* The QSPI bit rate clock is generated by dividing the peripheral clock by * a value between 1 and 255. * * Find out the frequency of the QSPI clock. */ #if BOARD_QSPI_CLK == RCC_D1CCIPR_QSPISEL_HCLK # define QSPI_CLK_FREQUENCY STM32_HCLK_FREQUENCY #elif BOARD_QSPI_CLK == RCC_D1CCIPR_QSPISEL_PLL1 # define QSPI_CLK_FREQUENCY STM32_PLL1Q_FREQUENCY #elif BOARD_QSPI_CLK == RCC_D1CCIPR_QSPISEL_PLL2 # define QSPI_CLK_FREQUENCY STM32_PLL2R_FREQUENCY #elif BOARD_QSPI_CLK == RCC_D1CCIPR_QSPISEL_PER # define QSPI_CLK_FREQUENCY STM32_PER_FREQUENCY #else # error "BOARD_QSPI_CLK has unknown value!" #endif /**************************************************************************** * Private Types ****************************************************************************/ /* The state of the QSPI controller. * * NOTE: the STM32H7 supports only a single QSPI peripheral. Logic here is * designed to support multiple QSPI peripherals. */ struct stm32h7_qspidev_s { struct qspi_dev_s qspi; /* Externally visible part of the QSPI interface */ uint32_t base; /* QSPI controller register base address */ uint32_t frequency; /* Requested clock frequency */ uint32_t actual; /* Actual clock frequency */ uint8_t mode; /* Mode 0,3 */ uint8_t nbits; /* Width of word in bits (8 to 32) */ uint8_t intf; /* QSPI controller number (0) */ bool initialized; /* TRUE: Controller has been initialized */ sem_t exclsem; /* Assures mutually exclusive access to QSPI */ bool memmap; /* TRUE: Controller is in memory mapped mode */ #ifdef CONFIG_STM32H7_QSPI_INTERRUPTS xcpt_t handler; /* Interrupt handler */ uint8_t irq; /* Interrupt number */ sem_t op_sem; /* Block until complete */ struct qspi_xctnspec_s *xctn; /* context of transaction in progress */ #endif #ifdef CONFIG_STM32H7_QSPI_DMA bool candma; /* DMA is supported */ sem_t dmawait; /* Used to wait for DMA completion */ int result; /* DMA result */ DMA_HANDLE dmach; /* QSPI DMA handle */ struct wdog_s dmadog; /* Watchdog that handles DMA timeouts */ #endif /* Debug stuff */ #ifdef CONFIG_STM32H7_QSPI_DMADEBUG struct stm32h7_dmaregs_s dmaregs[DMA_NSAMPLES]; #endif #ifdef CONFIG_STM32H7_QSPI_REGDEBUG bool wrlast; /* Last was a write */ uint32_t addresslast; /* Last address */ uint32_t valuelast; /* Last value */ int ntimes; /* Number of times */ #endif }; /* The QSPI transaction specification * * This is mostly the values of the CCR and DLR, AR, ABR, broken out into a C * structure since these fields need to be considered at various phases of * the transaction processing activity. */ struct qspi_xctnspec_s { uint8_t instrmode; /* 'instruction mode'; 0=none, 1=single, 2=dual, 3=quad */ uint8_t instr; /* the (8-bit) Instruction (if any) */ uint8_t addrmode; /* 'address mode'; 0=none, 1=single, 2=dual, 3=quad */ uint8_t addrsize; /* address size (n - 1); 0, 1, 2, 3 */ uint32_t addr; /* the address (if any) (1 to 4 bytes as per addrsize) */ uint8_t altbytesmode; /* 'alt bytes mode'; 0=none, 1=single, 2=dual, 3=quad */ uint8_t altbytessize; /* 'alt bytes' size (n - 1); 0, 1, 2, 3 */ uint32_t altbytes; /* the 'alt bytes' (if any) */ uint8_t dummycycles; /* number of Dummy Cycles; 0 - 32 */ uint8_t datamode; /* 'data mode'; 0=none, 1=single, 2=dual, 3=quad */ uint32_t datasize; /* number of data bytes (0xffffffff == undefined) */ FAR void *buffer; /* Data buffer */ uint8_t isddr; /* true if 'double data rate' */ uint8_t issioo; /* true if 'send instruction only once' mode */ #ifdef CONFIG_STM32H7_QSPI_INTERRUPTS uint8_t function; /* functional mode; to distinguish a read or write */ int8_t disposition; /* how it all turned out */ uint32_t idxnow; /* index into databuffer of current byte in transfer */ #endif }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ /* Helpers */ #ifdef CONFIG_STM32H7_QSPI_REGDEBUG static bool qspi_checkreg(struct stm32h7_qspidev_s *priv, bool wr, uint32_t value, uint32_t address); #else # define qspi_checkreg(priv,wr,value,address) (false) #endif static inline uint32_t qspi_getreg(struct stm32h7_qspidev_s *priv, unsigned int offset); static inline void qspi_putreg(struct stm32h7_qspidev_s *priv, uint32_t value, unsigned int offset); #ifdef CONFIG_DEBUG_SPI_INFO static void qspi_dumpregs(struct stm32h7_qspidev_s *priv, const char *msg); #else # define qspi_dumpregs(priv,msg) #endif #if defined(CONFIG_DEBUG_SPI_INFO) && defined(CONFIG_DEBUG_GPIO) static void qspi_dumpgpioconfig(const char *msg); #else # define qspi_dumpgpioconfig(msg) #endif /* Interrupts */ #ifdef CONFIG_STM32H7_QSPI_INTERRUPTS static int qspi0_interrupt(int irq, void *context, FAR void *arg); #endif /* DMA support */ #ifdef CONFIG_STM32H7_QSPI_DMA # ifdef CONFIG_STM32H7_QSPI_DMADEBUG # define qspi_dma_sample(s,i) stm32h7_dmasample((s)->dmach, &(s)->dmaregs[i]) static void qspi_dma_sampleinit(struct stm32h7_qspidev_s *priv); static void qspi_dma_sampledone(struct stm32h7_qspidev_s *priv); # else # define qspi_dma_sample(s,i) # define qspi_dma_sampleinit(s) # define qspi_dma_sampledone(s) # endif # ifndef CONFIG_STM32H7_QSPI_DMATHRESHOLD # define CONFIG_STM32H7_QSPI_DMATHRESHOLD 4 # endif #endif /* QSPI methods */ static int qspi_lock(struct qspi_dev_s *dev, bool lock); static uint32_t qspi_setfrequency(struct qspi_dev_s *dev, uint32_t frequency); static void qspi_setmode(struct qspi_dev_s *dev, enum qspi_mode_e mode); static void qspi_setbits(struct qspi_dev_s *dev, int nbits); static int qspi_command(struct qspi_dev_s *dev, struct qspi_cmdinfo_s *cmdinfo); static int qspi_memory(struct qspi_dev_s *dev, struct qspi_meminfo_s *meminfo); static FAR void *qspi_alloc(FAR struct qspi_dev_s *dev, size_t buflen); static void qspi_free(FAR struct qspi_dev_s *dev, FAR void *buffer); /* Initialization */ static int qspi_hw_initialize(struct stm32h7_qspidev_s *priv); /**************************************************************************** * Private Data ****************************************************************************/ /* QSPI0 driver operations */ static const struct qspi_ops_s g_qspi0ops = { .lock = qspi_lock, .setfrequency = qspi_setfrequency, .setmode = qspi_setmode, .setbits = qspi_setbits, .command = qspi_command, .memory = qspi_memory, .alloc = qspi_alloc, .free = qspi_free, }; /* This is the overall state of the QSPI0 controller */ static struct stm32h7_qspidev_s g_qspi0dev = { .qspi = { .ops = &g_qspi0ops, }, .base = STM32_QUADSPI_BASE, #ifdef CONFIG_STM32H7_QSPI_INTERRUPTS .handler = qspi0_interrupt, .irq = STM32_IRQ_QUADSPI, #endif .intf = 0, #ifdef CONFIG_STM32H7_QSPI_DMA .candma = true, #endif }; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: qspi_checkreg * * Description: * Check if the current register access is a duplicate of the preceding. * * Input Parameters: * value - The value to be written * address - The address of the register to write to * * Returned Value: * true: This is the first register access of this type. * false: This is the same as the preceding register access. * ****************************************************************************/ #ifdef CONFIG_STM32H7_QSPI_REGDEBUG static bool qspi_checkreg(struct stm32h7_qspidev_s *priv, bool wr, uint32_t value, uint32_t address) { if (wr == priv->wrlast && /* Same kind of access? */ value == priv->valuelast && /* Same value? */ address == priv->addresslast) /* Same address? */ { /* Yes, then just keep a count of the number of times we did this. */ priv->ntimes++; return false; } else { /* Did we do the previous operation more than once? */ if (priv->ntimes > 0) { /* Yes... show how many times we did it */ spiinfo("...[Repeats %d times]...\n", priv->ntimes); } /* Save information about the new access */ priv->wrlast = wr; priv->valuelast = value; priv->addresslast = address; priv->ntimes = 0; } /* Return true if this is the first time that we have done this operation */ return true; } #endif /**************************************************************************** * Name: qspi_getreg * * Description: * Read an QSPI register * ****************************************************************************/ static inline uint32_t qspi_getreg(struct stm32h7_qspidev_s *priv, unsigned int offset) { uint32_t address = priv->base + offset; uint32_t value = getreg32(address); #ifdef CONFIG_STM32H7_QSPI_REGDEBUG if (qspi_checkreg(priv, false, value, address)) { spiinfo("%08x->%08x\n", address, value); } #endif return value; } /**************************************************************************** * Name: qspi_putreg * * Description: * Write a value to an QSPI register * ****************************************************************************/ static inline void qspi_putreg(struct stm32h7_qspidev_s *priv, uint32_t value, unsigned int offset) { uint32_t address = priv->base + offset; #ifdef CONFIG_STM32H7_QSPI_REGDEBUG if (qspi_checkreg(priv, true, value, address)) { spiinfo("%08x<-%08x\n", address, value); } #endif putreg32(value, address); } /**************************************************************************** * Name: qspi_dumpregs * * Description: * Dump the contents of all QSPI registers * * Input Parameters: * priv - The QSPI controller to dump * msg - Message to print before the register data * * Returned Value: * None * ****************************************************************************/ #ifdef CONFIG_DEBUG_SPI_INFO static void qspi_dumpregs(struct stm32h7_qspidev_s *priv, const char *msg) { uint32_t regval; spiinfo("%s:\n", msg); #if 0 /* this extra verbose output may be helpful in some cases; you'll need * to make sure your syslog is large enough to accommodate extra output. */ regval = getreg32(priv->base + STM32_QUADSPI_CR_OFFSET); /* Control Register */ spiinfo("CR:%08x\n", regval); spiinfo(" EN:%1d ABORT:%1d DMAEN:%1d TCEN:%1d SSHIFT:%1d\n" " FTHRES: %d\n" " TEIE:%1d TCIE:%1d FTIE:%1d SMIE:%1d TOIE:%1d APMS:%1d PMM:%1d\n" " PRESCALER: %d\n", (regval & QSPI_CR_EN) ? 1 : 0, (regval & QSPI_CR_ABORT) ? 1 : 0, (regval & QSPI_CR_DMAEN) ? 1 : 0, (regval & QSPI_CR_TCEN) ? 1 : 0, (regval & QSPI_CR_SSHIFT) ? 1 : 0, (regval & QSPI_CR_FTHRES_MASK) >> QSPI_CR_FTHRES_SHIFT, (regval & QSPI_CR_TEIE) ? 1 : 0, (regval & QSPI_CR_TCIE) ? 1 : 0, (regval & QSPI_CR_FTIE) ? 1 : 0, (regval & QSPI_CR_SMIE) ? 1 : 0, (regval & QSPI_CR_TOIE) ? 1 : 0, (regval & QSPI_CR_APMS) ? 1 : 0, (regval & QSPI_CR_PMM) ? 1 : 0, (regval & QSPI_CR_PRESCALER_MASK) >> QSPI_CR_PRESCALER_SHIFT); regval = getreg32(priv->base + STM32_QUADSPI_DCR_OFFSET); /* Device Configuration Register */ spiinfo("DCR:%08x\n", regval); spiinfo(" CKMODE:%1d CSHT:%d FSIZE:%d\n", (regval & QSPI_DCR_CKMODE) ? 1 : 0, (regval & QSPI_DCR_CSHT_MASK) >> QSPI_DCR_CSHT_SHIFT, (regval & QSPI_DCR_FSIZE_MASK) >> QSPI_DCR_FSIZE_SHIFT); regval = getreg32(priv->base + STM32_QUADSPI_CCR_OFFSET); /* Communication Configuration Register */ spiinfo("CCR:%08x\n", regval); spiinfo(" INST:%02x IMODE:%d ADMODE:%d ADSIZE:%d ABMODE:%d\n" " ABSIZE:%d DCYC:%d DMODE:%d FMODE:%d\n" " SIOO:%1d DDRM:%1d\n", (regval & QSPI_CCR_INSTRUCTION_MASK) >> QSPI_CCR_INSTRUCTION_SHIFT, (regval & QSPI_CCR_IMODE_MASK) >> QSPI_CCR_IMODE_SHIFT, (regval & QSPI_CCR_ADMODE_MASK) >> QSPI_CCR_ADMODE_SHIFT, (regval & QSPI_CCR_ADSIZE_MASK) >> QSPI_CCR_ABSIZE_SHIFT, (regval & QSPI_CCR_ABMODE_MASK) >> QSPI_CCR_ABMODE_SHIFT, (regval & QSPI_CCR_ABSIZE_MASK) >> QSPI_CCR_ABSIZE_SHIFT, (regval & QSPI_CCR_DCYC_MASK) >> QSPI_CCR_DCYC_SHIFT, (regval & QSPI_CCR_DMODE_MASK) >> QSPI_CCR_DMODE_SHIFT, (regval & QSPI_CCR_FMODE_MASK) >> QSPI_CCR_FMODE_SHIFT, (regval & QSPI_CCR_SIOO) ? 1 : 0, (regval & QSPI_CCR_DDRM) ? 1 : 0); regval = getreg32(priv->base + STM32_QUADSPI_SR_OFFSET); /* Status Register */ spiinfo("SR:%08x\n", regval); spiinfo(" TEF:%1d TCF:%1d FTF:%1d SMF:%1d TOF:%1d BUSY:%1d FLEVEL:%d\n", (regval & QSPI_SR_TEF) ? 1 : 0, (regval & QSPI_SR_TCF) ? 1 : 0, (regval & QSPI_SR_FTF) ? 1 : 0, (regval & QSPI_SR_SMF) ? 1 : 0, (regval & QSPI_SR_TOF) ? 1 : 0, (regval & QSPI_SR_BUSY) ? 1 : 0, (regval & QSPI_SR_FLEVEL_MASK) >> QSPI_SR_FLEVEL_SHIFT); #else spiinfo(" CR:%08x DCR:%08x CCR:%08x SR:%08x\n", getreg32(priv->base + STM32_QUADSPI_CR_OFFSET), /* Control Register */ getreg32(priv->base + STM32_QUADSPI_DCR_OFFSET), /* Device Configuration Register */ getreg32(priv->base + STM32_QUADSPI_CCR_OFFSET), /* Communication Configuration Register */ getreg32(priv->base + STM32_QUADSPI_SR_OFFSET)); /* Status Register */ spiinfo(" DLR:%08x ABR:%08x PSMKR:%08x PSMAR:%08x\n", getreg32(priv->base + STM32_QUADSPI_DLR_OFFSET), /* Data Length Register */ getreg32(priv->base + STM32_QUADSPI_ABR_OFFSET), /* Alternate Bytes Register */ getreg32(priv->base + STM32_QUADSPI_PSMKR_OFFSET), /* Polling Status mask Register */ getreg32(priv->base + STM32_QUADSPI_PSMAR_OFFSET)); /* Polling Status match Register */ spiinfo(" PIR:%08x LPTR:%08x\n", getreg32(priv->base + STM32_QUADSPI_PIR_OFFSET), /* Polling Interval Register */ getreg32(priv->base + STM32_QUADSPI_LPTR_OFFSET)); /* Low-Power Timeout Register */ (void)regval; #endif } #endif #if defined(CONFIG_DEBUG_SPI_INFO) && defined(CONFIG_DEBUG_GPIO) static void qspi_dumpgpioconfig(const char *msg) { uint32_t regval; spiinfo("%s:\n", msg); /* Port B */ regval = getreg32(STM32_GPIOB_MODER); spiinfo("B_MODER:%08x\n", regval); regval = getreg32(STM32_GPIOB_OTYPER); spiinfo("B_OTYPER:%08x\n", regval); regval = getreg32(STM32_GPIOB_OSPEED); spiinfo("B_OSPEED:%08x\n", regval); regval = getreg32(STM32_GPIOB_PUPDR); spiinfo("B_PUPDR:%08x\n", regval); regval = getreg32(STM32_GPIOB_AFRL); spiinfo("B_AFRL:%08x\n", regval); regval = getreg32(STM32_GPIOB_AFRH); spiinfo("B_AFRH:%08x\n", regval); /* Port D */ regval = getreg32(STM32_GPIOD_MODER); spiinfo("D_MODER:%08x\n", regval); regval = getreg32(STM32_GPIOD_OTYPER); spiinfo("D_OTYPER:%08x\n", regval); regval = getreg32(STM32_GPIOD_OSPEED); spiinfo("D_OSPEED:%08x\n", regval); regval = getreg32(STM32_GPIOD_PUPDR); spiinfo("D_PUPDR:%08x\n", regval); regval = getreg32(STM32_GPIOD_AFRL); spiinfo("D_AFRL:%08x\n", regval); regval = getreg32(STM32_GPIOD_AFRH); spiinfo("D_AFRH:%08x\n", regval); /* Port E */ regval = getreg32(STM32_GPIOE_MODER); spiinfo("E_MODER:%08x\n", regval); regval = getreg32(STM32_GPIOE_OTYPER); spiinfo("E_OTYPER:%08x\n", regval); regval = getreg32(STM32_GPIOE_OSPEED); spiinfo("E_OSPEED:%08x\n", regval); regval = getreg32(STM32_GPIOE_PUPDR); spiinfo("E_PUPDR:%08x\n", regval); regval = getreg32(STM32_GPIOE_AFRL); spiinfo("E_AFRL:%08x\n", regval); regval = getreg32(STM32_GPIOE_AFRH); spiinfo("E_AFRH:%08x\n", regval); } #endif #ifdef CONFIG_STM32H7_QSPI_DMADEBUG /**************************************************************************** * Name: qspi_dma_sampleinit * * Description: * Initialize sampling of DMA registers * * Input Parameters: * priv - QSPI driver instance * * Returned Value: * None * ****************************************************************************/ static void qspi_dma_sampleinit(struct stm32h7_qspidev_s *priv) { /* Put contents of register samples into a known state */ memset(priv->dmaregs, 0xff, DMA_NSAMPLES * sizeof(struct stm32h7_dmaregs_s)); /* Then get the initial samples */ stm32h7_dmasample(priv->dmach, &priv->dmaregs[DMA_INITIAL]); } /**************************************************************************** * Name: qspi_dma_sampledone * * Description: * Dump sampled DMA registers * * Input Parameters: * priv - QSPI driver instance * * Returned Value: * None * ****************************************************************************/ static void qspi_dma_sampledone(struct stm32h7_qspidev_s *priv) { /* Sample the final registers */ stm32h7_dmasample(priv->dmach, &priv->dmaregs[DMA_END_TRANSFER]); /* Then dump the sampled DMA registers */ /* Initial register values */ stm32h7_dmadump(priv->dmach, &priv->dmaregs[DMA_INITIAL], "Initial Registers"); /* Register values after DMA setup */ stm32h7_dmadump(priv->dmach, &priv->dmaregs[DMA_AFTER_SETUP], "After DMA Setup"); /* Register values after DMA start */ stm32h7_dmadump(priv->dmach, &priv->dmaregs[DMA_AFTER_START], "After DMA Start"); /* Register values at the time of the TX and RX DMA callbacks * -OR- DMA timeout. * * If the DMA timed out, then there will not be any RX DMA * callback samples. There is probably no TX DMA callback * samples either, but we don't know for sure. */ if (priv->result == -ETIMEDOUT) { stm32h7_dmadump(priv->dmach, &priv->dmaregs[DMA_TIMEOUT], "At DMA timeout"); } else { stm32h7_dmadump(priv->dmach, &priv->dmaregs[DMA_CALLBACK], "At DMA callback"); } stm32h7_dmadump(priv->dmach, &priv->dmaregs[DMA_END_TRANSFER], "At End-of-Transfer"); } #endif /**************************************************************************** * Name: qspi_setupxctnfromcmd * * Description: * Setup our transaction descriptor from a command info structure * * Input Parameters: * xctn - the transaction descriptor we setup * cmdinfo - the command info (originating from the MTD device) * * Returned Value: * OK, or -errno if invalid * ****************************************************************************/ static int qspi_setupxctnfromcmd(struct qspi_xctnspec_s *xctn, const struct qspi_cmdinfo_s *cmdinfo) { DEBUGASSERT(xctn != NULL && cmdinfo != NULL); #ifdef CONFIG_DEBUG_SPI_INFO spiinfo("Transfer:\n"); spiinfo(" flags: %02x\n", cmdinfo->flags); spiinfo(" cmd: %04x\n", cmdinfo->cmd); if (QSPICMD_ISADDRESS(cmdinfo->flags)) { spiinfo(" address/length: %08lx/%d\n", (unsigned long)cmdinfo->addr, cmdinfo->addrlen); } if (QSPICMD_ISDATA(cmdinfo->flags)) { spiinfo(" %s Data:\n", QSPICMD_ISWRITE(cmdinfo->flags) ? "Write" : "Read"); spiinfo(" buffer/length: %p/%d\n", cmdinfo->buffer, cmdinfo->buflen); } #endif DEBUGASSERT(cmdinfo->cmd < 256); /* Specify the instruction as per command info */ /* XXX III instruction mode, single dual quad option bits */ if (QSPICMD_ISIQUAD(cmdinfo->flags)) { xctn->instrmode = CCR_IMODE_QUAD; } else if (QSPICMD_ISIDUAL(cmdinfo->flags)) { xctn->instrmode = CCR_IMODE_DUAL; } else { xctn->instrmode = CCR_IMODE_SINGLE; } xctn->instr = cmdinfo->cmd; /* XXX III option bits for 'send instruction only once' */ xctn->issioo = 0; /* XXX III options for alt bytes, dummy cycles */ xctn->altbytesmode = CCR_ABMODE_NONE; xctn->altbytessize = CCR_ABSIZE_8; xctn->altbytes = 0; xctn->dummycycles = 0; /* Specify the address size as needed */ if (QSPICMD_ISADDRESS(cmdinfo->flags)) { /* XXX III address mode mode, single, dual, quad option bits */ xctn->addrmode = CCR_ADMODE_SINGLE; if (cmdinfo->addrlen == 1) { xctn->addrsize = CCR_ADSIZE_8; } else if (cmdinfo->addrlen == 2) { xctn->addrsize = CCR_ADSIZE_16; } else if (cmdinfo->addrlen == 3) { xctn->addrsize = CCR_ADSIZE_24; } else if (cmdinfo->addrlen == 4) { xctn->addrsize = CCR_ADSIZE_32; } else { return -EINVAL; } xctn->addr = cmdinfo->addr; } else { xctn->addrmode = CCR_ADMODE_NONE; xctn->addrsize = 0; xctn->addr = cmdinfo->addr; } /* Specify the data as needed */ xctn->buffer = cmdinfo->buffer; if (QSPICMD_ISDATA(cmdinfo->flags)) { /* XXX III data mode mode, single, dual, quad option bits */ xctn->datamode = CCR_DMODE_SINGLE; xctn->datasize = cmdinfo->buflen; /* XXX III double data rate option bits */ xctn->isddr = 0; } else { xctn->datamode = CCR_DMODE_NONE; xctn->datasize = 0; xctn->isddr = 0; } #if defined(CONFIG_STM32H7_QSPI_INTERRUPTS) xctn->function = QSPICMD_ISWRITE(cmdinfo->flags) ? CCR_FMODE_INDWR : CCR_FMODE_INDRD; xctn->disposition = - EIO; xctn->idxnow = 0; #endif return OK; } /**************************************************************************** * Name: qspi_setupxctnfrommem * * Description: * Setup our transaction descriptor from a memory info structure * * Input Parameters: * xctn - the transaction descriptor we setup * meminfo - the memory info (originating from the MTD device) * * Returned Value: * OK, or -errno if invalid * ****************************************************************************/ static int qspi_setupxctnfrommem(struct qspi_xctnspec_s *xctn, const struct qspi_meminfo_s *meminfo) { DEBUGASSERT(xctn != NULL && meminfo != NULL); #ifdef CONFIG_DEBUG_SPI_INFO spiinfo("Transfer:\n"); spiinfo(" flags: %02x\n", meminfo->flags); spiinfo(" cmd: %04x\n", meminfo->cmd); spiinfo(" address/length: %08lx/%d\n", (unsigned long)meminfo->addr, meminfo->addrlen); spiinfo(" %s Data:\n", QSPIMEM_ISWRITE(meminfo->flags) ? "Write" : "Read"); spiinfo(" buffer/length: %p/%d\n", meminfo->buffer, meminfo->buflen); #endif DEBUGASSERT(meminfo->cmd < 256); /* Specify the instruction as per command info */ /* XXX III instruction mode, single dual quad option bits */ if (QSPIMEM_ISIQUAD(meminfo->flags)) { xctn->instrmode = CCR_IMODE_QUAD; } else if (QSPIMEM_ISIDUAL(meminfo->flags)) { xctn->instrmode = CCR_IMODE_DUAL; } else { xctn->instrmode = CCR_IMODE_SINGLE; } xctn->instr = meminfo->cmd; /* XXX III option bits for 'send instruction only once' */ xctn->issioo = 0; /* XXX III options for alt bytes */ xctn->altbytesmode = CCR_ABMODE_NONE; xctn->altbytessize = CCR_ABSIZE_8; xctn->altbytes = 0; xctn->dummycycles = meminfo->dummies; /* Specify the address size as needed */ /* XXX III there should be a separate flags for single/dual/quad for each * of i,a,d */ if (QSPIMEM_ISDUALIO(meminfo->flags)) { xctn->addrmode = CCR_ADMODE_DUAL; } else if (QSPIMEM_ISQUADIO(meminfo->flags)) { xctn->addrmode = CCR_ADMODE_QUAD; } else { xctn->addrmode = CCR_ADMODE_SINGLE; } if (meminfo->addrlen == 1) { xctn->addrsize = CCR_ADSIZE_8; } else if (meminfo->addrlen == 2) { xctn->addrsize = CCR_ADSIZE_16; } else if (meminfo->addrlen == 3) { xctn->addrsize = CCR_ADSIZE_24; } else if (meminfo->addrlen == 4) { xctn->addrsize = CCR_ADSIZE_32; } else { return -EINVAL; } xctn->addr = meminfo->addr; /* Specify the data as needed */ xctn->buffer = meminfo->buffer; /* XXX III there should be a separate flags for single/dual/quad for each * of i,a,d */ if (QSPIMEM_ISDUALIO(meminfo->flags)) { xctn->datamode = CCR_DMODE_DUAL; } else if (QSPIMEM_ISQUADIO(meminfo->flags)) { xctn->datamode = CCR_DMODE_QUAD; } else { xctn->datamode = CCR_DMODE_SINGLE; } xctn->datasize = meminfo->buflen; /* XXX III double data rate option bits */ xctn->isddr = 0; #if defined(CONFIG_STM32H7_QSPI_INTERRUPTS) xctn->function = QSPIMEM_ISWRITE(meminfo->flags) ? CCR_FMODE_INDWR : CCR_FMODE_INDRD; xctn->disposition = - EIO; xctn->idxnow = 0; #endif return OK; } /**************************************************************************** * Name: qspi_waitstatusflags * * Description: * Spin wait for specified status flags to be set as desired * * Input Parameters: * priv - The QSPI controller to dump * mask - bits to check, can be multiple * polarity - true wait if any set, false to wait if all reset * * Returned Value: * None * ****************************************************************************/ static void qspi_waitstatusflags(struct stm32h7_qspidev_s *priv, uint32_t mask, int polarity) { uint32_t regval; if (polarity) { while (!((regval = qspi_getreg(priv, STM32_QUADSPI_SR_OFFSET)) & mask)) ; } else { while (((regval = qspi_getreg(priv, STM32_QUADSPI_SR_OFFSET)) & mask)) ; } } /**************************************************************************** * Name: qspi_abort * * Description: * Abort any transaction in progress * * Input Parameters: * priv - The QSPI controller to dump * * Returned Value: * None * ****************************************************************************/ static void qspi_abort(struct stm32h7_qspidev_s *priv) { uint32_t regval; regval = qspi_getreg(priv, STM32_QUADSPI_CR_OFFSET); regval |= QSPI_CR_ABORT; qspi_putreg(priv, regval, STM32_QUADSPI_CR_OFFSET); } /**************************************************************************** * Name: qspi_ccrconfig * * Description: * Do common Communications Configuration Register setup * * Input Parameters: * priv - The QSPI controller to dump * xctn - the transaction descriptor; CCR setup * fctn - 'functional mode'; 0=indwrite, 1=indread, 2=autopoll, 3=memmmap * * Returned Value: * None * ****************************************************************************/ static void qspi_ccrconfig(struct stm32h7_qspidev_s *priv, struct qspi_xctnspec_s *xctn, uint8_t fctn) { uint32_t regval; /* If we have data, and it's not memory mapped, write the length */ if (CCR_DMODE_NONE != xctn->datamode && CCR_FMODE_MEMMAP != fctn) { qspi_putreg(priv, xctn->datasize - 1, STM32_QUADSPI_DLR_OFFSET); } /* If we have alternate bytes, stick them in now */ if (CCR_ABMODE_NONE != xctn->altbytesmode) { qspi_putreg(priv, xctn->altbytes, STM32_QUADSPI_ABR_OFFSET); } /* Build the CCR value and set it */ regval = QSPI_CCR_INST(xctn->instr) | QSPI_CCR_IMODE(xctn->instrmode) | QSPI_CCR_ADMODE(xctn->addrmode) | QSPI_CCR_ADSIZE(xctn->addrsize) | QSPI_CCR_ABMODE(xctn->altbytesmode) | QSPI_CCR_ABSIZE(xctn->altbytessize) | QSPI_CCR_DCYC(xctn->dummycycles) | QSPI_CCR_DMODE(xctn->datamode) | QSPI_CCR_FMODE(fctn) | (xctn->isddr ? QSPI_CCR_SIOO : 0) | (xctn->issioo ? QSPI_CCR_DDRM : 0); qspi_putreg(priv, regval, STM32_QUADSPI_CCR_OFFSET); /* If we have and need and address, set that now, too */ if (CCR_ADMODE_NONE != xctn->addrmode && CCR_FMODE_MEMMAP != fctn) { qspi_putreg(priv, xctn->addr, STM32_QUADSPI_AR_OFFSET); } } #if defined(CONFIG_STM32H7_QSPI_INTERRUPTS) /**************************************************************************** * Name: qspi0_interrupt * * Description: * Interrupt handler; we handle all QSPI cases -- reads, writes, * automatic status polling, etc. * * Input Parameters: * irq - * context - * * Returned Value: * OK means we handled it * ****************************************************************************/ static int qspi0_interrupt(int irq, void *context, FAR void *arg) { uint32_t status; uint32_t cr; uint32_t regval; /* Let's find out what is going on */ status = qspi_getreg(&g_qspi0dev, STM32_QUADSPI_SR_OFFSET); cr = qspi_getreg(&g_qspi0dev, STM32_QUADSPI_CR_OFFSET); /* Is it 'FIFO Threshold'? */ if ((status & QSPI_SR_FTF) && (cr & QSPI_CR_FTIE)) { volatile uint32_t *datareg = (volatile uint32_t *)(g_qspi0dev.base + STM32_QUADSPI_DR_OFFSET); if (g_qspi0dev.xctn->function == CCR_FMODE_INDWR) { /* Write data until we have no more or have no place to put it */ while (((regval = qspi_getreg( &g_qspi0dev, STM32_QUADSPI_SR_OFFSET)) & QSPI_SR_FTF) != 0) { if (g_qspi0dev.xctn->idxnow < g_qspi0dev.xctn->datasize) { *(volatile uint8_t *)datareg = ((uint8_t *)g_qspi0dev.xctn->buffer) [g_qspi0dev.xctn->idxnow]; ++g_qspi0dev.xctn->idxnow; } else { /* Fresh out of data to write */ break; } } } else if (g_qspi0dev.xctn->function == CCR_FMODE_INDRD) { /* Read data until we have no more or have no place to put it */ while (((regval = qspi_getreg( &g_qspi0dev, STM32_QUADSPI_SR_OFFSET)) & QSPI_SR_FTF) != 0) { if (g_qspi0dev.xctn->idxnow < g_qspi0dev.xctn->datasize) { ((uint8_t *)g_qspi0dev.xctn->buffer) [g_qspi0dev.xctn->idxnow] = *(volatile uint8_t *)datareg; ++g_qspi0dev.xctn->idxnow; } else { /* no room at the inn */ break; } } } } /* Is it 'Transfer Complete'? */ if ((status & QSPI_SR_TCF) && (cr & QSPI_CR_TCIE)) { /* Acknowledge interrupt */ qspi_putreg(&g_qspi0dev, QSPI_FCR_CTCF, STM32_QUADSPI_FCR_OFFSET); /* Disable the QSPI FIFO Threshold, Transfer Error and Transfer * complete Interrupts */ regval = qspi_getreg(&g_qspi0dev, STM32_QUADSPI_CR_OFFSET); regval &= ~(QSPI_CR_TEIE | QSPI_CR_TCIE | QSPI_CR_FTIE); qspi_putreg(&g_qspi0dev, regval, STM32_QUADSPI_CR_OFFSET); /* Do the last bit of read if needed */ if (g_qspi0dev.xctn->function == CCR_FMODE_INDRD) { volatile uint32_t *datareg = (volatile uint32_t *)(g_qspi0dev.base + STM32_QUADSPI_DR_OFFSET); /* Read any remaining data */ while (((regval = qspi_getreg( &g_qspi0dev, STM32_QUADSPI_SR_OFFSET)) & QSPI_SR_FLEVEL_MASK) != 0) { if (g_qspi0dev.xctn->idxnow < g_qspi0dev.xctn->datasize) { ((uint8_t *)g_qspi0dev.xctn->buffer) [g_qspi0dev.xctn->idxnow] = *(volatile uint8_t *)datareg; ++g_qspi0dev.xctn->idxnow; } else { /* No room at the inn */ break; } } } /* Use 'abort' to ditch any stray fifo contents and clear BUSY flag */ qspi_abort(&g_qspi0dev); /* Set success status */ g_qspi0dev.xctn->disposition = OK; /* Signal complete */ nxsem_post(&g_qspi0dev.op_sem); } /* Is it 'Status Match'? */ if ((status & QSPI_SR_SMF) && (cr & QSPI_CR_SMIE)) { /* Acknowledge interrupt */ qspi_putreg(&g_qspi0dev, QSPI_FCR_CSMF, STM32_QUADSPI_FCR_OFFSET); /* If 'automatic poll mode stop' is activated, we're done */ if (cr & QSPI_CR_APMS) { /* Disable the QSPI Transfer Error and Status Match Interrupts */ regval = qspi_getreg(&g_qspi0dev, STM32_QUADSPI_CR_OFFSET); regval &= ~(QSPI_CR_TEIE | QSPI_CR_SMIE); qspi_putreg(&g_qspi0dev, regval, STM32_QUADSPI_CR_OFFSET); /* Set success status */ g_qspi0dev.xctn->disposition = OK; /* Signal complete */ nxsem_post(&g_qspi0dev.op_sem); } else { /* XXX if it's NOT auto stop; something needs to happen here; * a callback? */ } } /* Is it' Transfer Error'? :( */ if ((status & QSPI_SR_TEF) && (cr & QSPI_CR_TEIE)) { /* Acknowledge interrupt */ qspi_putreg(&g_qspi0dev, QSPI_FCR_CTEF, STM32_QUADSPI_FCR_OFFSET); /* Disable all the QSPI Interrupts */ regval = qspi_getreg(&g_qspi0dev, STM32_QUADSPI_CR_OFFSET); regval &= ~(QSPI_CR_TEIE | QSPI_CR_TCIE | QSPI_CR_FTIE | QSPI_CR_SMIE | QSPI_CR_TOIE); qspi_putreg(&g_qspi0dev, regval, STM32_QUADSPI_CR_OFFSET); /* Set error status; 'transfer error' means that, in 'indirect mode', * an invalid address is attempted to be accessed. 'Invalid' is * presumably relative to the FSIZE field in CCR; the manual is not * explicit, but what else could it be? */ g_qspi0dev.xctn->disposition = - EIO; /* Signal complete */ nxsem_post(&g_qspi0dev.op_sem); } /* Is it 'Timeout'? */ if ((status & QSPI_SR_TOF) && (cr & QSPI_CR_TOIE)) { /* Acknowledge interrupt */ qspi_putreg(&g_qspi0dev, QSPI_FCR_CTOF, STM32_QUADSPI_FCR_OFFSET); /* XXX this interrupt simply means that, in 'memory mapped mode', * the QSPI memory has not been accessed for a while, and the * IP block was configured to automatically de-assert CS after * a timeout. And now we're being informed that has happened. * * But who cares? If someone does, perhaps a user callback is * appropriate, or some signal? Either way, realize the xctn * member is /not/ valid, so you can't set the disposition * field. Also, note signaling completion has no meaning here * because in memory mapped mode no one holds the semaphore. */ } return OK; } #elif defined(CONFIG_STM32H7_QSPI_DMA) /**************************************************************************** * Name: qspi_dma_timeout * * Description: * The watchdog timeout setup when a has expired without completion of a * DMA. * * Input Parameters: * arg - The argument * * Returned Value: * None * * Assumptions: * Always called from the interrupt level with interrupts disabled. * ****************************************************************************/ static void qspi_dma_timeout(wdparm_t arg) { struct stm32h7_qspidev_s *priv = (struct stm32h7_qspidev_s *)arg; DEBUGASSERT(priv != NULL); /* Sample DMA registers at the time of the timeout */ qspi_dma_sample(priv, DMA_CALLBACK); /* Report timeout result, perhaps overwriting any failure reports from * the TX callback. */ priv->result = -ETIMEDOUT; /* Then wake up the waiting thread */ nxsem_post(&priv->dmawait); } /**************************************************************************** * Name: qspi_dma_callback * * Description: * This callback function is invoked at the completion of the QSPI DMA. * * Input Parameters: * handle - The DMA handler * isr - source of the DMA interrupt * arg - A pointer to the chip select structure * * Returned Value: * None * ****************************************************************************/ static void qspi_dma_callback(DMA_HANDLE handle, uint8_t isr, void *arg) { struct stm32h7_qspidev_s *priv = (struct stm32h7_qspidev_s *)arg; DEBUGASSERT(priv != NULL); /* Cancel the watchdog timeout */ wd_cancel(&priv->dmadog); /* Sample DMA registers at the time of the callback */ qspi_dma_sample(priv, DMA_CALLBACK); /* Report the result of the transfer only if the callback has not already * reported an error. */ if (priv->result == -EBUSY) { /* Save the result of the transfer if no error was previously * reported */ if (isr & DMA_STREAM_TCIF_BIT) { priv->result = OK; } else if (isr & DMA_STREAM_TEIF_BIT) { priv->result = -EIO; } else { priv->result = OK; } } /* Then wake up the waiting thread */ nxsem_post(&priv->dmawait); } /**************************************************************************** * Name: qspi_regaddr * * Description: * Return the address of an QSPI register * ****************************************************************************/ static inline uintptr_t qspi_regaddr(struct stm32h7_qspidev_s *priv, unsigned int offset) { return priv->base + offset; } /**************************************************************************** * Name: qspi_memory_dma * * Description: * Perform one QSPI memory transfer using DMA * * Input Parameters: * priv - Device-specific state data * meminfo - Describes the memory transfer to be performed. * xctn - Describes the transaction context. * * Returned Value: * Zero (OK) on SUCCESS, a negated errno on value of failure * ****************************************************************************/ static int qspi_memory_dma(struct stm32h7_qspidev_s *priv, struct qspi_meminfo_s *meminfo, struct qspi_xctnspec_s *xctn) { uint32_t dmaflags; uint32_t regval; int ret; /* Initialize register sampling */ qspi_dma_sampleinit(priv); /* Determine DMA flags and setup the DMA */ if (QSPIMEM_ISWRITE(meminfo->flags)) { /* Setup the DMA (memory-to-peripheral) */ dmaflags = (QSPI_DMA_PRIO | DMA_SCR_MSIZE_8BITS | DMA_SCR_PSIZE_8BITS | DMA_SCR_MINC | DMA_SCR_DIR_M2P); up_clean_dcache((uintptr_t)meminfo->buffer, (uintptr_t)meminfo->buffer + meminfo->buflen); } else { /* Setup the DMA (peripheral-to-memory) */ dmaflags = (QSPI_DMA_PRIO | DMA_SCR_MSIZE_8BITS | DMA_SCR_PSIZE_8BITS | DMA_SCR_MINC | DMA_SCR_DIR_P2M); } stm32_dmasetup(priv->dmach, qspi_regaddr(priv, STM32_QUADSPI_DR_OFFSET), (uint32_t)meminfo->buffer, meminfo->buflen, dmaflags); qspi_dma_sample(priv, DMA_AFTER_SETUP); /* Enable the memory transfer */ regval = qspi_getreg(priv, STM32_QUADSPI_CR_OFFSET); regval |= QSPI_CR_DMAEN; qspi_putreg(priv, regval, STM32_QUADSPI_CR_OFFSET); /* Set up the Communications Configuration Register as per command info */ qspi_ccrconfig(priv, xctn, QSPIMEM_ISWRITE(meminfo->flags) ? CCR_FMODE_INDWR : CCR_FMODE_INDRD); /* Start the DMA */ priv->result = -EBUSY; stm32_dmastart(priv->dmach, qspi_dma_callback, priv, false); qspi_dma_sample(priv, DMA_AFTER_START); /* Wait for DMA completion. This is done in a loop because there may be * false alarm semaphore counts that cause nxsem_wait() not fail to wait * or to wake-up prematurely (for example due to the receipt of a signal). * We know that the DMA has completed when the result is anything other * that -EBUSY. */ do { /* Start (or re-start) the watchdog timeout */ ret = wd_start(&priv->dmadog, DMA_TIMEOUT_TICKS, qspi_dma_timeout, (wdparm_t)priv); if (ret < 0) { spierr("ERROR: wd_start failed: %d\n", ret); } /* Wait for the DMA complete */ ret = nxsem_wait(&priv->dmawait); if (QSPIMEM_ISREAD(meminfo->flags)) { up_invalidate_dcache((uintptr_t)meminfo->buffer, (uintptr_t)meminfo->buffer + meminfo->buflen); } /* Cancel the watchdog timeout */ wd_cancel(&priv->dmadog); /* Check if we were awakened by an error of some kind */ if (ret < 0) { /* EINTR is not a failure. That simply means that the wait * was awakened by a signal. */ if (ret != -EINTR) { DEBUGPANIC(); regval = qspi_getreg(priv, STM32_QUADSPI_CR_OFFSET); regval &= ~QSPI_CR_DMAEN; qspi_putreg(priv, regval, STM32_QUADSPI_CR_OFFSET); return ret; } } /* Note that we might be awakened before the wait is over due to * residual counts on the semaphore. So, to handle, that case, * we loop until something changes the DMA result to any value other * than -EBUSY. */ } while (priv->result == -EBUSY); /* Wait for Transfer complete, and not busy */ qspi_waitstatusflags(priv, QSPI_SR_TCF, 1); qspi_waitstatusflags(priv, QSPI_SR_BUSY, 0); MEMORY_SYNC(); /* Dump the sampled DMA registers */ qspi_dma_sampledone(priv); /* Make sure that the DMA is stopped (it will be stopped automatically * on normal transfers, but not necessarily when the transfer terminates * on an error condition). */ stm32_dmastop(priv->dmach); regval = qspi_getreg(priv, STM32_QUADSPI_CR_OFFSET); regval &= ~QSPI_CR_DMAEN; qspi_putreg(priv, regval, STM32_QUADSPI_CR_OFFSET); /* Complain if the DMA fails */ if (priv->result) { spierr("ERROR: DMA failed with result: %d\n", priv->result); } return priv->result; } #endif #if !defined(CONFIG_STM32H7_QSPI_INTERRUPTS) /**************************************************************************** * Name: qspi_receive_blocking * * Description: * Do common data receive in a blocking (status polling) way * * Input Parameters: * priv - The QSPI controller to dump * xctn - the transaction descriptor * * Returned Value: * OK, or -errno on error * ****************************************************************************/ static int qspi_receive_blocking(struct stm32h7_qspidev_s *priv, struct qspi_xctnspec_s *xctn) { int ret = OK; volatile uint32_t *datareg = (volatile uint32_t *)(priv->base + STM32_QUADSPI_DR_OFFSET); uint8_t *dest = (uint8_t *)xctn->buffer; uint32_t addrval; uint32_t regval; addrval = qspi_getreg(priv, STM32_QUADSPI_AR_OFFSET); if (dest != NULL) { /* Counter of remaining data */ uint32_t remaining = xctn->datasize; /* Ensure CCR register specifies indirect read */ regval = qspi_getreg(priv, STM32_QUADSPI_CCR_OFFSET); regval &= ~QSPI_CCR_FMODE_MASK; regval |= QSPI_CCR_FMODE(CCR_FMODE_INDRD); qspi_putreg(priv, regval, STM32_QUADSPI_CCR_OFFSET); /* Start the transfer by re-writing the address in AR register */ qspi_putreg(priv, addrval, STM32_QUADSPI_AR_OFFSET); /* Transfer loop */ while (remaining > 0) { /* Wait for Fifo Threshold, or Transfer Complete, to read data */ qspi_waitstatusflags(priv, QSPI_SR_FTF | QSPI_SR_TCF, 1); *dest = *(volatile uint8_t *)datareg; dest++; remaining--; } if (ret == OK) { /* Wait for transfer complete, then clear it */ qspi_waitstatusflags(priv, QSPI_SR_TCF, 1); qspi_putreg(priv, QSPI_FCR_CTCF, STM32_QUADSPI_FCR_OFFSET); /* Use Abort to clear the busy flag, and ditch any extra bytes in * fifo */ qspi_abort(priv); } } else { ret = -EINVAL; } return ret; } /**************************************************************************** * Name: qspi_transmit_blocking * * Description: * Do common data transmit in a blocking (status polling) way * * Input Parameters: * priv - The QSPI controller to dump * xctn - the transaction descriptor * * Returned Value: * OK, or -errno on error * ****************************************************************************/ static int qspi_transmit_blocking(struct stm32h7_qspidev_s *priv, struct qspi_xctnspec_s *xctn) { int ret = OK; volatile uint32_t *datareg = (volatile uint32_t *)(priv->base + STM32_QUADSPI_DR_OFFSET); uint8_t *src = (uint8_t *)xctn->buffer; if (src != NULL) { /* Counter of remaining data */ uint32_t remaining = xctn->datasize; /* Transfer loop */ while (remaining > 0) { /* Wait for Fifo Threshold to write data */ qspi_waitstatusflags(priv, QSPI_SR_FTF, 1); *(volatile uint8_t *)datareg = *src++; remaining--; } if (ret == OK) { /* Wait for transfer complete, then clear it */ qspi_waitstatusflags(priv, QSPI_SR_TCF, 1); qspi_putreg(priv, QSPI_FCR_CTCF, STM32_QUADSPI_FCR_OFFSET); /* Use Abort to clear the Busy flag */ qspi_abort(priv); } } else { ret = -EINVAL; } return ret; } #endif /**************************************************************************** * Name: qspi_lock * * Description: * On QSPI buses where there are multiple devices, it will be necessary to * lock QSPI to have exclusive access to the buses for a sequence of * transfers. The bus should be locked before the chip is selected. After * locking the QSPI bus, the caller should then also call the setfrequency, * setbits, and setmode methods to make sure that the QSPI is properly * configured for the device. If the QSPI bus is being shared, then it * may have been left in an incompatible state. * * Input Parameters: * dev - Device-specific state data * lock - true: Lock QSPI bus, false: unlock QSPI bus * * Returned Value: * None * ****************************************************************************/ static int qspi_lock(struct qspi_dev_s *dev, bool lock) { struct stm32h7_qspidev_s *priv = (struct stm32h7_qspidev_s *)dev; int ret; spiinfo("lock=%d\n", lock); if (lock) { /* Take the semaphore (perhaps waiting) */ do { ret = nxsem_wait(&priv->exclsem); /* The only case that an error should occur here is if the wait * was awakened by a signal. */ DEBUGASSERT(ret == OK || ret == -EINTR); } while (ret == -EINTR); } else { ret = nxsem_post(&priv->exclsem); } return ret; } /**************************************************************************** * Name: qspi_setfrequency * * Description: * Set the QSPI frequency. * * Input Parameters: * dev - Device-specific state data * frequency - The QSPI frequency requested * * Returned Value: * Returns the actual frequency selected * ****************************************************************************/ static uint32_t qspi_setfrequency(struct qspi_dev_s *dev, uint32_t frequency) { struct stm32h7_qspidev_s *priv = (struct stm32h7_qspidev_s *)dev; uint32_t actual; uint32_t prescaler; uint32_t regval; if (priv->memmap) { /* XXX we have no better return here, but the caller will find out * in their subsequent calls. */ return 0; } spiinfo("frequency=%d\n", frequency); DEBUGASSERT(priv); /* Wait till BUSY flag reset */ qspi_abort(priv); qspi_waitstatusflags(priv, QSPI_SR_BUSY, 0); /* Check if the requested frequency is the same as the frequency * selection */ if (priv->frequency == frequency) { /* We are already at this frequency. Return the actual. */ return priv->actual; } /* Configure QSPI to a frequency as close as possible to the requested * frequency. * * QSCK frequency = QSPI_CLK_FREQUENCY / prescaler, or * prescaler = QSPI_CLK_FREQUENCY / frequency * * Where prescaler can have the range 1 to 256 and the * STM32_QUADSPI_CR_OFFSET register field holds prescaler - 1. * NOTE that a "ceiling" type of calculation is performed. * 'frequency' is treated as a not-to-exceed value. */ prescaler = (frequency + QSPI_CLK_FREQUENCY - 1) / frequency; /* Make sure that the divider is within range */ if (prescaler < 1) { prescaler = 1; } else if (prescaler > 256) { prescaler = 256; } /* Save the new prescaler value (minus one) */ regval = qspi_getreg(priv, STM32_QUADSPI_CR_OFFSET); regval &= ~(QSPI_CR_PRESCALER_MASK); regval |= (prescaler - 1) << QSPI_CR_PRESCALER_SHIFT; qspi_putreg(priv, regval, STM32_QUADSPI_CR_OFFSET); /* Calculate the new actual frequency */ actual = QSPI_CLK_FREQUENCY / prescaler; spiinfo("prescaler=%d actual=%d\n", prescaler, actual); /* Save the frequency setting */ priv->frequency = frequency; priv->actual = actual; spiinfo("Frequency %d->%d\n", frequency, actual); return actual; } /**************************************************************************** * Name: qspi_setmode * * Description: * Set the QSPI mode. Optional. See enum qspi_mode_e for mode definitions. * NOTE: the STM32H7 QSPI supports only modes 0 and 3. * * Input Parameters: * dev - Device-specific state data * mode - The QSPI mode requested * * Returned Value: * none * ****************************************************************************/ static void qspi_setmode(struct qspi_dev_s *dev, enum qspi_mode_e mode) { struct stm32h7_qspidev_s *priv = (struct stm32h7_qspidev_s *)dev; uint32_t regval; if (priv->memmap) { /* XXX we have no better return here, but the caller will find out * in their subsequent calls. */ return; } spiinfo("mode=%d\n", mode); /* Has the mode changed? */ if (mode != priv->mode) { /* Yes... Set the mode appropriately: * * QSPI CPOL CPHA * MODE * 0 0 0 * 1 0 1 * 2 1 0 * 3 1 1 */ regval = qspi_getreg(priv, STM32_QUADSPI_DCR_OFFSET); regval &= ~(QSPI_DCR_CKMODE); switch (mode) { case QSPIDEV_MODE0: /* CPOL=0; CPHA=0 */ break; case QSPIDEV_MODE3: /* CPOL=1; CPHA=1 */ regval |= (QSPI_DCR_CKMODE); break; case QSPIDEV_MODE1: /* CPOL=0; CPHA=1 */ case QSPIDEV_MODE2: /* CPOL=1; CPHA=0 */ spiinfo("unsupported mode=%d\n", mode); default: DEBUGASSERT(FALSE); return; } qspi_putreg(priv, regval, STM32_QUADSPI_DCR_OFFSET); spiinfo("DCR=%08x\n", regval); /* Save the mode so that subsequent re-configurations will be faster */ priv->mode = mode; } } /**************************************************************************** * Name: qspi_setbits * * Description: * Set the number if bits per word. * NOTE: the STM32H7 QSPI only supports 8 bits, so this does nothing. * * Input Parameters: * dev - Device-specific state data * nbits - The number of bits requests * * Returned Value: * none * ****************************************************************************/ static void qspi_setbits(struct qspi_dev_s *dev, int nbits) { /* Not meaningful for the STM32H7x6 */ if (8 != nbits) { spiinfo("unsupported nbits=%d\n", nbits); DEBUGASSERT(FALSE); } } /**************************************************************************** * Name: qspi_command * * Description: * Perform one QSPI data transfer * * Input Parameters: * dev - Device-specific state data * cmdinfo - Describes the command transfer to be performed. * * Returned Value: * Zero (OK) on SUCCESS, a negated errno on value of failure * ****************************************************************************/ static int qspi_command(struct qspi_dev_s *dev, struct qspi_cmdinfo_s *cmdinfo) { struct stm32h7_qspidev_s *priv = (struct stm32h7_qspidev_s *)dev; struct qspi_xctnspec_s xctn; int ret; /* Reject commands issued while in memory mapped mode, which will * automatically cancel the memory mapping. You must exit the * memory mapped mode first. */ if (priv->memmap) { return -EBUSY; } /* Set up the transaction descriptor as per command info */ ret = qspi_setupxctnfromcmd(&xctn, cmdinfo); if (OK != ret) { return ret; } /* Prepare for transaction */ /* Wait 'till non-busy */ qspi_abort(priv); qspi_waitstatusflags(priv, QSPI_SR_BUSY, 0); /* Clear flags */ qspi_putreg(priv, QSPI_FCR_CTEF | QSPI_FCR_CTCF | QSPI_FCR_CSMF | QSPI_FCR_CTOF, STM32_QUADSPI_FCR_OFFSET); #ifdef CONFIG_STM32H7_QSPI_INTERRUPTS /* interrupt mode will need access to the transaction context */ priv->xctn = &xctn; if (QSPICMD_ISDATA(cmdinfo->flags)) { DEBUGASSERT(cmdinfo->buffer != NULL && cmdinfo->buflen > 0); DEBUGASSERT(IS_ALIGNED(cmdinfo->buffer)); if (QSPICMD_ISWRITE(cmdinfo->flags)) { uint32_t regval; /* Set up the Communications Configuration Register as per command * info */ qspi_ccrconfig(priv, &xctn, CCR_FMODE_INDWR); /* Enable 'Transfer Error' 'FIFO Threshhold' and * 'Transfer Complete' interrupts. */ regval = qspi_getreg(priv, STM32_QUADSPI_CR_OFFSET); regval |= (QSPI_CR_TEIE | QSPI_CR_FTIE | QSPI_CR_TCIE); qspi_putreg(priv, regval, STM32_QUADSPI_CR_OFFSET); } else { uint32_t regval; uint32_t addrval; addrval = qspi_getreg(priv, STM32_QUADSPI_AR_OFFSET); /* Set up the Communications Configuration Register as per command * info */ qspi_ccrconfig(priv, &xctn, CCR_FMODE_INDRD); /* Start the transfer by re-writing the address in AR register */ qspi_putreg(priv, addrval, STM32_QUADSPI_AR_OFFSET); /* Enable 'Transfer Error' 'FIFO Threshhold' and * 'Transfer Complete' interrupts */ regval = qspi_getreg(priv, STM32_QUADSPI_CR_OFFSET); regval |= (QSPI_CR_TEIE | QSPI_CR_FTIE | QSPI_CR_TCIE); qspi_putreg(priv, regval, STM32_QUADSPI_CR_OFFSET); } } else { uint32_t regval; /* We have no data phase, the command will execute as soon as we emit * the CCR */ /* Enable 'Transfer Error' and 'Transfer Complete' interrupts */ regval = qspi_getreg(priv, STM32_QUADSPI_CR_OFFSET); regval |= (QSPI_CR_TEIE | QSPI_CR_TCIE); qspi_putreg(priv, regval, STM32_QUADSPI_CR_OFFSET); /* Set up the Communications Configuration Register as per command * info */ qspi_ccrconfig(priv, &xctn, CCR_FMODE_INDRD); } /* Wait for the interrupt routine to finish it's magic */ nxsem_wait(&priv->op_sem); MEMORY_SYNC(); /* Convey the result */ ret = xctn.disposition; /* because command transfers are so small, we're not going to use * DMA for them, only interrupts or polling */ #else /* Polling mode */ /* Set up the Communications Configuration Register as per command info */ qspi_ccrconfig(priv, &xctn, CCR_FMODE_INDWR); /* That may be it, unless there is also data to transfer */ if (QSPICMD_ISDATA(cmdinfo->flags)) { DEBUGASSERT(cmdinfo->buffer != NULL && cmdinfo->buflen > 0); DEBUGASSERT(IS_ALIGNED(cmdinfo->buffer)); if (QSPICMD_ISWRITE(cmdinfo->flags)) { ret = qspi_transmit_blocking(priv, &xctn); } else { ret = qspi_receive_blocking(priv, &xctn); } MEMORY_SYNC(); } else { ret = OK; } /* Wait for Transfer complete, and not busy */ qspi_waitstatusflags(priv, QSPI_SR_TCF, 1); qspi_waitstatusflags(priv, QSPI_SR_BUSY, 0); #endif return ret; } /**************************************************************************** * Name: qspi_memory * * Description: * Perform one QSPI memory transfer * * Input Parameters: * dev - Device-specific state data * meminfo - Describes the memory transfer to be performed. * * Returned Value: * Zero (OK) on SUCCESS, a negated errno on value of failure * ****************************************************************************/ static int qspi_memory(struct qspi_dev_s *dev, struct qspi_meminfo_s *meminfo) { struct stm32h7_qspidev_s *priv = (struct stm32h7_qspidev_s *)dev; struct qspi_xctnspec_s xctn; int ret; /* Reject commands issued while in memory mapped mode, which will * automatically cancel the memory mapping. You must exit the * memory mapped mode first. */ if (priv->memmap) { return -EBUSY; } /* Set up the transaction descriptor as per command info */ ret = qspi_setupxctnfrommem(&xctn, meminfo); if (OK != ret) { return ret; } /* Prepare for transaction */ /* Wait 'till non-busy */ qspi_abort(priv); qspi_waitstatusflags(priv, QSPI_SR_BUSY, 0); /* Clear flags */ qspi_putreg(priv, QSPI_FCR_CTEF | QSPI_FCR_CTCF | QSPI_FCR_CSMF | QSPI_FCR_CTOF, STM32_QUADSPI_FCR_OFFSET); #ifdef CONFIG_STM32H7_QSPI_INTERRUPTS /* interrupt mode will need access to the transaction context */ priv->xctn = &xctn; DEBUGASSERT(meminfo->buffer != NULL && meminfo->buflen > 0); DEBUGASSERT(IS_ALIGNED(meminfo->buffer)); if (QSPIMEM_ISWRITE(meminfo->flags)) { uint32_t regval; /* Set up the Communications Configuration Register as per command * info */ qspi_ccrconfig(priv, &xctn, CCR_FMODE_INDWR); /* Enable 'Transfer Error' 'FIFO Threshhold' and 'Transfer Complete' * interrupts */ regval = qspi_getreg(priv, STM32_QUADSPI_CR_OFFSET); regval |= (QSPI_CR_TEIE | QSPI_CR_FTIE | QSPI_CR_TCIE); qspi_putreg(priv, regval, STM32_QUADSPI_CR_OFFSET); } else { uint32_t regval; uint32_t addrval; addrval = qspi_getreg(priv, STM32_QUADSPI_AR_OFFSET); /* Set up the Communications Configuration Register as per command * info */ qspi_ccrconfig(priv, &xctn, CCR_FMODE_INDRD); /* Start the transfer by re-writing the address in AR register */ qspi_putreg(priv, addrval, STM32_QUADSPI_AR_OFFSET); /* Enable 'Transfer Error' 'FIFO Threshhold' and 'Transfer Complete' * interrupts */ regval = qspi_getreg(priv, STM32_QUADSPI_CR_OFFSET); regval |= (QSPI_CR_TEIE | QSPI_CR_FTIE | QSPI_CR_TCIE); qspi_putreg(priv, regval, STM32_QUADSPI_CR_OFFSET); } /* Wait for the interrupt routine to finish it's magic */ nxsem_wait(&priv->op_sem); MEMORY_SYNC(); /* convey the result */ ret = xctn.disposition; #elif defined(CONFIG_STM32H7_QSPI_DMA) /* Can we perform DMA? Should we perform DMA? */ if (priv->candma && meminfo->buflen > CONFIG_STM32H7_QSPI_DMATHRESHOLD && IS_ALIGNED((uintptr_t)meminfo->buffer) && IS_ALIGNED(meminfo->buflen)) { ret = qspi_memory_dma(priv, meminfo, &xctn); } else { /* polling mode */ /* Set up the Communications Configuration Register as per command * info */ qspi_ccrconfig(priv, &xctn, QSPIMEM_ISWRITE(meminfo->flags) ? CCR_FMODE_INDWR : CCR_FMODE_INDRD); /* Transfer data */ DEBUGASSERT(meminfo->buffer != NULL && meminfo->buflen > 0); DEBUGASSERT(IS_ALIGNED(meminfo->buffer)); if (QSPIMEM_ISWRITE(meminfo->flags)) { ret = qspi_transmit_blocking(priv, &xctn); } else { ret = qspi_receive_blocking(priv, &xctn); } /* Wait for Transfer complete, and not busy */ qspi_waitstatusflags(priv, QSPI_SR_TCF, 1); qspi_waitstatusflags(priv, QSPI_SR_BUSY, 0); MEMORY_SYNC(); } #else /* polling mode */ /* Set up the Communications Configuration Register as per command info */ qspi_ccrconfig(priv, &xctn, QSPIMEM_ISWRITE(meminfo->flags) ? CCR_FMODE_INDWR : CCR_FMODE_INDRD); /* Transfer data */ DEBUGASSERT(meminfo->buffer != NULL && meminfo->buflen > 0); DEBUGASSERT(IS_ALIGNED(meminfo->buffer)); if (QSPIMEM_ISWRITE(meminfo->flags)) { ret = qspi_transmit_blocking(priv, &xctn); } else { ret = qspi_receive_blocking(priv, &xctn); } /* Wait for Transfer complete, and not busy */ qspi_waitstatusflags(priv, QSPI_SR_TCF, 1); qspi_waitstatusflags(priv, QSPI_SR_BUSY, 0); MEMORY_SYNC(); #endif return ret; } /**************************************************************************** * Name: qspi_alloc * * Description: * Allocate a buffer suitable for DMA data transfer * * Input Parameters: * dev - Device-specific state data * buflen - Buffer length to allocate in bytes * * Returned Value: * Address of the allocated memory on success; NULL is returned on any * failure. * ****************************************************************************/ static FAR void *qspi_alloc(FAR struct qspi_dev_s *dev, size_t buflen) { /* Here we exploit the carnal knowledge the kmm_malloc() will return memory * aligned to 64-bit addresses. The buffer length must be large enough to * hold the rested buflen in units a 32-bits. */ return kmm_malloc(ALIGN_UP(buflen)); } /**************************************************************************** * Name: QSPI_FREE * * Description: * Free memory returned by QSPI_ALLOC * * Input Parameters: * dev - Device-specific state data * buffer - Buffer previously allocated via QSPI_ALLOC * * Returned Value: * None. * ****************************************************************************/ static void qspi_free(FAR struct qspi_dev_s *dev, FAR void *buffer) { if (buffer) { kmm_free(buffer); } } /**************************************************************************** * Name: qspi_hw_initialize * * Description: * Initialize the QSPI peripheral from hardware reset. * * Input Parameters: * priv - Device state structure. * * Returned Value: * Zero (OK) on SUCCESS, a negated errno on value of failure * ****************************************************************************/ static int qspi_hw_initialize(struct stm32h7_qspidev_s *priv) { uint32_t regval; /* Disable the QSPI; abort anything happening, disable, wait for not busy */ qspi_abort(priv); regval = 0; regval &= ~(QSPI_CR_EN); qspi_putreg(priv, regval, STM32_QUADSPI_CR_OFFSET); /* Wait till BUSY flag reset */ qspi_waitstatusflags(priv, QSPI_SR_BUSY, 0); /* Disable all interrupt sources for starters */ regval = qspi_getreg(priv, STM32_QUADSPI_CR_OFFSET); regval &= ~(QSPI_CR_TEIE | QSPI_CR_TCIE | QSPI_CR_FTIE | QSPI_CR_SMIE | QSPI_CR_TOIE | QSPI_CR_FSEL | QSPI_CR_DFM); #if defined(CONFIG_STM32H7_QSPI_MODE_BANK2) regval |= QSPI_CR_FSEL; #endif #if defined(CONFIG_STM32H7_QSPI_MODE_DUAL) regval |= QSPI_CR_DFM; #endif /* Configure QSPI FIFO Threshold */ regval &= ~(QSPI_CR_FTHRES_MASK); regval |= ((CONFIG_STM32H7_QSPI_FIFO_THESHOLD - 1) << QSPI_CR_FTHRES_SHIFT); qspi_putreg(priv, regval, STM32_QUADSPI_CR_OFFSET); /* Wait till BUSY flag reset */ qspi_waitstatusflags(priv, QSPI_SR_BUSY, 0); /* Configure QSPI Clock Prescaler and Sample Shift */ regval = qspi_getreg(priv, STM32_QUADSPI_CR_OFFSET); regval &= ~(QSPI_CR_PRESCALER_MASK | QSPI_CR_SSHIFT); regval |= (0x01 << QSPI_CR_PRESCALER_SHIFT); regval |= (0x00); qspi_putreg(priv, regval, STM32_QUADSPI_CR_OFFSET); /* Configure QSPI Flash Size, CS High Time and Clock Mode */ regval = qspi_getreg(priv, STM32_QUADSPI_DCR_OFFSET); regval &= ~(QSPI_DCR_CKMODE | QSPI_DCR_CSHT_MASK | QSPI_DCR_FSIZE_MASK); regval |= (0x00); regval |= ((CONFIG_STM32H7_QSPI_CSHT - 1) << QSPI_DCR_CSHT_SHIFT); if (0 != CONFIG_STM32H7_QSPI_FLASH_SIZE) { unsigned int nsize = CONFIG_STM32H7_QSPI_FLASH_SIZE; int nlog2size = 31; while ((nsize & 0x80000000) == 0) { --nlog2size; nsize <<= 1; } regval |= ((nlog2size - 1) << QSPI_DCR_FSIZE_SHIFT); } qspi_putreg(priv, regval, STM32_QUADSPI_DCR_OFFSET); /* Enable QSPI */ regval = qspi_getreg(priv, STM32_QUADSPI_CR_OFFSET); regval |= QSPI_CR_EN; qspi_putreg(priv, regval, STM32_QUADSPI_CR_OFFSET); /* Wait till BUSY flag reset */ qspi_waitstatusflags(priv, QSPI_SR_BUSY, 0); qspi_dumpregs(priv, "After initialization"); qspi_dumpgpioconfig("GPIO"); return OK; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: stm32h7_qspi_initialize * * Description: * Initialize the selected QSPI port in master mode * * Input Parameters: * intf - Interface number(must be zero) * * Returned Value: * Valid QSPI device structure reference on success; a NULL on failure * ****************************************************************************/ struct qspi_dev_s *stm32h7_qspi_initialize(int intf) { struct stm32h7_qspidev_s *priv; uint32_t regval; int ret; /* The STM32H7 has only a single QSPI port */ spiinfo("intf: %d\n", intf); DEBUGASSERT(intf == 0); /* Select the QSPI interface */ if (intf == 0) { /* If this function is called multiple times, the following operations * will be performed multiple times. */ /* Select QSPI0 */ priv = &g_qspi0dev; /* Select QSPI clock source */ modreg32 (BOARD_QSPI_CLK, RCC_D1CCIPR_QSPISEL_MASK, STM32_RCC_D1CCIPR); /* Enable clocking to the QSPI peripheral */ regval = getreg32(STM32_RCC_AHB3ENR); regval |= RCC_AHB3ENR_QSPIEN; putreg32(regval, STM32_RCC_AHB3ENR); /* Reset the QSPI peripheral */ regval = getreg32(STM32_RCC_AHB3RSTR); regval |= RCC_AHB3RSTR_QSPIRST; putreg32(regval, STM32_RCC_AHB3RSTR); regval &= ~RCC_AHB3RSTR_QSPIRST; putreg32(regval, STM32_RCC_AHB3RSTR); /* Configure multiplexed pins as connected on the board. */ stm32_configgpio(GPIO_QSPI_CS); stm32_configgpio(GPIO_QSPI_IO0); stm32_configgpio(GPIO_QSPI_IO1); stm32_configgpio(GPIO_QSPI_IO2); stm32_configgpio(GPIO_QSPI_IO3); stm32_configgpio(GPIO_QSPI_SCK); } else { spierr("ERROR: QSPI%d not supported\n", intf); return NULL; } /* Has the QSPI hardware been initialized? */ if (!priv->initialized) { /* Now perform one time initialization. * * Initialize the QSPI semaphore that enforces mutually exclusive * access to the QSPI registers. */ nxsem_init(&priv->exclsem, 0, 1); #ifdef CONFIG_STM32H7_QSPI_DMA /* Pre-allocate DMA channels. */ if (priv->candma) { priv->dmach = stm32_dmachannel(DMACHAN_QUADSPI); if (!priv->dmach) { spierr("ERROR: Failed to allocate the DMA channel\n"); priv->candma = false; } } /* Initialize the QSPI semaphore that is used to wake up the waiting * thread when the DMA transfer completes. This semaphore is used for * signaling and, hence, should not have priority inheritance enabled. */ nxsem_init(&priv->dmawait, 0, 0); nxsem_set_protocol(&priv->dmawait, SEM_PRIO_NONE); #endif #ifdef CONFIG_STM32H7_QSPI_INTERRUPTS /* Attach the interrupt handler */ ret = irq_attach(priv->irq, priv->handler, NULL); if (ret < 0) { spierr("ERROR: Failed to attach irq %d\n", priv->irq); goto errout_with_dmawait; } /* Initialize the semaphore that blocks until the operation completes. * This semaphore is used for signaling and, hence, should not have * priority inheritance enabled. */ nxsem_init(&priv->op_sem, 0, 0); nxsem_set_protocol(&priv->op_sem, SEM_PRIO_NONE); #endif /* Perform hardware initialization. Puts the QSPI into an active * state. */ ret = qspi_hw_initialize(priv); if (ret < 0) { spierr("ERROR: Failed to initialize QSPI hardware\n"); goto errout_with_irq; } /* Enable interrupts at the NVIC */ priv->initialized = true; priv->memmap = false; #ifdef CONFIG_STM32H7_QSPI_INTERRUPTS up_enable_irq(priv->irq); #endif } return &priv->qspi; errout_with_irq: #ifdef CONFIG_STM32H7_QSPI_INTERRUPTS irq_detach(priv->irq); errout_with_dmawait: #endif #ifdef CONFIG_STM32H7_QSPI_DMA nxsem_destroy(&priv->dmawait); if (priv->dmach) { stm32_dmafree(priv->dmach); priv->dmach = NULL; } #endif nxsem_destroy(&priv->exclsem); return NULL; } /**************************************************************************** * Name: stm32h7_qspi_enter_memorymapped * * Description: * Put the QSPI device into memory mapped mode * * Input Parameters: * dev - QSPI device * meminfo - parameters like for a memory transfer used for reading * * Returned Value: * None * ****************************************************************************/ void stm32h7_qspi_enter_memorymapped(struct qspi_dev_s *dev, const struct qspi_meminfo_s *meminfo, uint32_t lpto) { struct stm32h7_qspidev_s *priv = (struct stm32h7_qspidev_s *)dev; uint32_t regval; struct qspi_xctnspec_s xctn; /* lock during this mode change */ qspi_lock(dev, true); if (priv->memmap) { qspi_lock(dev, false); return; } /* Abort anything in-progress */ qspi_abort(priv); /* Wait till BUSY flag reset */ qspi_waitstatusflags(priv, QSPI_SR_BUSY, 0); /* if we want the 'low-power timeout counter' */ if (lpto > 0) { /* Set the Low Power Timeout value (automatically de-assert * CS if memory is not accessed for a while) */ qspi_putreg(priv, lpto, STM32_QUADSPI_LPTR_OFFSET); /* Clear Timeout interrupt */ qspi_putreg(&g_qspi0dev, QSPI_FCR_CTOF, STM32_QUADSPI_FCR_OFFSET); #ifdef CONFIG_STM32H7_QSPI_INTERRUPTS /* Enable Timeout interrupt */ regval = qspi_getreg(priv, STM32_QUADSPI_CR_OFFSET); regval |= (QSPI_CR_TCEN | QSPI_CR_TOIE); qspi_putreg(priv, regval, STM32_QUADSPI_CR_OFFSET); #endif } else { regval = qspi_getreg(priv, STM32_QUADSPI_CR_OFFSET); regval &= ~QSPI_CR_TCEN; qspi_putreg(priv, regval, STM32_QUADSPI_CR_OFFSET); } /* create a transaction object */ qspi_setupxctnfrommem(&xctn, meminfo); #ifdef CONFIG_STM32H7_QSPI_INTERRUPTS priv->xctn = NULL; #endif /* set it into the ccr */ qspi_ccrconfig(priv, &xctn, CCR_FMODE_MEMMAP); priv->memmap = true; /* we should be in memory mapped mode now */ qspi_dumpregs(priv, "After memory mapped:"); /* finished this mode change */ qspi_lock(dev, false); } /**************************************************************************** * Name: stm32h7_qspi_exit_memorymapped * * Description: * Take the QSPI device out of memory mapped mode * * Input Parameters: * dev - QSPI device * * Returned Value: * None * ****************************************************************************/ void stm32h7_qspi_exit_memorymapped(struct qspi_dev_s *dev) { struct stm32h7_qspidev_s *priv = (struct stm32h7_qspidev_s *)dev; qspi_lock(dev, true); /* A simple abort is sufficient */ qspi_abort(priv); priv->memmap = false; qspi_lock(dev, false); } #endif /* CONFIG_STM32H7_QSPI */