diff --git a/arch/arm/src/stm32h5/Kconfig b/arch/arm/src/stm32h5/Kconfig index 2aa85b7c8e591..ebba830b7b1c8 100644 --- a/arch/arm/src/stm32h5/Kconfig +++ b/arch/arm/src/stm32h5/Kconfig @@ -309,6 +309,10 @@ config STM32H5_ETHMAC select ARCH_HAVE_PHY select STM32H5_HAVE_PHY_POLLED +config STM32H5_QSPI1 + bool "QSPI1" + default n + config STM32H5_USART2 bool "USART2" default n @@ -1570,4 +1574,138 @@ config STM32H5_I2CTIMEOTICKS endmenu # "I2C Configuration" +menu "QuadSPI Configuration" + depends on STM32H5_QSPI1 + +config STM32H5_QSPI_FLASH_SIZE + int "Size of attached serial flash, bytes" + default 16777216 + range 1 2147483648 + ---help--- + The STM32H5 QSPI peripheral requires the size of the Flash be specified + +config STM32H5_QSPI_FIFO_THESHOLD + int "Number of bytes before asserting FIFO threshold flag" + default 4 + range 1 32 + ---help--- + The STM32H5 QSPI peripheral requires that the FIFO threshold be specified + I would leave it at the default value of 4 unless you know what you are doing. + +config STM32H5_QSPI_CSHT + int "Number of cycles Chip Select must be inactive between transactions" + default 5 + range 1 64 + ---help--- + The STM32H5 QSPI peripheral requires that it be specified the minimum number + of AHB cycles that Chip Select be held inactive between transactions. + +choice + prompt "Transfer technique" + default STM32H5_QSPI_DMA + ---help--- + You can choose between using polling, interrupts, or DMA to transfer data + over the QSPI interface. + +config STM32H5_QSPI_POLLING + bool "Polling" + ---help--- + Use conventional register I/O with status polling to transfer data. + +config STM32H5_QSPI_INTERRUPTS + bool "Interrupts" + ---help--- + User interrupt driven I/O transfers. + +config STM32H5_QSPI_DMA + bool "DMA" + depends on STM32H5_DMA + ---help--- + Use DMA to improve QSPI transfer performance. + +endchoice + +choice + prompt "Bank selection" + default STM32H5_QSPI_MODE_BANK1 + ---help--- + You can choose between using polling, interrupts, or DMA to transfer data + over the QSPI interface. + +config STM32H5_QSPI_MODE_BANK1 + bool "Bank 1" + +config STM32H5_QSPI_MODE_BANK2 + bool "Bank 2" + +config STM32H5_QSPI_MODE_DUAL + bool "Dual Bank" + +endchoice + +choice + prompt "DMA Priority" + default STM32H5_QSPI_DMAPRIORITY_MEDIUM + depends on STM32H5_DMA + ---help--- + The DMA controller supports priority levels. You are probably fine + with the default of 'medium' except for special cases. In the event + of contention between to channels at the same priority, the lower + numbered channel has hardware priority over the higher numbered one. + +config STM32H5_QSPI_DMAPRIORITY_VERYHIGH + bool "Very High priority" + depends on STM32H5_DMA + ---help--- + 'Highest' priority. + +config STM32H5_QSPI_DMAPRIORITY_HIGH + bool "High priority" + depends on STM32H5_DMA + ---help--- + 'High' priority. + +config STM32H5_QSPI_DMAPRIORITY_MEDIUM + bool "Medium priority" + depends on STM32H5_DMA + ---help--- + 'Medium' priority. + +config STM32H5_QSPI_DMAPRIORITY_LOW + bool "Low priority" + depends on STM32H5_DMA + ---help--- + 'Low' priority. + +endchoice + +config STM32H5_QSPI_DMATHRESHOLD + int "QSPI DMA threshold" + default 4 + depends on STM32H5_QSPI_DMA + ---help--- + When QSPI DMA is enabled, small DMA transfers will still be performed + by polling logic. This value is the threshold below which transfers + will still be performed by conventional register status polling. + +config STM32H5_QSPI_DMADEBUG + bool "QSPI DMA transfer debug" + depends on STM32H5_QSPI_DMA && DEBUG_SPI && DEBUG_DMA + default n + ---help--- + Enable special debug instrumentation to analyze QSPI DMA data transfers. + This logic is as non-invasive as possible: It samples DMA + registers at key points in the data transfer and then dumps all of + the registers at the end of the transfer. + +config STM32H5_QSPI_REGDEBUG + bool "QSPI Register level debug" + depends on DEBUG_SPI_INFO + default n + ---help--- + Output detailed register-level QSPI device debug information. + Requires also CONFIG_DEBUG_SPI_INFO. + +endmenu + endif # ARCH_CHIP_STM32H5 diff --git a/arch/arm/src/stm32h5/Make.defs b/arch/arm/src/stm32h5/Make.defs index 6eeee5e16fc70..452d284d143cb 100644 --- a/arch/arm/src/stm32h5/Make.defs +++ b/arch/arm/src/stm32h5/Make.defs @@ -58,6 +58,10 @@ ifeq ($(CONFIG_STM32H5_SPI),y) CHIP_CSRCS += stm32_spi.c endif +ifeq ($(CONFIG_STM32H5_QSPI1),y) +CHIP_CSRCS += stm32_qspi.c +endif + # Required chip type specific files ifeq ($(CONFIG_STM32H5_STM32H5XXXX),y) diff --git a/arch/arm/src/stm32h5/stm32_qspi.c b/arch/arm/src/stm32h5/stm32_qspi.c new file mode 100644 index 0000000000000..98fa4960e26eb --- /dev/null +++ b/arch/arm/src/stm32h5/stm32_qspi.c @@ -0,0 +1,2756 @@ +/**************************************************************************** + * arch/arm/src/stm32h5/stm32_qspi.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include "stm32_qspi.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "arm_internal.h" +#include "barriers.h" + +#include "stm32_gpio.h" +#include "stm32_rcc.h" +#include "hardware/stm32_qspi.h" + +#ifdef CONFIG_STM32H5_QSPI_DMA +#include "stm32_dma.h" +#endif + +#ifdef CONFIG_STM32H5_QSPI1 + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* QSPI memory synchronization */ + +#define MEMORY_SYNC() do { ARM_DSB(); ARM_ISB(); } while (0) + +/* Debug ********************************************************************/ + +/* Check if QSPI debug is enabled */ + +#ifndef CONFIG_DEBUG_DMA +# undef CONFIG_STM32H5_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_STM32H5_QSPI_INTERRUPTS) && defined(CONFIG_STM32H5_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_STM32H5_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_STM32H5_QSPI_DMAPRIORITY_LOW) +# define QSPI_DMA_PRIO DMA_SCR_PRILO +# elif defined(CONFIG_STM32H5_QSPI_DMAPRIORITY_MEDIUM) +# define QSPI_DMA_PRIO DMA_SCR_PRIMED +# elif defined(CONFIG_STM32H5_QSPI_DMAPRIORITY_HIGH) +# define QSPI_DMA_PRIO DMA_SCR_PRIHI +# elif defined(CONFIG_STM32H5_QSPI_DMAPRIORITY_VERYHIGH) +# define QSPI_DMA_PRIO DMA_SCR_PRIVERYHI +# else +# define QSPI_DMA_PRIO DMA_SCR_PRIMED +# endif + +#endif /* CONFIG_STM32H5_QSPI_DMA */ + +#ifndef STM32_SYSCLK_FREQUENCY +# error your board.h needs to define the value of STM32_SYSCLK_FREQUENCY +#endif + +#if !defined(CONFIG_STM32H5_QSPI_FLASH_SIZE) || 0 == CONFIG_STM32H5_QSPI_FLASH_SIZE +# error you must specify a positive flash size via CONFIG_STM32H5_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 + * peripheral by defining the BOARD_QSPI_CLK macro to one of the + * RCC_CCIPR4_OCTOSPI1SEL_XXX values (XXX = HCLK, PLL1, PLL2, PER). + * QUADSPI clock defaults to HCLK. + */ + +#if defined(CONFIG_STM32H5_QSPI1) && !defined(STM32_RCC_CCIPR4_OCTOSPI1SEL) +# error your board.h needs to define STM32_RCC_CCIPR4_OCTOSPI1SEL +#endif + +#if defined(CONFIG_STM32H5_QSPI1) && !defined(STM32_QSPI_FREQUENCY) +# error your board.h needs to defined STM32_QSPI_FREQUENCY +#else +# define QSPI_CLK_FREQUENCY STM32_QSPI_FREQUENCY +#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. + */ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* The state of the QSPI controller. + * + * NOTE: the STM32H5 supports only a single QSPI peripheral. Logic here is + * designed to support multiple QSPI peripherals. + */ + +struct stm32_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 */ + mutex_t lock; /* Assures mutually exclusive access to QSPI */ + bool memmap; /* TRUE: Controller is in memory mapped mode */ + +#ifdef CONFIG_STM32H5_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_STM32H5_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_STM32H5_QSPI_DMADEBUG + struct stm32_dmaregs_s dmaregs[DMA_NSAMPLES]; +#endif + +#ifdef CONFIG_STM32H5_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) */ + void *buffer; /* Data buffer */ + + uint8_t isddr; /* true if 'double data rate' */ + +#ifdef CONFIG_STM32H5_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_STM32H5_QSPI_REGDEBUG +static bool qspi_checkreg(struct stm32_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 stm32_qspidev_s *priv, + unsigned int offset); +static inline void qspi_putreg(struct stm32_qspidev_s *priv, + uint32_t value, unsigned int offset); + +#ifdef CONFIG_DEBUG_SPI_INFO +static void qspi_dumpregs(struct stm32_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_STM32H5_QSPI_INTERRUPTS +static int qspi0_interrupt(int irq, void *context, void *arg); + +#endif + +/* DMA support */ + +#ifdef CONFIG_STM32H5_QSPI_DMA + +# ifdef CONFIG_STM32H5_QSPI_DMADEBUG +# define qspi_dma_sample(s,i) stm32_dmasample((s)->dmach, &(s)->dmaregs[i]) +static void qspi_dma_sampleinit(struct stm32_qspidev_s *priv); +static void qspi_dma_sampledone(struct stm32_qspidev_s *priv); +# else +# define qspi_dma_sample(s,i) +# define qspi_dma_sampleinit(s) +# define qspi_dma_sampledone(s) +# endif + +# ifndef CONFIG_STM32H5_QSPI_DMATHRESHOLD +# define CONFIG_STM32H5_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 void *qspi_alloc(struct qspi_dev_s *dev, size_t buflen); +static void qspi_free(struct qspi_dev_s *dev, void *buffer); + +/* Initialization */ + +static int qspi_hw_initialize(struct stm32_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, +#ifdef CONFIG_QSPI_HWFEATURES + .hwfeatures = NULL, +#endif + .command = qspi_command, + .memory = qspi_memory, + .alloc = qspi_alloc, + .free = qspi_free, +}; + +/* This is the overall state of the QSPI0 controller */ + +static struct stm32_qspidev_s g_qspi0dev = +{ + .qspi = + { + .ops = &g_qspi0ops, + }, + .base = STM32_OCTOSPI1_BASE, + .lock = NXMUTEX_INITIALIZER, +#ifdef CONFIG_STM32H5_QSPI_INTERRUPTS + .handler = qspi0_interrupt, + .irq = STM32_IRQ_OCTOSPI1, + .op_sem = SEM_INITIALIZER(0), +#endif + .intf = 0, +#ifdef CONFIG_STM32H5_QSPI_DMA + .candma = true, + .dmawait = SEM_INITIALIZER(0), +#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_STM32H5_QSPI_REGDEBUG +static bool qspi_checkreg(struct stm32_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 stm32_qspidev_s *priv, + unsigned int offset) +{ + uint32_t address = priv->base + offset; + uint32_t value = getreg32(address); + +#ifdef CONFIG_STM32H5_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 stm32_qspidev_s *priv, + uint32_t value, unsigned int offset) +{ + uint32_t address = priv->base + offset; + +#ifdef CONFIG_STM32H5_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 stm32_qspidev_s *priv, const char *msg) +{ + spiinfo("%s:\n", msg); + + spiinfo(" CR:%08x TCR:%08x CCR:%08x SR:%08x\n", + getreg32(priv->base + STM32_QUADSPI_CR_OFFSET), /* Control Register */ + getreg32(priv->base + STM32_QUADSPI_TCR_OFFSET), /* Device Configuration Register 1 */ + 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 DCR1:%08x DCR2:%08x\n", + getreg32(priv->base + STM32_QUADSPI_PIR_OFFSET), /* Polling Interval Register */ + getreg32(priv->base + STM32_QUADSPI_LPTR_OFFSET), /* Low-Power Timeout Register */ + getreg32(priv->base + STM32_QUADSPI_DCR1_OFFSET), /* Device Configuration Register 1 */ + getreg32(priv->base + STM32_QUADSPI_DCR2_OFFSET)); /* Device Configuration Register 2 */ + spiinfo(" DCR3:%08x DCR4:%08x\n", + getreg32(priv->base + STM32_QUADSPI_DCR3_OFFSET), /* Device Configuration Register 3 */ + getreg32(priv->base + STM32_QUADSPI_DCR4_OFFSET)); /* Device Configuration Register 4 */ +} +#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_STM32H5_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 stm32_qspidev_s *priv) +{ + /* Put contents of register samples into a known state */ + + memset(priv->dmaregs, 0xff, + DMA_NSAMPLES * sizeof(struct stm32_dmaregs_s)); + + /* Then get the initial samples */ + + stm32_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 stm32_qspidev_s *priv) +{ + /* Sample the final registers */ + + stm32_dmasample(priv->dmach, &priv->dmaregs[DMA_END_TRANSFER]); + + /* Then dump the sampled DMA registers */ + + /* Initial register values */ + + stm32_dmadump(priv->dmach, &priv->dmaregs[DMA_INITIAL], + "Initial Registers"); + + /* Register values after DMA setup */ + + stm32_dmadump(priv->dmach, &priv->dmaregs[DMA_AFTER_SETUP], + "After DMA Setup"); + + /* Register values after DMA start */ + + stm32_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) + { + stm32_dmadump(priv->dmach, &priv->dmaregs[DMA_TIMEOUT], + "At DMA timeout"); + } + else + { + stm32_dmadump(priv->dmach, &priv->dmaregs[DMA_CALLBACK], + "At DMA callback"); + } + + stm32_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 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_STM32H5_QSPI_INTERRUPTS) + xctn->function = QSPICMD_ISWRITE(cmdinfo->flags) ? CR_FMODE_INDWR : + CR_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 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_STM32H5_QSPI_INTERRUPTS) + xctn->function = QSPIMEM_ISWRITE(meminfo->flags) ? CR_FMODE_INDWR : + CR_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 stm32_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 stm32_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 stm32_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 && CR_FMODE_MEMMAP != fctn) + { + qspi_putreg(priv, xctn->datasize - 1, STM32_QUADSPI_DLR_OFFSET); + } + + /* If we have alternate bytes, stick them in now */ + + if (xctn->altbytesmode != CCR_ABMODE_NONE) + { + qspi_putreg(priv, xctn->altbytes, STM32_QUADSPI_ABR_OFFSET); + } + + /* Build the CCR value and set it */ + + regval = 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_DMODE(xctn->datamode) | + (xctn->isddr ? QSPI_CCR_DDTR : 0) | + (xctn->isddr ? QSPI_CCR_ABDTR : 0) | + (xctn->isddr ? QSPI_CCR_ADDTR : 0); + qspi_putreg(priv, regval, STM32_QUADSPI_CCR_OFFSET); + + regval = qspi_getreg(priv, STM32_QUADSPI_CR_OFFSET); + regval = (regval & ~(QSPI_CR_FMODE_MASK)) | QSPI_CR_FMODE(fctn); + qspi_putreg(priv, regval, STM32_QUADSPI_CR_OFFSET); + + regval = qspi_getreg(priv, STM32_QUADSPI_TCR_OFFSET); + regval = (regval & ~(QSPI_TCR_DCYC_MASK)) | + QSPI_TCR_DCYC(xctn->dummycycles); + qspi_putreg(priv, regval, STM32_QUADSPI_TCR_OFFSET); + + /* Writing IR could trigger a transaction if: + * ADMODE == 000 & (FMODE = 01 | DMODE = 000) + */ + + regval = xctn->instr; + qspi_putreg(priv, regval, STM32_QUADSPI_IR_OFFSET); + + /* If we have and need and address, set that now, too */ + + if (CCR_ADMODE_NONE != xctn->addrmode && CR_FMODE_MEMMAP != fctn) + { + qspi_putreg(priv, xctn->addr, STM32_QUADSPI_AR_OFFSET); + } +} + +#if defined(CONFIG_STM32H5_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, 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 == CR_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 == CR_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 == CR_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_STM32H5_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 stm32_qspidev_s *priv = (struct stm32_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 stm32_qspidev_s *priv = (struct stm32_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 stm32_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 stm32_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) ? CR_FMODE_INDWR : + CR_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_STM32H5_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 stm32_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 CR register specifies indirect read */ + + regval = qspi_getreg(priv, STM32_QUADSPI_CR_OFFSET); + regval &= ~QSPI_CR_FMODE_MASK; + regval |= QSPI_CR_FMODE(CR_FMODE_INDRD); + qspi_putreg(priv, regval, STM32_QUADSPI_CR_OFFSET); + + /* Start the transfer by re-writing the address in AR or IR register */ + + if (xctn->addrmode != CCR_ADMODE_NONE) + { + qspi_putreg(priv, addrval, STM32_QUADSPI_AR_OFFSET); + } + else + { + qspi_putreg(priv, xctn->instr, STM32_QUADSPI_IR_OFFSET); + } + + if (qspi_getreg(priv, STM32_QUADSPI_SR_OFFSET) & QSPI_SR_TEF) + { + qspi_putreg(priv, QSPI_FCR_CTEF, STM32_QUADSPI_FCR_OFFSET); + qspi_abort(priv); + return -ENOTBLK; + } + + 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 stm32_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 stm32_qspidev_s *priv = (struct stm32_qspidev_s *)dev; + int ret; + + spiinfo("lock=%d\n", lock); + if (lock) + { + /* Take the mutex (perhaps waiting) */ + + ret = nxmutex_lock(&priv->lock); + } + else + { + ret = nxmutex_unlock(&priv->lock); + } + + 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 stm32_qspidev_s *priv = (struct stm32_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=%" PRId32 "\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_DCR2_OFFSET); + regval &= ~(QSPI_DCR2_PRESCALER_MASK); + regval |= (prescaler - 1) << QSPI_DCR2_PRESCALER_SHIFT; + qspi_putreg(priv, regval, STM32_QUADSPI_DCR2_OFFSET); + + /* Calculate the new actual frequency */ + + actual = QSPI_CLK_FREQUENCY / prescaler; + spiinfo("prescaler=%" PRId32 " actual=%" PRId32 "\n", prescaler, actual); + + /* Save the frequency setting */ + + priv->frequency = frequency; + priv->actual = actual; + + spiinfo("Frequency %" PRId32 "->%" PRId32 "\n", frequency, actual); + return actual; +} + +/**************************************************************************** + * Name: qspi_setmode + * + * Description: + * Set the QSPI mode. Optional. See enum qspi_mode_e for mode definitions. + * NOTE: the STM32H5 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 stm32_qspidev_s *priv = (struct stm32_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_DCR1_OFFSET); + regval &= ~(QSPI_DCR1_CKMODE); + + switch (mode) + { + case QSPIDEV_MODE0: /* CPOL=0; CPHA=0 */ + break; + + case QSPIDEV_MODE3: /* CPOL=1; CPHA=1 */ + regval |= (QSPI_DCR1_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_DCR1_OFFSET); + spiinfo("DCR1=%08" PRIx32 "\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 STM32H5 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 STM32H5x6 */ + + 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 stm32_qspidev_s *priv = (struct stm32_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_STM32H5_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((uintptr_t)cmdinfo->buffer, 4)); + + if (QSPICMD_ISWRITE(cmdinfo->flags)) + { + uint32_t regval; + + /* Set up the Communications Configuration Register as per command + * info + */ + + qspi_ccrconfig(priv, &xctn, CR_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, CR_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, CR_FMODE_INDWR); + } + + /* 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 */ + + /* That may be it, unless there is also data to transfer */ + + qspi_ccrconfig(priv, &xctn, CR_FMODE_INDWR); + + if (QSPICMD_ISDATA(cmdinfo->flags)) + { + DEBUGASSERT(cmdinfo->buffer != NULL && cmdinfo->buflen > 0); + DEBUGASSERT(IS_ALIGNED((uintptr_t)cmdinfo->buffer, 4)); + + 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 stm32_qspidev_s *priv = (struct stm32_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_STM32H5_QSPI_INTERRUPTS + /* interrupt mode will need access to the transaction context */ + + priv->xctn = &xctn; + + DEBUGASSERT(meminfo->buffer != NULL && meminfo->buflen > 0); + DEBUGASSERT(IS_ALIGNED((uintptr_t)meminfo->buffer, 4)); + + if (QSPIMEM_ISWRITE(meminfo->flags)) + { + uint32_t regval; + + /* Set up the Communications Configuration Register as per meminfo */ + + qspi_ccrconfig(priv, &xctn, CR_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, CR_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_STM32H5_QSPI_DMA) + /* Can we perform DMA? Should we perform DMA? */ + + if (priv->candma && + meminfo->buflen > CONFIG_STM32H5_QSPI_DMATHRESHOLD && + IS_ALIGNED((uintptr_t)meminfo->buffer, 4) && + IS_ALIGNED(meminfo->buflen, 4)) + { + 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) ? CR_FMODE_INDWR : + CR_FMODE_INDRD); + + /* Transfer data */ + + DEBUGASSERT(meminfo->buffer != NULL && meminfo->buflen > 0); + DEBUGASSERT(IS_ALIGNED((uintptr_t)meminfo->buffer, 4)); + + 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) ? CR_FMODE_INDWR : + CR_FMODE_INDRD); + + /* Transfer data */ + + DEBUGASSERT(meminfo->buffer != NULL && meminfo->buflen > 0); + DEBUGASSERT(IS_ALIGNED((uintptr_t)meminfo->buffer, 4)); + + 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 void *qspi_alloc(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. + */ + + /* Ensure that the DMA buffers are word-aligned. */ + + return kmm_malloc(ALIGN_UP(buflen, 4)); +} + +/**************************************************************************** + * 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(struct qspi_dev_s *dev, 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 stm32_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_MSEL | QSPI_CR_DMM); + +#if defined(CONFIG_STM32H5_QSPI_MODE_BANK2) + regval |= QSPI_CR_MSEL; +#endif + +#if defined(CONFIG_STM32H5_QSPI_MODE_DUAL) + regval |= QSPI_CR_DMM; +#endif + + /* Configure QSPI FIFO Threshold */ + + regval &= ~(QSPI_CR_FTHRES_MASK); + regval |= ((CONFIG_STM32H5_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_DCR2_OFFSET); + regval &= ~(QSPI_DCR2_PRESCALER_MASK); + regval |= (0x01 << QSPI_DCR2_PRESCALER_SHIFT); + qspi_putreg(priv, regval, STM32_QUADSPI_DCR2_OFFSET); + + regval = qspi_getreg(priv, STM32_QUADSPI_TCR_OFFSET); + regval &= ~(QSPI_TCR_SSHIFT); + qspi_putreg(priv, regval, STM32_QUADSPI_TCR_OFFSET); + + /* Configure QSPI Flash Size, CS High Time and Clock Mode */ + + regval = qspi_getreg(priv, STM32_QUADSPI_DCR1_OFFSET); + regval &= ~(QSPI_DCR1_CKMODE | + QSPI_DCR1_CSHT_MASK | + QSPI_DCR1_DEVSIZE_MASK); + + regval |= ((CONFIG_STM32H5_QSPI_CSHT - 1) << QSPI_DCR1_CSHT_SHIFT); + if (0 != CONFIG_STM32H5_QSPI_FLASH_SIZE) + { + unsigned int nsize = CONFIG_STM32H5_QSPI_FLASH_SIZE; + int nlog2size = 31; + + while ((nsize & 0x80000000) == 0) + { + --nlog2size; + nsize <<= 1; + } + + regval |= ((nlog2size - 1) << QSPI_DCR1_DEVSIZE_SHIFT); + } + + qspi_putreg(priv, regval, STM32_QUADSPI_DCR1_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: stm32_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 *stm32_qspi_initialize(int intf) +{ + struct stm32_qspidev_s *priv; + uint32_t regval; + int ret; + + /* The STM32H5 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; + + /* Enable clocking to the QSPI peripheral */ + + regval = getreg32(STM32_RCC_AHB4ENR); + regval |= RCC_AHB4ENR_OSPI1EN; + putreg32(regval, STM32_RCC_AHB4ENR); + + /* Reset the QSPI peripheral */ + + regval = getreg32(STM32_RCC_AHB4RSTR); + regval |= RCC_AHB4RSTR_OSPI1RST; + putreg32(regval, STM32_RCC_AHB4RSTR); + regval &= ~RCC_AHB4RSTR_OSPI1RST; + putreg32(regval, STM32_RCC_AHB4RSTR); + + /* 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. */ + +#ifdef CONFIG_STM32H5_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; + } + } +#endif + +#ifdef CONFIG_STM32H5_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_dmach; + } +#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_STM32H5_QSPI_INTERRUPTS + up_enable_irq(priv->irq); +#endif + } + + return &priv->qspi; + +errout_with_irq: +#ifdef CONFIG_STM32H5_QSPI_INTERRUPTS + irq_detach(priv->irq); + +errout_with_dmach: +#endif +#ifdef CONFIG_STM32H5_QSPI_DMA + if (priv->dmach) + { + stm32_dmafree(priv->dmach); + priv->dmach = NULL; + } +#endif + + return NULL; +} + +/**************************************************************************** + * Name: stm32_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 stm32_qspi_enter_memorymapped(struct qspi_dev_s *dev, + const struct qspi_meminfo_s *meminfo, + uint32_t lpto) +{ + struct stm32_qspidev_s *priv = (struct stm32_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_STM32H5_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_STM32H5_QSPI_INTERRUPTS + priv->xctn = NULL; +#endif + + /* set it into the ccr */ + + qspi_ccrconfig(priv, &xctn, CR_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: stm32_qspi_exit_memorymapped + * + * Description: + * Take the QSPI device out of memory mapped mode + * + * Input Parameters: + * dev - QSPI device + * + * Returned Value: + * None + * + ****************************************************************************/ + +void stm32_qspi_exit_memorymapped(struct qspi_dev_s *dev) +{ + struct stm32_qspidev_s *priv = (struct stm32_qspidev_s *)dev; + + qspi_lock(dev, true); + + /* A simple abort is sufficient */ + + qspi_abort(priv); + priv->memmap = false; + + qspi_lock(dev, false); +} + +#endif /* CONFIG_STM32H5_QSPI */ diff --git a/arch/arm/src/stm32h5/stm32_qspi.h b/arch/arm/src/stm32h5/stm32_qspi.h new file mode 100644 index 0000000000000..e052df4c1d7c3 --- /dev/null +++ b/arch/arm/src/stm32h5/stm32_qspi.h @@ -0,0 +1,129 @@ +/**************************************************************************** + * arch/arm/src/stm32h5/stm32_qspi.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __ARCH_ARM_SRC_STM32H5_STM32_QSPI_H +#define __ARCH_ARM_SRC_STM32H5_STM32_QSPI_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include + +#include "chip.h" + +#ifdef CONFIG_STM32H5_QSPI1 + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/**************************************************************************** + * Inline Functions + ****************************************************************************/ + +#ifndef __ASSEMBLY__ + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +#undef EXTERN +#if defined(__cplusplus) +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: stm32l4_qspi_initialize + * + * Description: + * Initialize the selected QSPI port in master mode + * + * Input Parameters: + * intf - Interface number(must be zero) + * + * Returned Value: + * Valid SPI device structure reference on success; a NULL on failure + * + ****************************************************************************/ + +struct qspi_dev_s; +struct qspi_dev_s *stm32_qspi_initialize(int intf); + +/**************************************************************************** + * Name: stm32l4_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 + * lpto - number of cycles to wait to automatically de-assert CS + * + * Returned Value: + * None + * + ****************************************************************************/ + +void stm32_qspi_enter_memorymapped(struct qspi_dev_s *dev, + const struct qspi_meminfo_s *meminfo, + uint32_t lpto); + +/**************************************************************************** + * Name: stm32l4_qspi_exit_memorymapped + * + * Description: + * Take the QSPI device out of memory mapped mode + * + * Input Parameters: + * dev - QSPI device + * + * Returned Value: + * None + * + ****************************************************************************/ + +void stm32_qspi_exit_memorymapped(struct qspi_dev_s *dev); + +#undef EXTERN +#if defined(__cplusplus) +} +#endif + +#endif /* __ASSEMBLY__ */ +#endif /* CONFIG_STM32H5_QSPI */ +#endif /* __ARCH_ARM_SRC_STM32H5_STM32_QSPI_H */ diff --git a/arch/arm/src/stm32h5/stm32h5xx_rcc.c b/arch/arm/src/stm32h5/stm32h5xx_rcc.c index 0a1c2d50864f3..18a051341ec44 100644 --- a/arch/arm/src/stm32h5/stm32h5xx_rcc.c +++ b/arch/arm/src/stm32h5/stm32h5xx_rcc.c @@ -1205,6 +1205,15 @@ void stm32_stdclockconfig(void) putreg32(regval, STM32_RCC_CCIPR5); #endif + /* Configure OCTOSPI1 source clock */ + +#if defined(STM32_RCC_CCIPR4_OCTOSPI1SEL) + regval = getreg32(STM32_RCC_CCIPR4); + regval &= ~RCC_CCIPR4_OCTOSPI1SEL_MASK; + regval |= STM32_RCC_CCIPR4_OCTOSPI1SEL; + putreg32(regval, STM32_RCC_CCIPR4); +#endif + /* Configure SPI1 source clock */ #if defined(STM32_RCC_CCIPR3_SPI1SEL)