| /**************************************************************************** |
| * arch/arm/src/sama5/sam_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 <nuttx/config.h> |
| |
| #include <sys/types.h> |
| #include <stdint.h> |
| #include <stdbool.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <assert.h> |
| #include <debug.h> |
| |
| #include <arch/board/board.h> |
| |
| #include <nuttx/arch.h> |
| #include <nuttx/wdog.h> |
| #include <nuttx/clock.h> |
| #include <nuttx/kmalloc.h> |
| #include <nuttx/mutex.h> |
| #include <nuttx/semaphore.h> |
| #include <nuttx/spi/qspi.h> |
| |
| #include "arm_internal.h" |
| #include "barriers.h" |
| |
| #include "sam_pio.h" |
| #include "sam_dmac.h" |
| #include "sam_periphclks.h" |
| #include "sam_qspi.h" |
| #include "hardware/sam_pmc.h" |
| #include "hardware/sam_xdmac.h" |
| #include "hardware/sam_qspi.h" |
| #include "hardware/sam_pinmap.h" |
| |
| #if (defined(CONFIG_SAMA5_QSPI0) || (defined(CONFIG_SAMA5_QSPI1))) |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| /* Configuration ************************************************************/ |
| |
| #ifndef CONFIG_SAMA5_QSPI_DLYBS |
| # define CONFIG_SAMA5_QSPI_DLYBS 0 |
| #endif |
| |
| #ifndef CONFIG_SAMA5_QSPI_DLYBCT |
| # define CONFIG_SAMA5_QSPI_DLYBCT 0 |
| #endif |
| |
| #ifndef CONFIG_DEBUG_SPI_INFO |
| # undef CONFIG_SAMA5_QSPI_REGDEBUG |
| #endif |
| |
| /* When QSPI DMA is enabled, small DMA transfers will still be performed by |
| * polling logic. But we need a threshold value to determine what is small. |
| * That value is provided by CONFIG_SAMA5_QSPI_DMATHRESHOLD. |
| */ |
| |
| #ifndef CONFIG_SAMA5_QSPI_DMATHRESHOLD |
| # define CONFIG_SAMA5_QSPI_DMATHRESHOLD 4 |
| #endif |
| |
| #ifndef CONFIG_SAMA5_XDMAC0 |
| # undef CONFIG_SAMA5_QSPI_DMA |
| #endif |
| |
| #ifdef CONFIG_SAMA5_QSPI_DMA |
| # define SAMA5_QSPI0_DMA true |
| #endif |
| |
| #ifndef CONFIG_SAMA5_QSPI_DMA |
| # undef CONFIG_SAMA5_QSPI_DMADEBUG |
| #endif |
| |
| /* QSPI interrupts are not used */ |
| |
| #undef QSPI_USE_INTERRUPTS |
| |
| /* Clocking *****************************************************************/ |
| |
| /* The QSPI Baud rate clock is generated by dividing the peripheral clock by |
| * a value between 1 and 255 |
| */ |
| |
| #define SAM_QSPI_CLOCK BOARD_MCK_FREQUENCY /* Frequency of the main clock */ |
| |
| /* 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. This is set to |
| */ |
| |
| #define DMA_TIMEOUT_MS (800) |
| #define DMA_TIMEOUT_TICKS MSEC2TICK(DMA_TIMEOUT_MS) |
| |
| /* QSPI memory synchronization */ |
| |
| #define MEMORY_SYNC() do { ARM_DSB(); ARM_ISB(); } while (0) |
| |
| /* The SAMA5x QSPI driver insists that transfers be performed in multiples |
| * of 32-bits. The alignment requirement only applies to RX DMA data. |
| */ |
| |
| #define ALIGN_SHIFT 2 |
| #define ALIGN_MASK 3 |
| #define ALIGN_UP(n) (((n)+ALIGN_MASK) & ~ALIGN_MASK) |
| #define IS_ALIGNED(n) (((uint32_t)(n) & ALIGN_MASK) == 0) |
| |
| /* Debug ********************************************************************/ |
| |
| /* Check if QSPI debug is enabled */ |
| |
| #ifndef CONFIG_DEBUG_DMA |
| # undef CONFIG_SAMA5_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 |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| /* The state of the QSPI controller. */ |
| |
| struct sam_qspidev_s |
| { |
| struct qspi_dev_s qspi; /* Externally visible part of the QSPI interface */ |
| #ifdef QSPI_USE_INTERRUPTS |
| xcpt_t handler; /* Interrupt handler */ |
| #endif |
| uint32_t base; /* QSPI controller register base address */ |
| uint32_t membase; /* QSPI virtual memory region base address */ |
| uint32_t frequency; /* Requested clock frequency */ |
| uint32_t actual; /* Actual clock frequency */ |
| uint8_t mode; /* Mode 0,1,2,3 */ |
| uint8_t nbits; /* Width of word in bits (8 to 16) */ |
| uint8_t intf; /* QSPI controller number (0, 1) */ |
| #ifdef QSPI_USE_INTERRUPTS |
| uint8_t irq; /* Interrupt number */ |
| #endif |
| bool initialized; /* TRUE: Controller has been initialized */ |
| mutex_t lock; /* Assures mutually exclusive access to QSPI */ |
| |
| #ifdef CONFIG_SAMA5_QSPI_DMA |
| bool candma; /* DMA is supported */ |
| uint8_t rxintf; /* RX hardware interface number */ |
| uint8_t txintf; /* TX hardware interface number */ |
| sem_t dmawait; /* Used to wait for DMA completion */ |
| struct wdog_s dmadog; /* Watchdog that handles DMA timeouts */ |
| int result; /* DMA result */ |
| DMA_HANDLE dmach; /* QSPI DMA handle */ |
| #endif |
| |
| /* Debug stuff */ |
| |
| #ifdef CONFIG_SAMA5_QSPI_DMADEBUG |
| struct sam_dmaregs_s dmaregs[DMA_NSAMPLES]; |
| #endif |
| |
| #ifdef CONFIG_SAMA5_QSPI_REGDEBUG |
| bool wrlast; /* Last was a write */ |
| uint32_t addresslast; /* Last address */ |
| uint32_t valuelast; /* Last value */ |
| int ntimes; /* Number of times */ |
| #endif |
| }; |
| |
| /**************************************************************************** |
| * Private Function Prototypes |
| ****************************************************************************/ |
| |
| /* Helpers */ |
| |
| #ifdef CONFIG_SAMA5_QSPI_REGDEBUG |
| static bool qspi_checkreg(struct sam_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 sam_qspidev_s *priv, |
| unsigned int offset); |
| static inline void qspi_putreg(struct sam_qspidev_s *priv, uint32_t value, |
| unsigned int offset); |
| |
| #ifdef CONFIG_DEBUG_SPI_INFO |
| static void qspi_dumpregs(struct sam_qspidev_s *priv, const char *msg); |
| #else |
| # define qspi_dumpregs(priv,msg) |
| #endif |
| |
| /* DMA support */ |
| |
| #ifdef CONFIG_SAMA5_QSPI_DMA |
| #ifdef CONFIG_SAMA5_QSPI_DMADEBUG |
| # define qspi_dma_sample(s,i) sam_dmasample((s)->dmach, &(s)->dmaregs[i]) |
| static void qspi_dma_sampleinit(struct sam_qspidev_s *priv); |
| static void qspi_dma_sampledone(struct sam_qspidev_s *priv); |
| |
| #else |
| # define qspi_dma_sample(s,i) |
| # define qspi_dma_sampleinit(s) |
| # define qspi_dma_sampledone(s) |
| |
| #endif |
| |
| static void qspi_dma_callback(DMA_HANDLE handle, void *arg, int result); |
| static inline uintptr_t qspi_regaddr(struct sam_qspidev_s *priv, |
| unsigned int offset); |
| #endif |
| |
| static int qspi_memory_enable(struct sam_qspidev_s *priv, |
| struct qspi_meminfo_s *meminfo); |
| #ifdef CONFIG_SAMA5_QSPI_DMA |
| static int qspi_memory_dma(struct sam_qspidev_s *priv, |
| struct qspi_meminfo_s *meminfo); |
| #endif |
| |
| static int qspi_memory_nodma(struct sam_qspidev_s *priv, |
| struct qspi_meminfo_s *meminfo); |
| static void qspi_memcpy(uint8_t *dest, const uint8_t *src, |
| size_t buflen); |
| |
| /* Interrupts */ |
| |
| #ifdef QSPI_USE_INTERRUPTS |
| static int qspi_interrupt(struct sam_qspidev_s *priv); |
| #ifdef CONFIG_SAMA5_QSPI |
| static int qspi0_interrupt(int irq, void *context, void *arg); |
| #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 sam_qspidev_s *priv); |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_SAMA5_QSPI0 |
| /* 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 sam_qspidev_s g_qspi0dev = |
| { |
| .qspi = |
| { |
| .ops = &g_qspi0ops, |
| }, |
| .base = SAM_QSPI0_VBASE, |
| .membase = SAM_QSPI0_VSECTION, |
| #ifdef QSPI_USE_INTERRUPTS |
| .handler = qspi0_interrupt, |
| #endif |
| .intf = 0, |
| #ifdef QSPI_USE_INTERRUPTS |
| .irq = SAM_IRQ_QSPI0, |
| #endif |
| .lock = NXMUTEX_INITIALIZER, |
| #ifdef CONFIG_SAMA5_QSPI_DMA |
| .candma = SAMA5_QSPI0_DMA, |
| .rxintf = XDMAC0_CH_QSPI0_RX, |
| .txintf = XDMAC0_CH_QSPI0_TX, |
| .dmawait = SEM_INITIALIZER(0), |
| #endif |
| }; |
| #endif /* CONFIG_SAMA5_QSPI0 */ |
| |
| #ifdef CONFIG_SAMA5_QSPI1 |
| /* QSPI1 driver operations */ |
| |
| static const struct qspi_ops_s g_qspi1ops = |
| { |
| .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 QSPI1 controller */ |
| |
| static struct sam_qspidev_s g_qspi1dev = |
| { |
| .qspi = |
| { |
| .ops = &g_qspi1ops, |
| }, |
| .base = SAM_QSPI1_VBASE, |
| .membase = SAM_QSPI1_VSECTION, |
| #ifdef QSPI_USE_INTERRUPTS |
| .handler = qspi1_interrupt, |
| #endif |
| .intf = 0, |
| #ifdef QSPI_USE_INTERRUPTS |
| .irq = SAM_IRQ_QSPI1, |
| #endif |
| .lock = NXMUTEX_INITIALIZER, |
| #ifdef CONFIG_SAMA5_QSPI_DMA |
| .candma = SAMA5_QSPI1_DMA, |
| .rxintf = XDMAC0_CH_QSPI1_RX, |
| .txintf = XDMAC0_CH_QSPI1_TX, |
| .dmawait = SEM_INITIALIZER(0), |
| #endif |
| }; |
| #endif /* CONFIG_SAMA5_QSPI1 */ |
| |
| /**************************************************************************** |
| * Public Data |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * 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. |
| * flase: This is the same as the preceding register access. |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_SAMA5_QSPI_REGDEBUG |
| static bool qspi_checkreg(struct sam_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 sam_qspidev_s *priv, |
| unsigned int offset) |
| { |
| uint32_t address = priv->base + offset; |
| uint32_t value = getreg32(address); |
| |
| #ifdef CONFIG_SAMA5_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 sam_qspidev_s *priv, uint32_t value, |
| unsigned int offset) |
| { |
| uint32_t address = priv->base + offset; |
| |
| #ifdef CONFIG_SAMA5_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 sam_qspidev_s *priv, const char *msg) |
| { |
| spiinfo("%s:\n", msg); |
| spiinfo(" MR:%08x SR:%08x IMR:%08x SCR:%08x\n", |
| getreg32(priv->base + SAM_QSPI_MR_OFFSET), |
| getreg32(priv->base + SAM_QSPI_SR_OFFSET), |
| getreg32(priv->base + SAM_QSPI_IMR_OFFSET), |
| getreg32(priv->base + SAM_QSPI_SCR_OFFSET)); |
| spiinfo(" IAR:%08x ICR:%08x IFR:%08x SMR:%08x\n", |
| getreg32(priv->base + SAM_QSPI_IAR_OFFSET), |
| getreg32(priv->base + SAM_QSPI_ICR_OFFSET), |
| getreg32(priv->base + SAM_QSPI_IFR_OFFSET), |
| getreg32(priv->base + SAM_QSPI_SMR_OFFSET)); |
| spiinfo(" WPCR:%08x WPSR:%08x\n", |
| getreg32(priv->base + SAM_QSPI_WPCR_OFFSET), |
| getreg32(priv->base + SAM_QSPI_WPSR_OFFSET)); |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: qspi_dma_sampleinit |
| * |
| * Description: |
| * Initialize sampling of DMA registers (if CONFIG_SAMA5_QSPI_DMADEBUG) |
| * |
| * Input Parameters: |
| * priv - QSPI driver instance |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_SAMA5_QSPI_DMADEBUG |
| static void qspi_dma_sampleinit(struct sam_qspidev_s *priv) |
| { |
| /* Put contents of register samples into a known state */ |
| |
| memset(priv->dmaregs, 0xff, DMA_NSAMPLES * sizeof(struct sam_dmaregs_s)); |
| |
| /* Then get the initial samples */ |
| |
| sam_dmasample(priv->dmach, &priv->dmaregs[DMA_INITIAL]); |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: qspi_dma_sampledone |
| * |
| * Description: |
| * Dump sampled DMA registers |
| * |
| * Input Parameters: |
| * priv - QSPI driver instance |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_SAMA5_QSPI_DMADEBUG |
| static void qspi_dma_sampledone(struct sam_qspidev_s *priv) |
| { |
| /* Sample the final registers */ |
| |
| sam_dmasample(priv->dmach, &priv->dmaregs[DMA_END_TRANSFER]); |
| |
| /* Then dump the sampled DMA registers */ |
| |
| /* Initial register values */ |
| |
| sam_dmadump(priv->dmach, &priv->dmaregs[DMA_INITIAL], |
| "Initial Registers"); |
| |
| /* Register values after DMA setup */ |
| |
| sam_dmadump(priv->dmach, &priv->dmaregs[DMA_AFTER_SETUP], |
| "After DMA Setup"); |
| |
| /* Register values after DMA start */ |
| |
| sam_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) |
| { |
| sam_dmadump(priv->dmach, &priv->dmaregs[DMA_TIMEOUT], |
| "At DMA timeout"); |
| } |
| else |
| { |
| sam_dmadump(priv->dmach, &priv->dmaregs[DMA_CALLBACK], |
| "At DMA callback"); |
| } |
| |
| sam_dmadump(priv->dmach, &priv->dmaregs[DMA_END_TRANSFER], |
| "At End-of-Transfer"); |
| } |
| #endif |
| |
| /**************************************************************************** |
| * 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. |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_SAMA5_QSPI_DMA |
| static void qspi_dma_timeout(wdparm_t arg) |
| { |
| struct sam_qspidev_s *priv = (struct sam_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); |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: qspi_dma_callback |
| * |
| * Description: |
| * This callback function is invoked at the completion of the QSPI RX DMA. |
| * |
| * Input Parameters: |
| * handle - The DMA handler |
| * arg - A pointer to the chip select structure |
| * result - The result of the DMA transfer |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_SAMA5_QSPI_DMA |
| static void qspi_dma_callback(DMA_HANDLE handle, void *arg, int result) |
| { |
| struct sam_qspidev_s *priv = (struct sam_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 TX callback has not |
| * already reported an error. |
| */ |
| |
| if (priv->result == -EBUSY) |
| { |
| /* Save the result of the transfer if no error was previously |
| * reported |
| */ |
| |
| priv->result = result; |
| } |
| |
| /* Then wake up the waiting thread */ |
| |
| nxsem_post(&priv->dmawait); |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: qspi_regaddr |
| * |
| * Description: |
| * Return the address of an QSPI register |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_SAMA5_QSPI_DMA |
| static inline uintptr_t qspi_regaddr(struct sam_qspidev_s *priv, |
| unsigned int offset) |
| { |
| return priv->base + offset; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: qspi_memory_enable |
| * |
| * Description: |
| * Enable the QSPI memory transfer |
| * |
| * Input Parameters: |
| * priv - 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_enable(struct sam_qspidev_s *priv, |
| struct qspi_meminfo_s *meminfo) |
| { |
| uint32_t regval; |
| |
| /* Write the Instruction code register: |
| * |
| * QSPI_ICR_INST(cmd) 8-bit command |
| * QSPI_ICR_OPT(0) No option |
| */ |
| |
| regval = QSPI_ICR_INST(meminfo->cmd) | QSPI_ICR_OPT(0); |
| qspi_putreg(priv, regval, SAM_QSPI_ICR_OFFSET); |
| |
| /* Is memory data scrambled? */ |
| |
| if (QSPIMEM_ISSCRAMBLE(meminfo->flags)) |
| { |
| /* Yes.. set the scramble key */ |
| |
| qspi_putreg(priv, meminfo->key, SAM_QSPI_SKR_OFFSET); |
| |
| /* Enable the scrambler and enable/disable the random value in the |
| * key. |
| */ |
| |
| regval = QSPI_SMR_SCREN; |
| if (!QSPIMEM_ISRANDOM(meminfo->flags)) |
| { |
| /* Disable random value in key */ |
| |
| regval |= QSPI_SMR_RVDIS; |
| } |
| |
| qspi_putreg(priv, 0, SAM_QSPI_SMR_OFFSET); |
| } |
| else |
| { |
| /* Disable the scrambler */ |
| |
| qspi_putreg(priv, 0, SAM_QSPI_SKR_OFFSET); |
| qspi_putreg(priv, 0, SAM_QSPI_SMR_OFFSET); |
| } |
| |
| /* Write Instruction Frame Register: |
| * |
| * QSPI_IFR_WIDTH_? Instruction=single bit/Data depends on |
| * meminfo->flags |
| * QSPI_IFR_INSTEN=1 Instruction Enable |
| * QSPI_IFR_ADDREN=1 Address Enable |
| * QSPI_IFR_OPTEN=0 Option Disable |
| * QSPI_IFR_DATAEN=1 Data Enable |
| * QSPI_IFR_OPTL_* Not used (zero) |
| * QSPI_IFR_ADDRL=0/1 Depends on meminfo->addrlen; |
| * QSPI_IFR_TFRTYP_RD/WRMEM Depends on meminfo->flags |
| * QSPI_IFR_CRM=0 Not continuous read |
| * QSPI_IFR_NBDUM Depends on meminfo->dummies |
| */ |
| |
| regval = QSPI_IFR_INSTEN | QSPI_IFR_ADDREN | QSPI_IFR_DATAEN | |
| QSPI_IFR_NBDUM(meminfo->dummies); |
| |
| if (QSPIMEM_ISWRITE(meminfo->flags)) |
| { |
| regval |= QSPI_IFR_TFRTYP_WRMEM; |
| } |
| else |
| { |
| regval |= QSPI_IFR_TFRTYP_RDMEM; |
| } |
| |
| if (QSPIMEM_ISQUADIO(meminfo->flags)) |
| { |
| regval |= QSPI_IFR_WIDTH_QUADIO; |
| } |
| else if (QSPIMEM_ISDUALIO(meminfo->flags)) |
| { |
| regval |= QSPI_IFR_WIDTH_DUALIO; |
| } |
| else |
| { |
| regval |= QSPI_IFR_WIDTH_SINGLE; |
| } |
| |
| if (meminfo->addrlen == 3) |
| { |
| regval |= QSPI_IFR_ADDRL_24BIT; |
| } |
| else if (meminfo->addrlen == 4) |
| { |
| regval |= QSPI_IFR_ADDRL_32BIT; |
| } |
| else |
| { |
| return -EINVAL; |
| } |
| |
| /* Write the instruction frame value */ |
| |
| qspi_putreg(priv, regval, SAM_QSPI_IFR_OFFSET); |
| qspi_getreg(priv, SAM_QSPI_IFR_OFFSET); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * 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. |
| * |
| * Returned Value: |
| * Zero (OK) on SUCCESS, a negated errno on value of failure |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_SAMA5_QSPI_DMA |
| static int qspi_memory_dma(struct sam_qspidev_s *priv, |
| struct qspi_meminfo_s *meminfo) |
| { |
| uintptr_t qspimem = priv->membase + meminfo->addr; |
| uint32_t dmaflags; |
| int ret; |
| |
| /* Initialize register sampling */ |
| |
| qspi_dma_sampleinit(priv); |
| |
| /* Determine DMA flags and setup the DMA */ |
| |
| dmaflags = DMACH_FLAG_FIFOCFG_LARGEST | DMACH_FLAG_PERIPHAHB_AHB_IF1 | |
| DMACH_FLAG_PERIPHISMEMORY | DMACH_FLAG_PERIPHINCREMENT | |
| DMACH_FLAG_PERIPHCHUNKSIZE_1 | DMACH_FLAG_MEMPID_MAX | |
| DMACH_FLAG_MEMAHB_AHB_IF1 | DMACH_FLAG_MEMINCREMENT | |
| DMACH_FLAG_MEMCHUNKSIZE_1 | DMACH_FLAG_MEMBURST_16; |
| |
| if (QSPIMEM_ISWRITE(meminfo->flags)) |
| { |
| /* Configure TX DMA */ |
| |
| dmaflags |= ((uint32_t)priv->txintf << DMACH_FLAG_PERIPHPID_SHIFT) | |
| DMACH_FLAG_PERIPHWIDTH_8BITS | DMACH_FLAG_MEMWIDTH_8BITS; |
| sam_dmaconfig(priv->dmach, dmaflags); |
| |
| /* Setup the TX DMA (peripheral-to-memory) */ |
| |
| ret = sam_dmatxsetup(priv->dmach, qspimem, (uint32_t)meminfo->buffer, |
| meminfo->buflen); |
| } |
| else |
| { |
| /* Configure RX DMA */ |
| |
| dmaflags |= ((uint32_t)priv->rxintf << DMACH_FLAG_PERIPHPID_SHIFT) | |
| DMACH_FLAG_PERIPHWIDTH_32BITS | DMACH_FLAG_MEMWIDTH_32BITS; |
| sam_dmaconfig(priv->dmach, dmaflags); |
| |
| /* Setup the RX DMA (memory-to-peripheral) */ |
| |
| ret = sam_dmarxsetup(priv->dmach, qspimem, (uint32_t)meminfo->buffer, |
| meminfo->buflen); |
| } |
| |
| if (ret < 0) |
| { |
| spierr("ERROR: DMA setup failed: %d\n", ret); |
| return ret; |
| } |
| |
| qspi_dma_sample(priv, DMA_AFTER_SETUP); |
| |
| /* Enable the memory transfer */ |
| |
| qspi_memory_enable(priv, meminfo); |
| |
| /* Start the DMA */ |
| |
| priv->result = -EBUSY; |
| ret = sam_dmastart(priv->dmach, qspi_dma_callback, priv); |
| if (ret < 0) |
| { |
| spierr("ERROR: sam_dmastart failed: %d\n", ret); |
| return ret; |
| } |
| |
| 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 sam_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_uninterruptible(&priv->dmawait); |
| |
| /* Cancel the watchdog timeout */ |
| |
| wd_cancel(&priv->dmadog); |
| |
| /* Check if we were awakened by an error of some kind. */ |
| |
| if (ret < 0) |
| { |
| DEBUGPANIC(); |
| return ret; |
| } |
| |
| /* Not 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 until the transmission registers are empty. */ |
| |
| while ((qspi_getreg(priv, SAM_QSPI_SR_OFFSET) & QSPI_INT_TXEMPTY) == 0); |
| qspi_putreg(priv, QSPI_CR_LASTXFER, SAM_QSPI_CR_OFFSET); |
| |
| while ((qspi_getreg(priv, SAM_QSPI_SR_OFFSET) & QSPI_SR_INSTRE) == 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). |
| */ |
| |
| sam_dmastop(priv->dmach); |
| |
| /* Complain if the DMA fails */ |
| |
| if (priv->result) |
| { |
| spierr("ERROR: DMA failed with result: %d\n", priv->result); |
| } |
| |
| return priv->result; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: qspi_memory_nodma |
| * |
| * Description: |
| * Perform one QSPI memory transfer without using DMA |
| * |
| * Input Parameters: |
| * priv - 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_nodma(struct sam_qspidev_s *priv, |
| struct qspi_meminfo_s *meminfo) |
| { |
| uintptr_t qspimem = priv->membase + meminfo->addr; |
| |
| /* Enable the memory transfer */ |
| |
| qspi_memory_enable(priv, meminfo); |
| |
| /* Transfer data to/from QSPI memory */ |
| |
| if (QSPIMEM_ISWRITE(meminfo->flags)) |
| { |
| qspi_memcpy((uint8_t *)qspimem, (const uint8_t *)meminfo->buffer, |
| meminfo->buflen); |
| } |
| else |
| { |
| qspi_memcpy((uint8_t *)meminfo->buffer, (const uint8_t *)qspimem, |
| meminfo->buflen); |
| } |
| |
| MEMORY_SYNC(); |
| |
| /* Indicate the end of the transfer as soon as the transmission |
| * registers are empty. |
| */ |
| |
| while ((qspi_getreg(priv, SAM_QSPI_SR_OFFSET) & QSPI_INT_TXEMPTY) == 0); |
| qspi_putreg(priv, QSPI_CR_LASTXFER, SAM_QSPI_CR_OFFSET); |
| |
| /* Wait for the end of the transfer |
| * |
| * REVISIT: If DMA is not used then large transfers could come through |
| * this path. In that case, there would be a benefit to waiting for an |
| * interrupt to signal the end of the transfer. |
| */ |
| |
| while ((qspi_getreg(priv, SAM_QSPI_SR_OFFSET) & QSPI_SR_INSTRE) == 0); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: qspi_memcpy |
| * |
| * Description: |
| * 32-bit version of memcpy. |
| * |
| * Input Parameters: |
| * dest - Destination address of the copy |
| * src - Source address of the copy |
| * buflen - The number of 32-bit words to copy. |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void qspi_memcpy(uint8_t *dest, const uint8_t *src, size_t buflen) |
| { |
| /* The size of the SPI transfer is equal to the bus access width. |
| * 8-bit transfers should result in 8-bit SPI accesses. |
| */ |
| |
| for (; buflen > 0; buflen--) |
| { |
| *dest++ = *src++; |
| } |
| } |
| |
| /**************************************************************************** |
| * 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 sam_qspidev_s *priv = (struct sam_qspidev_s *)dev; |
| int ret; |
| |
| spiinfo("lock=%d\n", lock); |
| if (lock) |
| { |
| 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 sam_qspidev_s *priv = (struct sam_qspidev_s *)dev; |
| uint32_t actual; |
| uint32_t scbr; |
| #if CONFIG_SAMA5_QSPI_DLYBS > 0 |
| uint32_t dlybs; |
| #endif |
| #if CONFIG_SAMA5_QSPI_DLYBCT > 0 |
| uint32_t dlybct; |
| #endif |
| uint32_t regval; |
| |
| spiinfo("frequency=%"PRIu32"\n", frequency); |
| DEBUGASSERT(priv); |
| |
| /* 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 / SCBR, or SCBR = QSPI_CLK / frequency |
| * |
| * Where SCBR can have the range 1 to 256 and the SCR register field holds |
| * SCBR - 1. NOTE that a "ceiling" type of calculation is performed. |
| * 'frequency' is treated as a not-to-exceed value. |
| */ |
| |
| scbr = (frequency + SAM_QSPI_CLOCK - 1) / frequency; |
| |
| /* Make sure that the divider is within range */ |
| |
| if (scbr < 1) |
| { |
| scbr = 1; |
| } |
| else if (scbr > 256) |
| { |
| scbr = 256; |
| } |
| |
| /* Save the new SCBR value (minus one) */ |
| |
| regval = qspi_getreg(priv, SAM_QSPI_SCR_OFFSET); |
| regval &= ~(QSPI_SCR_SCBR_MASK | QSPI_SCR_DLYBS_MASK); |
| regval |= (scbr - 1) << QSPI_SCR_SCBR_SHIFT; |
| |
| /* DLYBS: Delay Before QSCK. This field defines the delay from NPCS valid |
| * to the first valid QSCK transition. When DLYBS equals zero, the NPCS |
| * valid to QSCK transition is 1/2 the QSCK clock period. Otherwise, the |
| * following equations determine the delay: |
| * |
| * Delay Before QSCK = DLYBS / QSPI_CLK |
| * |
| * For a 100 nsec delay (assumes QSPI_CLK is an even multiple of MHz): |
| * |
| * DLYBS == 100 * QSPI_CLK / 1000000000 |
| * == (100 * (QSPI_CLK / 1000000)) / 1000 |
| */ |
| |
| #if CONFIG_SAMA5_QSPI_DLYBS > 0 |
| dlybs = (CONFIG_SAMA5_QSPI_DLYBS * (SAM_QSPI_CLOCK / 1000000)) / 1000; |
| regval |= dlybs << QSPI_SCR_DLYBS_SHIFT; |
| #endif |
| |
| qspi_putreg(priv, regval, SAM_QSPI_SCR_OFFSET); |
| |
| /* DLYBCT: Delay Between Consecutive Transfers. This field defines the |
| * delay between two consecutive transfers with the same peripheral without |
| * removing the chip select. The delay is always inserted after each |
| * transfer and before removing the chip select if needed. |
| * |
| * Delay Between Consecutive Transfers = (32 x DLYBCT) / QSPI_CLK |
| * |
| * For a 500 nsec delay (assumes QSPI_CLK is an even multiple of MHz): |
| * |
| * DLYBCT = 500 * QSPI_CLK / 1000000000 / 32 |
| * = (500 * (QSPI_CLK / 1000000) / 1000 / 32 |
| */ |
| |
| regval = qspi_getreg(priv, SAM_QSPI_MR_OFFSET); |
| regval &= ~QSPI_MR_DLYBCT_MASK; |
| |
| #if CONFIG_SAMA5_QSPI_DLYBCT > 0 |
| dlybct = ((CONFIG_SAMA5_QSPI_DLYBCT * (SAM_QSPI_CLOCK / 1000000)) |
| / 1000 / 32); |
| regval |= dlybct << QSPI_MR_DLYBCT_SHIFT; |
| #endif |
| |
| qspi_putreg(priv, regval, SAM_QSPI_MR_OFFSET); |
| |
| /* Calculate the new actual frequency */ |
| |
| actual = SAM_QSPI_CLOCK / scbr; |
| spiinfo("SCBR=%"PRIu32" actual=%"PRIu32"\n", scbr, actual); |
| |
| /* Save the frequency setting */ |
| |
| priv->frequency = frequency; |
| priv->actual = actual; |
| |
| spiinfo("Frequency %"PRIu32"->%"PRIu32"\n", frequency, actual); |
| return actual; |
| } |
| |
| /**************************************************************************** |
| * Name: qspi_setmode |
| * |
| * Description: |
| * Set the QSPI mode. Optional. See enum qspi_mode_e for mode definitions |
| * |
| * 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 sam_qspidev_s *priv = (struct sam_qspidev_s *)dev; |
| uint32_t regval; |
| |
| 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, SAM_QSPI_SCR_OFFSET); |
| regval &= ~(QSPI_SCR_CPOL | QSPI_SCR_CPHA); |
| |
| switch (mode) |
| { |
| case QSPIDEV_MODE0: /* CPOL=0; CPHA=0 */ |
| break; |
| |
| case QSPIDEV_MODE1: /* CPOL=0; CPHA=1 */ |
| regval |= QSPI_SCR_CPHA; |
| break; |
| |
| case QSPIDEV_MODE2: /* CPOL=1; CPHA=0 */ |
| regval |= QSPI_SCR_CPOL; |
| break; |
| |
| case QSPIDEV_MODE3: /* CPOL=1; CPHA=1 */ |
| regval |= (QSPI_SCR_CPOL | QSPI_SCR_CPHA); |
| break; |
| |
| default: |
| DEBUGASSERT(FALSE); |
| return; |
| } |
| |
| qspi_putreg(priv, regval, SAM_QSPI_SCR_OFFSET); |
| spiinfo("SCR=%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. |
| * |
| * Input Parameters: |
| * dev - Device-specific state data |
| * nbits - The number of bits requested |
| * |
| * Returned Value: |
| * none |
| * |
| ****************************************************************************/ |
| |
| static void qspi_setbits(struct qspi_dev_s *dev, int nbits) |
| { |
| struct sam_qspidev_s *priv = (struct sam_qspidev_s *)dev; |
| uint32_t regval; |
| |
| spiinfo("nbits=%d\n", nbits); |
| DEBUGASSERT(priv != NULL); |
| DEBUGASSERT(nbits >= SAM_QSPI_MINBITS && nbits <= SAM_QSPI_MAXBITS); |
| |
| /* Has the number of bits changed? */ |
| |
| if (nbits != priv->nbits) |
| { |
| /* Yes... Set number of bits appropriately */ |
| |
| regval = qspi_getreg(priv, SAM_QSPI_MR_OFFSET); |
| regval &= ~QSPI_MR_NBBITS_MASK; |
| regval |= QSPI_MR_NBBITS(nbits); |
| qspi_putreg(priv, regval, SAM_QSPI_MR_OFFSET); |
| |
| spiinfo("MR=%08"PRIx32"\n", regval); |
| |
| /* Save the selection so that subsequent re-configurations will be |
| * faster. |
| */ |
| |
| priv->nbits = nbits; |
| } |
| } |
| |
| /**************************************************************************** |
| * 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 sam_qspidev_s *priv = (struct sam_qspidev_s *)dev; |
| uint32_t regval; |
| uint32_t ifr; |
| |
| DEBUGASSERT(priv != 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); |
| |
| /* Write the instruction address register */ |
| |
| ifr = 0; |
| if (QSPICMD_ISADDRESS(cmdinfo->flags)) |
| { |
| DEBUGASSERT(cmdinfo->addrlen == 3 || cmdinfo->addrlen == 4); |
| |
| /* Set the address in the IAR. This is required only if the |
| * instruction frame includes an address, but no data. When data is |
| * preset, the address of the instruction is determined by the address |
| * of QSPI memory accesses, and not by the content of the IAR. |
| */ |
| |
| qspi_putreg(priv, cmdinfo->addr, SAM_QSPI_IAR_OFFSET); |
| |
| /* Set/clear the address enable bit and the address size in the IFR */ |
| |
| ifr |= QSPI_IFR_ADDREN; |
| |
| if (cmdinfo->addrlen == 3) |
| { |
| ifr |= QSPI_IFR_ADDRL_24BIT; |
| } |
| else if (cmdinfo->addrlen == 4) |
| { |
| ifr |= QSPI_IFR_ADDRL_32BIT; |
| } |
| else |
| { |
| return -EINVAL; |
| } |
| } |
| |
| /* Write the Instruction code register: |
| * |
| * QSPI_ICR_INST(cmd) 8-bit command |
| * QSPI_ICR_OPT(0) No option |
| */ |
| |
| regval = QSPI_ICR_INST(cmdinfo->cmd) | QSPI_ICR_OPT(0); |
| qspi_putreg(priv, regval, SAM_QSPI_ICR_OFFSET); |
| |
| /* Does data accompany the command? */ |
| |
| if (QSPICMD_ISDATA(cmdinfo->flags)) |
| { |
| DEBUGASSERT(cmdinfo->buffer != NULL && cmdinfo->buflen > 0); |
| |
| /* Write Instruction Frame Register: |
| * |
| * QSPI_IFR_WIDTH_SINGLE Instruction=single bit/Data single bit |
| * QSPI_IFR_INSTEN=1 Instruction Enable |
| * QSPI_IFR_ADDREN=? (See logic above) |
| * QSPI_IFR_OPTEN=0 Option Disable |
| * QSPI_IFR_DATAEN=1 Data Enable |
| * QSPI_IFR_OPTL_* Not used (zero) |
| * QSPI_IFR_ADDRL=0 Not used (zero) |
| * QSPI_IFR_TFRTYP_WRITE Write transfer into serial memory, OR |
| * QSPI_IFR_TFRTYP_READ Read transfer from serial memory |
| * QSPI_IFR_CRM=0 Not continuous read |
| * QSPI_IFR_NBDUM(0) No dummy cycles |
| */ |
| |
| ifr |= QSPI_IFR_WIDTH_SINGLE | QSPI_IFR_INSTEN | QSPI_IFR_DATAEN | |
| QSPI_IFR_NBDUM(0); |
| |
| if (QSPICMD_ISIDUAL(cmdinfo->flags)) |
| { |
| ifr |= QSPI_IFR_WIDTH_DUALIO; |
| } |
| |
| if (QSPICMD_ISIQUAD(cmdinfo->flags)) |
| { |
| ifr |= QSPI_IFR_WIDTH_QUADIO; |
| } |
| |
| /* Read or write operation? */ |
| |
| if (QSPICMD_ISWRITE(cmdinfo->flags)) |
| { |
| /* Set write data operation |
| * |
| * Write the IFR to the hardware. If the instruction frame |
| * includes data, writing to the IFR does not trigger the |
| * instruction frame transfer. Rather, the instruction frame |
| * is triggered by the first access to QSPI memory. |
| */ |
| |
| ifr |= QSPI_IFR_TFRTYP_WRITE; |
| qspi_putreg(priv, ifr, SAM_QSPI_IFR_OFFSET); |
| |
| /* Read QSPI_IFR (dummy read) to synchronize APB and AHB |
| * accesses. |
| */ |
| |
| qspi_getreg(priv, SAM_QSPI_IFR_OFFSET); |
| |
| /* Copy the data to write to QSPI_RAM */ |
| |
| qspi_memcpy((uint8_t *) priv->membase, |
| (const uint8_t *)cmdinfo->buffer, cmdinfo->buflen); |
| } |
| else |
| { |
| /* Set read data operation |
| * |
| * Write the IFR to the hardware. If the instruction frame |
| * includes data, writing to the IFR does not trigger the |
| * instruction frame transfer. Rather, the instruction frame |
| * is triggered by the first access to QSPI memory. |
| */ |
| |
| ifr |= QSPI_IFR_TFRTYP_READ; |
| qspi_putreg(priv, ifr, SAM_QSPI_IFR_OFFSET); |
| |
| /* Read QSPI_IFR (dummy read) to synchronize APB and AHB |
| * accesses. |
| */ |
| |
| qspi_getreg(priv, SAM_QSPI_IFR_OFFSET); |
| |
| /* Copy the data from QSPI memory into the user buffer */ |
| |
| qspi_memcpy((uint8_t *)cmdinfo->buffer, |
| (const uint8_t *) priv->membase, cmdinfo->buflen); |
| } |
| |
| MEMORY_SYNC(); |
| |
| /* Indicate the end of the transfer as soon as the transmission |
| * registers are empty. |
| */ |
| |
| while ((qspi_getreg(priv, SAM_QSPI_SR_OFFSET) & QSPI_INT_TXEMPTY) |
| == 0); |
| |
| qspi_putreg(priv, QSPI_CR_LASTXFER, SAM_QSPI_CR_OFFSET); |
| |
| /* Fall through to INSTRE wait */ |
| } |
| else |
| { |
| /* Write Instruction Frame Register: |
| * |
| * QSPI_IFR_WIDTH_SINGLE Instruction=single bit/Data single bit |
| * QSPI_IFR_INSTEN=1 Instruction Enable |
| * QSPI_IFR_ADDREN=? (See logic above) |
| * QSPI_IFR_OPTEN=0 Option Disable |
| * QSPI_IFR_DATAEN=0 Data Disable |
| * QSPI_IFR_OPTL_* Not used (zero) |
| * QSPI_IFR_ADDRL=0 Not used (zero) |
| * QSPI_IFR_TFRTYP_READ Shouldn't matter |
| * QSPI_IFR_CRM=0 Not continuous read |
| * QSPI_IFR_NBDUM(0) No dummy cycles |
| */ |
| |
| ifr |= QSPI_IFR_WIDTH_SINGLE | QSPI_IFR_INSTEN | QSPI_IFR_TFRTYP_READ | |
| QSPI_IFR_NBDUM(0); |
| qspi_putreg(priv, ifr, SAM_QSPI_IFR_OFFSET); |
| |
| MEMORY_SYNC(); |
| |
| /* If the instruction frame does not include data, writing to the IFR |
| * triggers sending of the instruction frame. Fall through to INSTRE |
| * wait. |
| */ |
| } |
| |
| /* When the command has been sent, Instruction End Status (INTRE) will be |
| * set in the QSPI status register. |
| */ |
| |
| while ((qspi_getreg(priv, SAM_QSPI_SR_OFFSET) & QSPI_SR_INSTRE) == 0); |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * 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 sam_qspidev_s *priv = (struct sam_qspidev_s *)dev; |
| |
| DEBUGASSERT(priv != NULL && meminfo != NULL); |
| |
| 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/%"PRIu32"\n", |
| meminfo->buffer, meminfo->buflen); |
| |
| #ifdef CONFIG_SAMA5_QSPI_DMA |
| /* Can we perform DMA? Should we perform DMA? */ |
| |
| if (priv->candma && |
| meminfo->buflen > CONFIG_SAMA5_QSPI_DMATHRESHOLD && |
| IS_ALIGNED((uintptr_t)meminfo->buffer) && |
| IS_ALIGNED(meminfo->buflen)) |
| { |
| return qspi_memory_dma(priv, meminfo); |
| } |
| else |
| #endif |
| { |
| return qspi_memory_nodma(priv, meminfo); |
| } |
| } |
| |
| /**************************************************************************** |
| * 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 internal knowledge the kmm_malloc() will return |
| * memory aligned to 64-bit addresses. The buffer length must be large |
| * enough to hold the rested buflen in units a 32-bits. |
| */ |
| |
| return kmm_malloc(ALIGN_UP(buflen)); |
| } |
| |
| /**************************************************************************** |
| * Name: QSPI_FREE |
| * |
| * Description: |
| * Free memory returned by QSPI_ALLOC |
| * |
| * Input Parameters: |
| * dev - Device-specific state data |
| * buffer - Buffer previously allocated via QSPI_ALLOC |
| * |
| * Returned Value: |
| * None. |
| * |
| ****************************************************************************/ |
| |
| static void qspi_free(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 sam_qspidev_s *priv) |
| { |
| uint32_t regval; |
| |
| /* Disable the QSPI */ |
| |
| qspi_putreg(priv, QSPI_CR_QSPIDIS, SAM_QSPI_CR_OFFSET); |
| while ((qspi_getreg(priv, SAM_QSPI_SR_OFFSET) & QSPI_SR_QSPIENS) != 0); |
| |
| /* Reset the QSPI (twice) */ |
| |
| qspi_putreg(priv, QSPI_CR_SWRST, SAM_QSPI_CR_OFFSET); |
| qspi_putreg(priv, QSPI_CR_SWRST, SAM_QSPI_CR_OFFSET); |
| |
| /* Configure the QSPI |
| * |
| * QSPI_MR_SMM - Serial Memory Mode |
| * QSPI_MR_CSMODE_LASTXFER - CS de-asserted when LASTXFER transferred |
| */ |
| |
| regval = QSPI_MR_SMM; |
| qspi_putreg(priv, regval, SAM_QSPI_MR_OFFSET); |
| |
| regval |= QSPI_MR_CSMODE_LASTXFER; |
| qspi_putreg(priv, regval, SAM_QSPI_MR_OFFSET); |
| |
| /* Set up the initial QSPI clock mode: |
| * |
| * Mode 0: CPOL=0; CPHA=0 |
| */ |
| |
| regval = qspi_getreg(priv, SAM_QSPI_SCR_OFFSET); |
| regval &= ~(QSPI_SCR_CPOL | QSPI_SCR_CPHA); |
| qspi_putreg(priv, regval, SAM_QSPI_SCR_OFFSET); |
| |
| regval |= QSPI_SCR_SCBR(1); |
| qspi_putreg(priv, regval, SAM_QSPI_SCR_OFFSET); |
| |
| /* 8-bit mode */ |
| |
| regval = qspi_getreg(priv, SAM_QSPI_MR_OFFSET); |
| regval &= ~QSPI_MR_NBBITS_MASK; |
| regval |= QSPI_MR_NBBITS_8BIT; |
| qspi_putreg(priv, regval, SAM_QSPI_MR_OFFSET); |
| |
| priv->nbits = 8; |
| |
| /* Enable QSPI */ |
| |
| qspi_putreg(priv, QSPI_CR_QSPIEN, SAM_QSPI_CR_OFFSET); |
| while ((qspi_getreg(priv, SAM_QSPI_SR_OFFSET) & QSPI_SR_QSPIENS) == 0); |
| |
| /* Flush any pending transfers */ |
| |
| qspi_getreg(priv, SAM_QSPI_SR_OFFSET); |
| qspi_getreg(priv, SAM_QSPI_RDR_OFFSET); |
| |
| qspi_dumpregs(priv, "After initialization"); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: sam_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 *sam_qspi_initialize(int intf) |
| { |
| struct sam_qspidev_s *priv; |
| int ret; |
| |
| /* The supported SAM parts have only a single QSPI port */ |
| |
| spiinfo("intf: %d\n", intf); |
| DEBUGASSERT(intf >= 0 && intf < SAM_NQSPI); |
| |
| /* Select the QSPI interface */ |
| |
| #ifdef CONFIG_SAMA5_QSPI0 |
| 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 QSPI0 peripheral */ |
| |
| sam_qspi0_enableclk(); |
| |
| /* Configure multiplexed pins as connected on the board. */ |
| |
| sam_configpio(PIO_QSPI0_CS); |
| sam_configpio(PIO_QSPI0_IO0); |
| sam_configpio(PIO_QSPI0_IO1); |
| sam_configpio(PIO_QSPI0_IO2); |
| sam_configpio(PIO_QSPI0_IO3); |
| sam_configpio(PIO_QSPI0_SCK); |
| } |
| else |
| #endif |
| #ifdef CONFIG_SAMA5_QSPI1 |
| if (intf == 1) |
| { |
| /* If this function is called multiple times, the following operations |
| * will be performed multiple times. |
| */ |
| |
| /* Select QSPI1 */ |
| |
| priv = &g_qspi1dev; |
| |
| /* Enable clocking to the QSPI1 peripheral */ |
| |
| sam_qspi1_enableclk(); |
| |
| /* Configure multiplexed pins as connected on the board. */ |
| |
| sam_configpio(PIO_QSPI1_CS); |
| sam_configpio(PIO_QSPI1_IO0); |
| sam_configpio(PIO_QSPI1_IO1); |
| sam_configpio(PIO_QSPI1_IO2); |
| sam_configpio(PIO_QSPI1_IO3); |
| sam_configpio(PIO_QSPI1_SCK); |
| } |
| else |
| #endif |
| { |
| spierr("ERROR: QSPI%d not supported\n", intf); |
| return NULL; |
| } |
| |
| /* Has the QSPI hardware been initialized? */ |
| |
| if (!priv->initialized) |
| { |
| /* No perform one time initialization */ |
| |
| #ifdef CONFIG_SAMA5_QSPI_DMA |
| /* Pre-allocate DMA channels. */ |
| |
| if (priv->candma) |
| { |
| priv->dmach = sam_dmachannel(0, 0); |
| if (!priv->dmach) |
| { |
| spierr("ERROR: Failed to allocate the DMA channel\n"); |
| priv->candma = false; |
| } |
| } |
| #endif |
| |
| #ifdef QSPI_USE_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; |
| #ifdef QSPI_USE_INTERRUPTS |
| up_enable_irq(priv->irq); |
| #endif |
| } |
| |
| return &priv->qspi; |
| |
| errout_with_irq: |
| #ifdef QSPI_USE_INTERRUPTS |
| irq_detach(priv->irq); |
| |
| errout_with_dmach: |
| #endif |
| #ifdef CONFIG_SAMA5_QSPI_DMA |
| if (priv->dmach) |
| { |
| sam_dmafree(priv->dmach); |
| priv->dmach = NULL; |
| } |
| #endif |
| |
| return NULL; |
| } |
| #endif /* CONFIG_SAMA5_QSPI */ |