From 1cfab89e65e1ec5cd24b22219c476612131367d6 Mon Sep 17 00:00:00 2001 From: Kyle Wilson Date: Thu, 5 Dec 2024 13:08:56 -0600 Subject: [PATCH] Added incomplete octospi hardware defintions. Register addresses are defined. Bit defintions are still needed. Bitmaps for OCTOSPI peripheral register Incremental updates to qspi files Redefined QSPI/OCTOSPI registers for STM32H5 Fixed register definitions for the STM32H5. However, more work still needs to be done. Some bits that shared registers on the STM32H7 have different register locations on the H5. This still needs to be accounted for. Fixed more differences vs STM32H7 qspi Added ifdef for including stm32_dma.h in stm32_qspi.c. Added stm32_qspi.c to Make.defs. Register fixes. SPI activity but not able to format device yet. Fixed DCYC mask Set HCLK frequency to correct value. Undid ccrconfig debug. Tested Interrupt Mode (single SPI). Added alternate bytes to meminfo and cmdinfo structures. Updated Kconfig variables for STM32H5. Fixed base register in hardware/stm32_qspi.h. Updated qspi_dumpregs. The base register was previously set to STM32_QUADSPI_BASE, changed to the correctly named STM32_OCTOSPI1_BASE. However, these defines for the OCTOSPI registers are not even used. Instead qspi_putreg and qspi_getreg utilize the priv->base value to access OCTOSPI registers. Removed altbytes code, left as before. Moved QSPI clock selection to stm32h5xx_rcc.c Changed STM32H5_QUADSPI to STM32H5_QSPI1 Added hook to define QSPI_CLK_FREQUENCY as STM32_QSPI_FREQUENCY from board.h Removed changes to nuttx qspi.h style fixes --- arch/arm/src/stm32h5/Kconfig | 138 ++ arch/arm/src/stm32h5/Make.defs | 4 + arch/arm/src/stm32h5/stm32_qspi.c | 2756 ++++++++++++++++++++++++++ arch/arm/src/stm32h5/stm32_qspi.h | 129 ++ arch/arm/src/stm32h5/stm32h5xx_rcc.c | 9 + 5 files changed, 3036 insertions(+) create mode 100644 arch/arm/src/stm32h5/stm32_qspi.c create mode 100644 arch/arm/src/stm32h5/stm32_qspi.h diff --git a/arch/arm/src/stm32h5/Kconfig b/arch/arm/src/stm32h5/Kconfig index 2aa85b7c8e..ebba830b7b 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 6eeee5e16f..452d284d14 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 0000000000..98fa4960e2 --- /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 0000000000..e052df4c1d --- /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 0a1c2d5086..18a051341e 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)