| /**************************************************************************** |
| * arch/risc-v/src/mpfs/mpfs_coremmc.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 <inttypes.h> |
| #include <stdint.h> |
| #include <stdbool.h> |
| #include <string.h> |
| #include <assert.h> |
| #include <debug.h> |
| #include <errno.h> |
| |
| #include <nuttx/arch.h> |
| #include <nuttx/wdog.h> |
| #include <nuttx/clock.h> |
| #include <nuttx/sdio.h> |
| #include <nuttx/wqueue.h> |
| #include <nuttx/semaphore.h> |
| #include <nuttx/signal.h> |
| #include <nuttx/spinlock.h> |
| #include <nuttx/mmcsd.h> |
| #include <nuttx/irq.h> |
| |
| #include <arch/board/board.h> |
| |
| #include "mpfs_emmcsd.h" |
| #include "riscv_internal.h" |
| #include "hardware/mpfs_coremmc.h" |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| #define MPFS_COREMMC_SR (priv->hw_base + MPFS_COREMMC_SR_OFFSET) |
| #define MPFS_COREMMC_VR (priv->hw_base + MPFS_COREMMC_VR_OFFSET) |
| #define MPFS_COREMMC_MJVR (priv->hw_base + MPFS_COREMMC_MJVR_OFFSET) |
| #define MPFS_COREMMC_MIVR (priv->hw_base + MPFS_COREMMC_MIVR_OFFSET) |
| |
| #define MPFS_COREMMC_CMDX (priv->hw_base + MPFS_COREMMC_CMDX_OFFSET) |
| #define MPFS_COREMMC_ARG1 (priv->hw_base + MPFS_COREMMC_ARG1_OFFSET) |
| #define MPFS_COREMMC_ARG2 (priv->hw_base + MPFS_COREMMC_ARG2_OFFSET) |
| #define MPFS_COREMMC_ARG3 (priv->hw_base + MPFS_COREMMC_ARG3_OFFSET) |
| #define MPFS_COREMMC_ARG4 (priv->hw_base + MPFS_COREMMC_ARG4_OFFSET) |
| |
| #define MPFS_COREMMC_RR0 (priv->hw_base + MPFS_COREMMC_RR0_OFFSET) |
| #define MPFS_COREMMC_RR1 (priv->hw_base + MPFS_COREMMC_RR1_OFFSET) |
| #define MPFS_COREMMC_RR2 (priv->hw_base + MPFS_COREMMC_RR2_OFFSET) |
| #define MPFS_COREMMC_RR3 (priv->hw_base + MPFS_COREMMC_RR3_OFFSET) |
| #define MPFS_COREMMC_RR4 (priv->hw_base + MPFS_COREMMC_RR4_OFFSET) |
| #define MPFS_COREMMC_RR5 (priv->hw_base + MPFS_COREMMC_RR5_OFFSET) |
| #define MPFS_COREMMC_RR6 (priv->hw_base + MPFS_COREMMC_RR6_OFFSET) |
| #define MPFS_COREMMC_RR7 (priv->hw_base + MPFS_COREMMC_RR7_OFFSET) |
| #define MPFS_COREMMC_RR8 (priv->hw_base + MPFS_COREMMC_RR8_OFFSET) |
| #define MPFS_COREMMC_RR9 (priv->hw_base + MPFS_COREMMC_RR9_OFFSET) |
| #define MPFS_COREMMC_RR10 (priv->hw_base + MPFS_COREMMC_RR10_OFFSET) |
| #define MPFS_COREMMC_RR11 (priv->hw_base + MPFS_COREMMC_RR11_OFFSET) |
| #define MPFS_COREMMC_RR12 (priv->hw_base + MPFS_COREMMC_RR12_OFFSET) |
| #define MPFS_COREMMC_RR13 (priv->hw_base + MPFS_COREMMC_RR13_OFFSET) |
| #define MPFS_COREMMC_RR14 (priv->hw_base + MPFS_COREMMC_RR14_OFFSET) |
| #define MPFS_COREMMC_RR15 (priv->hw_base + MPFS_COREMMC_RR15_OFFSET) |
| |
| #define MPFS_COREMMC_WDR (priv->hw_base + MPFS_COREMMC_WDR_OFFSET) |
| #define MPFS_COREMMC_RDR (priv->hw_base + MPFS_COREMMC_RDR_OFFSET) |
| #define MPFS_COREMMC_IMR (priv->hw_base + MPFS_COREMMC_IMR_OFFSET) |
| #define MPFS_COREMMC_SBIMR (priv->hw_base + MPFS_COREMMC_SBIMR_OFFSET) |
| #define MPFS_COREMMC_MBIMR (priv->hw_base + MPFS_COREMMC_MBIMR_OFFSET) |
| #define MPFS_COREMMC_ISR (priv->hw_base + MPFS_COREMMC_ISR_OFFSET) |
| #define MPFS_COREMMC_SBISR (priv->hw_base + MPFS_COREMMC_SBISR_OFFSET) |
| #define MPFS_COREMMC_MBISR (priv->hw_base + MPFS_COREMMC_MBISR_OFFSET) |
| #define MPFS_COREMMC_ICR (priv->hw_base + MPFS_COREMMC_ICR_OFFSET) |
| #define MPFS_COREMMC_SBICR (priv->hw_base + MPFS_COREMMC_SBICR_OFFSET) |
| #define MPFS_COREMMC_MBICR (priv->hw_base + MPFS_COREMMC_MBICR_OFFSET) |
| #define MPFS_COREMMC_CTRL (priv->hw_base + MPFS_COREMMC_CTRL_OFFSET) |
| #define MPFS_COREMMC_SBCSR (priv->hw_base + MPFS_COREMMC_SBCSR_OFFSET) |
| #define MPFS_COREMMC_MBCSR (priv->hw_base + MPFS_COREMMC_MBCSR_OFFSET) |
| |
| #define MPFS_COREMMC_RSPTO (priv->hw_base + MPFS_COREMMC_RSPTO_OFFSET) |
| #define MPFS_COREMMC_DATATO (priv->hw_base + MPFS_COREMMC_DATATO_OFFSET) |
| #define MPFS_COREMMC_BLR (priv->hw_base + MPFS_COREMMC_BLR_OFFSET) |
| #define MPFS_COREMMC_DCTRL (priv->hw_base + MPFS_COREMMC_DCTRL_OFFSET) |
| #define MPFS_COREMMC_CLKR (priv->hw_base + MPFS_COREMMC_CLKR_OFFSET) |
| #define MPFS_COREMMC_BCR (priv->hw_base + MPFS_COREMMC_BCR_OFFSET) |
| |
| /* Clocks and reset */ |
| |
| #define MPFS_SYSREG_SOFT_RESET_CR (MPFS_SYSREG_BASE + \ |
| MPFS_SYSREG_SOFT_RESET_CR_OFFSET) |
| |
| #define MPFS_SYSREG_SUBBLK_CLOCK_CR (MPFS_SYSREG_BASE + \ |
| MPFS_SYSREG_SUBBLK_CLOCK_CR_OFFSET) |
| |
| #ifndef CONFIG_SCHED_WORKQUEUE |
| # error "Callback support requires CONFIG_SCHED_WORKQUEUE" |
| #endif |
| |
| #ifndef CONFIG_SDIO_BLOCKSETUP |
| # error "This requires CONFIG_SDIO_BLOCKSETUP" |
| #endif |
| |
| /* Clocks and timing */ |
| |
| #define MPFS_FPGA_FIC0_CLK (50000000) |
| |
| #define COREMMC_CMDTIMEOUT (100000) |
| #define COREMMC_LONGTIMEOUT (100000000) |
| #define COREMMC_DATA_TIMEOUT (500000) |
| |
| #define MPFS_MMC_CLOCK_50MHZ (50000000) |
| #define MPFS_MMC_CLOCK_400KHZ (400000) |
| |
| /* Event wait interrupt status and clear bits */ |
| |
| #define COREMMC_ALL_ICR (COREMMC_ICR_CLRCSI | \ |
| COREMMC_ICR_CLRRRI | \ |
| COREMMC_ICR_ERROR) |
| |
| /* Event wait interrupt mask bits */ |
| |
| #define COREMMC_RECV_MASK (COREMMC_IMR_ERROR) |
| |
| #define COREMMC_RECV_SB_MASK (COREMMC_SBIMR_ERROR | \ |
| COREMMC_SBIMR_RDONE) |
| |
| #define COREMMC_RECV_MB_MASK (COREMMC_MBIMR_ERROR | \ |
| COREMMC_MBIMR_RDONE) |
| |
| #define COREMMC_SEND_MASK (COREMMC_IMR_ERROR) |
| |
| #define COREMMC_SEND_SB_MASK (COREMMC_SBIMR_ERROR | \ |
| COREMMC_SBIMR_WDONE) |
| |
| #define COREMMC_SEND_MB_MASK (COREMMC_MBIMR_ERROR | \ |
| COREMMC_MBIMR_WDONE) |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| /* This structure defines the state of the MPFS eMMCSD interface */ |
| |
| struct mpfs_dev_s |
| { |
| struct sdio_dev_s dev; /* Standard, base SDIO interface */ |
| |
| const uintptr_t hw_base; /* Base address */ |
| const int plic_irq; /* PLIC interrupt */ |
| bool clk_enabled; /* Clk state */ |
| |
| /* Event support */ |
| |
| sem_t waitsem; /* Implements event waiting */ |
| sdio_eventset_t waitevents; /* Set of events to be waited for */ |
| uint32_t waitmask; /* Interrupt enables for event waiting */ |
| volatile sdio_eventset_t wkupevent; /* The event that caused the wakeup */ |
| struct wdog_s waitwdog; /* Watchdog that handles event timeouts */ |
| |
| /* Callback support */ |
| |
| sdio_statset_t cdstatus; /* Card status */ |
| sdio_eventset_t cbevents; /* Set of events to be cause callbacks */ |
| worker_t callback; /* Registered callback function */ |
| void *cbarg; /* Registered callback argument */ |
| struct work_s cbwork; /* Callback work queue structure */ |
| |
| /* Interrupt mode data transfer support */ |
| |
| uint32_t *buffer; /* Address of current R/W buffer */ |
| size_t remaining; /* Number of bytes remaining in the transfer */ |
| size_t receivecnt; /* Real count to receive */ |
| uint32_t xfrmask; /* Interrupt enables for data transfer */ |
| uint32_t xfr_blkmask; /* Interrupt enables for SB/MB data transfer */ |
| |
| bool widebus; /* Required for DMA support */ |
| bool onebit; /* true: Only 1-bit transfers are supported */ |
| |
| /* Data transfer support */ |
| |
| bool polltransfer; /* Indicate a poll transfer, no DMA */ |
| bool multiblock; /* Indicate a multi-block transfer */ |
| |
| /* Misc */ |
| |
| uint32_t blocksize; /* Current block size */ |
| uint32_t fifo_depth; /* Fifo size, read from the register */ |
| }; |
| |
| union |
| { |
| uint32_t w; |
| uint8_t b[4]; |
| } data; |
| |
| /**************************************************************************** |
| * Private Function Prototypes |
| ****************************************************************************/ |
| |
| /* Initialization/setup */ |
| |
| static void mpfs_reset(struct sdio_dev_s *dev); |
| static sdio_capset_t mpfs_capabilities(struct sdio_dev_s *dev); |
| static sdio_statset_t mpfs_status(struct sdio_dev_s *dev); |
| static void mpfs_widebus(struct sdio_dev_s *dev, bool enable); |
| static void mpfs_clock(struct sdio_dev_s *dev, enum sdio_clock_e rate); |
| static int mpfs_attach(struct sdio_dev_s *dev); |
| |
| /* Command / Status / Data Transfer */ |
| |
| static int mpfs_sendcmd(struct sdio_dev_s *dev, uint32_t cmd, uint32_t arg); |
| static void mpfs_blocksetup(struct sdio_dev_s *dev, unsigned int blocksize, |
| unsigned int nblocks); |
| static int mpfs_recvsetup(struct sdio_dev_s *dev, uint8_t *buffer, |
| size_t nbytes); |
| static int mpfs_sendsetup(struct sdio_dev_s *dev, const uint8_t *buffer, |
| size_t nbytes); |
| static int mpfs_cancel(struct sdio_dev_s *dev); |
| static int mpfs_waitresponse(struct sdio_dev_s *dev, uint32_t cmd); |
| static int mpfs_recvshortcrc(struct sdio_dev_s *dev, uint32_t cmd, |
| uint32_t *rshort); |
| static int mpfs_recvlong(struct sdio_dev_s *dev, uint32_t cmd, |
| uint32_t rlong[4]); |
| static int mpfs_recvshort(struct sdio_dev_s *dev, uint32_t cmd, |
| uint32_t *rshort); |
| |
| /* Event handler */ |
| |
| static void mpfs_waitenable(struct sdio_dev_s *dev, |
| sdio_eventset_t eventset, uint32_t timeout); |
| static sdio_eventset_t mpfs_eventwait(struct sdio_dev_s *dev); |
| static void mpfs_callbackenable(struct sdio_dev_s *dev, |
| sdio_eventset_t eventset); |
| static int mpfs_registercallback(struct sdio_dev_s *dev, |
| worker_t callback, void *arg); |
| static void mpfs_callback(void *arg); |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| struct mpfs_dev_s g_coremmc_dev = |
| { |
| .dev = |
| { |
| .reset = mpfs_reset, |
| .capabilities = mpfs_capabilities, |
| .status = mpfs_status, |
| .widebus = mpfs_widebus, |
| .clock = mpfs_clock, |
| .attach = mpfs_attach, |
| .sendcmd = mpfs_sendcmd, |
| .blocksetup = mpfs_blocksetup, |
| .recvsetup = mpfs_recvsetup, |
| .sendsetup = mpfs_sendsetup, |
| .cancel = mpfs_cancel, |
| .waitresponse = mpfs_waitresponse, |
| .recv_r1 = mpfs_recvshortcrc, |
| .recv_r2 = mpfs_recvlong, |
| .recv_r3 = mpfs_recvshort, |
| .recv_r4 = mpfs_recvshort, |
| .recv_r5 = mpfs_recvshortcrc, |
| .recv_r6 = mpfs_recvshortcrc, |
| .recv_r7 = mpfs_recvshort, |
| .waitenable = mpfs_waitenable, |
| .eventwait = mpfs_eventwait, |
| .callbackenable = mpfs_callbackenable, |
| .registercallback = mpfs_registercallback, |
| }, |
| .hw_base = CONFIG_MPFS_COREMMC_BASE, |
| .plic_irq = MPFS_IRQ_FABRIC_F2H_0 + CONFIG_MPFS_COREMMC_IRQNUM, |
| .blocksize = 512, |
| .fifo_depth = 0, |
| .onebit = false, |
| .polltransfer = true, |
| .multiblock = false, |
| .waitsem = SEM_INITIALIZER(0), |
| }; |
| |
| /* Not all requests are 32-bit aligned, use a spare buffer workaround */ |
| |
| static uint32_t g_aligned_buffer[512 / 4]; |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: mpfs_modifyreg8 |
| * |
| * Description: |
| * Atomically modify the specified bits in a memory mapped register |
| * |
| * Input Parameters: |
| * addr - address to use |
| * clearbits - bit clear mask |
| * setbits - set bit mask |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void mpfs_modifyreg8(uintptr_t addr, uint8_t clearbits, |
| uint8_t setbits) |
| { |
| irqstate_t flags; |
| uint8_t regval; |
| |
| flags = spin_lock_irqsave(NULL); |
| regval = getreg8(addr); |
| regval &= ~clearbits; |
| regval |= setbits; |
| putreg8(regval, addr); |
| spin_unlock_irqrestore(NULL, flags); |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_putreg32 |
| * |
| * Description: |
| * Set the contents of a 32-bit register. This helper wrapper checks |
| * the range before accessing the address which prevents thinkos. |
| * |
| * Input Parameters: |
| * regval - Value to store |
| * regaddr - Address where the regval is stored |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static inline void mpfs_putreg32(struct mpfs_dev_s *priv, uint32_t regval, |
| uintptr_t regaddr) |
| { |
| DEBUGASSERT((regaddr >= priv->hw_base) && regaddr < (priv->hw_base + |
| MPFS_COREMMC_MAX_OFFSET)); |
| |
| putreg32(regval, regaddr); |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_putreg8 |
| * |
| * Description: |
| * Set the contents of an 8-bit register. This helper wrapper checks |
| * the range before accessing the address which prevents thinkos. |
| * |
| * Input Parameters: |
| * regval - Value to store |
| * regaddr - Address where the regval is stored |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static inline void mpfs_putreg8(struct mpfs_dev_s *priv, uint8_t regval, |
| uintptr_t regaddr) |
| { |
| DEBUGASSERT((regaddr >= priv->hw_base) && regaddr < (priv->hw_base + |
| MPFS_COREMMC_MAX_OFFSET)); |
| |
| putreg8(regval, regaddr); |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_reset_lines |
| * |
| * Description: |
| * Resets the DAT and CMD lines. |
| * |
| * Input Parameters: |
| * priv - Instance of the CoreMMC private state structure. |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void mpfs_reset_lines(struct mpfs_dev_s *priv) |
| { |
| mpfs_modifyreg8(MPFS_COREMMC_CTRL, 0, COREMMC_CTRL_FIFORESET); |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_check_lines_busy |
| * |
| * Description: |
| * Verifies the DAT and CMD lines are available, not busy. |
| * |
| * Input Parameters: |
| * priv - Instance of the CoreMMC private state structure. |
| * |
| * Returned Value: |
| * true if busy, false if available |
| * |
| ****************************************************************************/ |
| |
| static bool mpfs_check_lines_busy(struct mpfs_dev_s *priv) |
| { |
| uint32_t retries = COREMMC_LONGTIMEOUT; |
| uint8_t ctrl; |
| |
| do |
| { |
| ctrl = getreg8(MPFS_COREMMC_CTRL); |
| } |
| while ((ctrl & COREMMC_CTRL_BUSY) && --retries); |
| |
| if (retries == 0) |
| { |
| mcerr("Lines are still busy!\n"); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_setclkrate |
| * |
| * Description: |
| * Set the clock rate. Disables the SD clock for the time changing the |
| * settings, if already enabled. |
| * |
| * Input Parameters: |
| * priv - Instance of the CoreMMC private state structure. |
| * clkcr - New clock rate. |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void mpfs_setclkrate(struct mpfs_dev_s *priv, uint32_t clkrate) |
| { |
| uint8_t divider; |
| |
| mcinfo("clkrate: %08" PRIx32 "\n", clkrate); |
| |
| if (clkrate == 0) |
| { |
| mpfs_modifyreg8(MPFS_COREMMC_CTRL, COREMMC_CTRL_CLKOE, 0); |
| priv->clk_enabled = false; |
| return; |
| } |
| |
| /* Disable clock if enabled */ |
| |
| if (priv->clk_enabled) |
| { |
| mpfs_modifyreg8(MPFS_COREMMC_CTRL, COREMMC_CTRL_CLKOE, 0); |
| } |
| |
| /* Datasheet has misleading clk equation, correct is: |
| * Fclk = Hclk / (2 * (CLKHP - 1)), CLKHP = 50000000 / (Fclk * 2) + 1 |
| * except for the higher rates. |
| */ |
| |
| divider = MPFS_FPGA_FIC0_CLK / (clkrate * 2) + 1; |
| |
| mcinfo("divider: %02" PRIx8 "\n", divider); |
| |
| mpfs_modifyreg8(MPFS_COREMMC_CLKR, 0xff, divider); |
| |
| /* Apply new settings */ |
| |
| priv->clk_enabled = true; |
| mpfs_modifyreg8(MPFS_COREMMC_CTRL, 0, COREMMC_CTRL_CLKOE); |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_configwaitints |
| * |
| * Description: |
| * Enable/disable SDIO interrupts needed to support the wait function |
| * |
| * Input Parameters: |
| * priv - Instance of the CoreMMC private state structure. |
| * waitmask - The set of bits in the SDIO MASK register to set |
| * waitevents - Waited for events |
| * wkupevent - Wake-up events |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void mpfs_configwaitints(struct mpfs_dev_s *priv, uint32_t waitmask, |
| sdio_eventset_t waitevents, |
| sdio_eventset_t wkupevent) |
| { |
| irqstate_t flags; |
| |
| /* Save all of the data and set the new interrupt mask in one, atomic |
| * operation. |
| */ |
| |
| flags = enter_critical_section(); |
| |
| priv->waitevents = waitevents; |
| priv->wkupevent = wkupevent; |
| priv->waitmask = waitmask; |
| |
| leave_critical_section(flags); |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_configxfrints |
| * |
| * Description: |
| * Enable SDIO interrupts needed to support the data transfer event |
| * |
| * Input Parameters: |
| * priv - Instance of the CoreMMC private state structure |
| * xfrmask - The set of bits in the IMR register to set |
| * xfr_blkmask - The set of bits in the SB/MB IMR register to set |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void mpfs_configxfrints(struct mpfs_dev_s *priv, uint32_t xfrmask, |
| uint32_t xfr_blkmask) |
| { |
| irqstate_t flags; |
| |
| flags = enter_critical_section(); |
| priv->xfrmask = xfrmask; |
| priv->xfr_blkmask = xfr_blkmask; |
| |
| mcinfo("Mask: %08" PRIx32 "\n", priv->xfrmask | priv->waitmask); |
| mcinfo("blkmask: %08" PRIx32 "\n", priv->xfr_blkmask); |
| |
| mpfs_putreg8(priv, priv->xfrmask | priv->waitmask, MPFS_COREMMC_IMR); |
| |
| if (priv->multiblock) |
| { |
| mpfs_putreg8(priv, priv->xfr_blkmask, MPFS_COREMMC_MBIMR); |
| } |
| else |
| { |
| mpfs_putreg8(priv, priv->xfr_blkmask, MPFS_COREMMC_SBIMR); |
| } |
| |
| leave_critical_section(flags); |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_sendfifo |
| * |
| * Description: |
| * Send single block or multiblock data in interrupt mode |
| * |
| * Input Parameters: |
| * priv - Instance of the CoreMMC private state structure. |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void mpfs_sendfifo(struct mpfs_dev_s *priv) |
| { |
| /* Loop while there is more data to be sent and the TX FIFO is not full */ |
| |
| while (priv->remaining > 0) |
| { |
| /* Is there a full word remaining in the user buffer? */ |
| |
| if (priv->remaining >= sizeof(uint32_t)) |
| { |
| /* Yes, transfer the word to the TX FIFO */ |
| |
| data.w = *priv->buffer++; |
| priv->remaining -= sizeof(uint32_t); |
| } |
| else |
| { |
| /* No.. transfer just the bytes remaining in the user buffer, |
| * padding with zero as necessary to extend to a full word. |
| */ |
| |
| uint8_t *ptr = (uint8_t *)priv->remaining; |
| size_t i; |
| |
| data.w = 0; |
| for (i = 0; i < priv->remaining; i++) |
| { |
| data.b[i] = *ptr++; |
| } |
| |
| /* Now the transfer is finished */ |
| |
| priv->remaining = 0; |
| } |
| |
| /* Put the word in the FIFO - needs to be 32-bit write */ |
| |
| mpfs_putreg32(priv, data.w, MPFS_COREMMC_WDR); |
| } |
| |
| mcinfo("Wrote all\n"); |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_recvfifo |
| * |
| * Description: |
| * Receive mmc data in single block or multiblock interrupt mode |
| * |
| * Input Parameters: |
| * priv - Instance of the mmc private state structure. |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void mpfs_recvfifo(struct mpfs_dev_s *priv) |
| { |
| mcinfo("Reading: %lu bytes\n", priv->remaining); |
| |
| /* Loop while there is space to store the data and there is more |
| * data available in the RX FIFO. |
| */ |
| |
| while (priv->remaining > 0) |
| { |
| /* Read the next word from the RX FIFO */ |
| |
| data.w = getreg32(MPFS_COREMMC_RDR); |
| |
| if (priv->remaining >= sizeof(uint32_t)) |
| { |
| /* Transfer the whole word to the user buffer */ |
| |
| *priv->buffer++ = data.w; |
| priv->remaining -= sizeof(uint32_t); |
| } |
| else |
| { |
| /* Transfer any trailing fractional word */ |
| |
| uint8_t *ptr = (uint8_t *)priv->buffer; |
| size_t i; |
| |
| for (i = 0; i < priv->remaining; i++) |
| { |
| *ptr++ = data.b[i]; |
| } |
| |
| /* Now the transfer is finished */ |
| |
| priv->remaining = 0; |
| } |
| } |
| |
| mcinfo("Read all\n"); |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_endwait |
| * |
| * Description: |
| * Wake up a waiting thread if the waited-for event has occurred. |
| * |
| * Input Parameters: |
| * priv - Instance of the CoreMMC private state structure. |
| * wkupevent - The event that caused the wait to end |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * Always called from the interrupt level with interrupts disabled. |
| * |
| ****************************************************************************/ |
| |
| static void mpfs_endwait(struct mpfs_dev_s *priv, |
| sdio_eventset_t wkupevent) |
| { |
| mcinfo("wkupevent: %u\n", wkupevent); |
| |
| /* Cancel the watchdog timeout */ |
| |
| wd_cancel(&priv->waitwdog); |
| |
| /* Disable event-related interrupts */ |
| |
| mpfs_configwaitints(priv, 0, 0, wkupevent); |
| |
| /* Wake up the waiting thread */ |
| |
| nxsem_post(&priv->waitsem); |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_eventtimeout |
| * |
| * Description: |
| * The watchdog timeout setup when the event wait start has expired without |
| * any other waited-for event occurring. |
| * |
| * Input Parameters: |
| * arg - The argument |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * Always called from the interrupt level with interrupts disabled. |
| * |
| ****************************************************************************/ |
| |
| static void mpfs_eventtimeout(wdparm_t arg) |
| { |
| struct mpfs_dev_s *priv = (struct mpfs_dev_s *)arg; |
| |
| /* There is always race conditions with timer expirations. */ |
| |
| DEBUGASSERT((priv->waitevents & SDIOWAIT_TIMEOUT) != 0 || |
| priv->wkupevent != 0); |
| |
| mcinfo("sta: %08" PRIx32 " enabled irq: %08" PRIx32 "\n", |
| getreg8(MPFS_COREMMC_ISR), |
| getreg8(MPFS_COREMMC_IMR)); |
| |
| /* Is a data transfer complete event expected? */ |
| |
| if ((priv->waitevents & SDIOWAIT_TIMEOUT) != 0) |
| { |
| /* Yes.. wake up any waiting threads */ |
| |
| mpfs_endwait(priv, SDIOWAIT_TIMEOUT); |
| mcerr("Timeout: remaining: %lu\n", priv->remaining); |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_endtransfer |
| * |
| * Description: |
| * Terminate a transfer with the provided status. This function is called |
| * only from the interrupt handler when end-of-transfer conditions are |
| * detected. |
| * |
| * Input Parameters: |
| * priv - Instance of the CoreMMC private state structure. |
| * wkupevent - The event that caused the transfer to end |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * Always called from the interrupt level with interrupts disabled. |
| * |
| ****************************************************************************/ |
| |
| static void mpfs_endtransfer(struct mpfs_dev_s *priv, |
| sdio_eventset_t wkupevent) |
| { |
| /* Disable all transfer related interrupts */ |
| |
| mpfs_configxfrints(priv, 0, 0); |
| |
| /* If there were errors, reset lines */ |
| |
| if ((wkupevent & (~SDIOWAIT_TRANSFERDONE)) != 0) |
| { |
| mpfs_reset_lines(priv); |
| } |
| |
| /* Clear Read Done (RDONE), Write Done (WDONE) */ |
| |
| if (priv->multiblock) |
| { |
| mpfs_putreg8(priv, COREMMC_MBICR_RDONE | COREMMC_MBICR_WDONE, |
| MPFS_COREMMC_MBICR); |
| } |
| else |
| { |
| mpfs_putreg8(priv, COREMMC_SBICR_RDONE | COREMMC_SBICR_WDONE, |
| MPFS_COREMMC_SBICR); |
| } |
| |
| /* Mark the transfer finished */ |
| |
| priv->remaining = 0; |
| |
| /* Is a thread wait for these data transfer complete events? */ |
| |
| if ((priv->waitevents & wkupevent) != 0) |
| { |
| /* Yes.. wake up any waiting threads */ |
| |
| mpfs_endwait(priv, wkupevent); |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_coremmc_interrupt |
| * |
| * Description: |
| * coremmc interrupt handler |
| * |
| * Input Parameters: |
| * priv - Instance of the coremmc private state structure. |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static int mpfs_coremmc_interrupt(int irq, void *context, void *arg) |
| { |
| struct mpfs_dev_s *priv = (struct mpfs_dev_s *)arg; |
| uint8_t status; |
| uint8_t status_xb; |
| |
| DEBUGASSERT(priv != NULL); |
| |
| status = getreg8(MPFS_COREMMC_ISR); |
| |
| if (priv->multiblock) |
| { |
| status_xb = getreg8(MPFS_COREMMC_MBISR); |
| } |
| else |
| { |
| status_xb = getreg8(MPFS_COREMMC_SBISR); |
| } |
| |
| mcinfo("status: %02" PRIx8 " mb: %02" PRIx8 "\n", status, status_xb); |
| |
| /* Check for errors first */ |
| |
| if (status & COREMMC_ISR_ERROR) |
| { |
| mcerr("ISR ERROR: %08" PRIx8 "\n", status); |
| mpfs_endtransfer(priv, SDIOWAIT_TRANSFERDONE | SDIOWAIT_ERROR); |
| return OK; |
| } |
| |
| if (status_xb & COREMMC_XBISR_ERROR) |
| { |
| mcerr("XBISR ERROR: %08" PRIx8 "\n", status_xb); |
| mpfs_endtransfer(priv, SDIOWAIT_TRANSFERDONE | SDIOWAIT_ERROR); |
| return OK; |
| } |
| |
| /* Check single / multiple block activities */ |
| |
| if (status_xb) |
| { |
| if (status_xb & COREMMC_XBISR_RDONE) |
| { |
| mpfs_recvfifo(priv); |
| if (priv->multiblock) |
| { |
| mpfs_putreg8(priv, COREMMC_MBICR_RDONE, MPFS_COREMMC_MBICR); |
| } |
| else |
| { |
| mpfs_putreg8(priv, COREMMC_SBICR_RDONE, MPFS_COREMMC_SBICR); |
| } |
| |
| if (priv->remaining == 0) |
| { |
| mpfs_endtransfer(priv, SDIOWAIT_TRANSFERDONE); |
| } |
| } |
| |
| if (status_xb & COREMMC_XBISR_WDONE) |
| { |
| if (priv->multiblock) |
| { |
| mpfs_putreg8(priv, COREMMC_MBICR_WDONE, MPFS_COREMMC_MBICR); |
| } |
| else |
| { |
| mpfs_putreg8(priv, COREMMC_SBICR_WDONE, MPFS_COREMMC_SBICR); |
| } |
| |
| mpfs_endtransfer(priv, SDIOWAIT_TRANSFERDONE); |
| } |
| } |
| |
| mcinfo("done\n"); |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_lock |
| * |
| * Description: |
| * Locks the bus. Function calls low-level multiplexed bus routines to |
| * resolve bus requests and acknowledgment issues. |
| * |
| * Input Parameters: |
| * dev - An instance of the SDIO device interface |
| * lock - TRUE to lock, FALSE to unlock. |
| * |
| * Returned Value: |
| * OK on success; a negated errno on failure |
| * |
| ****************************************************************************/ |
| |
| #if defined(CONFIG_SDIO_MUXBUS) |
| static int mpfs_lock(struct sdio_dev_s *dev, bool lock) |
| { |
| /* The multiplex bus is part of board support package. */ |
| |
| mpfs_muxbus_sdio_lock(dev, lock); |
| |
| return OK; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: mpfs_set_data_timeout |
| * |
| * Description: |
| * Sets the cycles for determining the data line timeout. |
| * |
| * Input Parameters: |
| * priv - Instance of the CoreMMC private state structure. |
| * timeout_us - Requested timeout in microseconds |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void mpfs_set_data_timeout(struct mpfs_dev_s *priv, |
| uint32_t timeout_us) |
| { |
| uint32_t timeout; |
| |
| /* MPFS_FPGA_FIC0_CLK = 125 Mhz: |
| * uS = 0.000001 s, clk tick = 1 / 125 MHz = 0.008 uS |
| */ |
| |
| timeout = (MPFS_FPGA_FIC0_CLK / USEC_PER_SEC) * timeout_us; |
| |
| mpfs_putreg32(priv, timeout, MPFS_COREMMC_DATATO); |
| |
| /* Response timeout */ |
| |
| mpfs_putreg8(priv, 0xc0, MPFS_COREMMC_RSPTO); |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_device_reset |
| * |
| * Description: |
| * Reset the SDIO controller. Undo all setup and initialization. |
| * |
| * Input Parameters: |
| * dev - An instance of the SDIO device interface |
| * |
| * Returned Value: |
| * true on success, false otherwise |
| * |
| ****************************************************************************/ |
| |
| static bool mpfs_device_reset(struct sdio_dev_s *dev) |
| { |
| struct mpfs_dev_s *priv = (struct mpfs_dev_s *)dev; |
| uint8_t fifo_size; |
| bool retval = true; |
| |
| /* CoreMMC uses FIC0 clock at FPGA. Don't perform resets on it as |
| * many other drivers may be using the same CLK provider. Just |
| * make sure the FPGA is out of reset and clock is on. |
| */ |
| |
| modifyreg32(MPFS_SYSREG_SOFT_RESET_CR, SYSREG_SOFT_RESET_CR_FPGA | |
| SYSREG_SOFT_RESET_CR_FIC0, 0); |
| |
| modifyreg32(MPFS_SYSREG_SUBBLK_CLOCK_CR, 0, SYSREG_SUBBLK_CLOCK_CR_FIC0); |
| |
| nxsig_usleep(100); |
| |
| /* Perform module and slave reset */ |
| |
| mpfs_modifyreg8(MPFS_COREMMC_CTRL, 0, COREMMC_CTRL_SLRST | |
| COREMMC_CTRL_SWRST); |
| |
| nxsig_usleep(100); |
| |
| mpfs_modifyreg8(MPFS_COREMMC_CTRL, COREMMC_CTRL_SLRST | COREMMC_CTRL_SWRST, |
| 0); |
| |
| nxsig_usleep(100); |
| |
| /* Clear interrupt status and disable interrupts */ |
| |
| mpfs_putreg8(priv, COREMMC_ALL_ICR, MPFS_COREMMC_ICR); |
| mpfs_putreg8(priv, 0, MPFS_COREMMC_IMR); |
| |
| mpfs_set_data_timeout(priv, COREMMC_DATA_TIMEOUT); |
| |
| /* Set 1-bit bus mode */ |
| |
| mpfs_modifyreg8(MPFS_COREMMC_DCTRL, COREMMC_DCTRL_DSIZE, |
| COREMMC_DCTRL_DSIZE_1BIT); |
| |
| mpfs_setclkrate(priv, MPFS_MMC_CLOCK_400KHZ); |
| |
| nxsig_usleep(100); |
| |
| /* Store fifo size for later to check no fifo overruns occur */ |
| |
| fifo_size = ((getreg8(MPFS_COREMMC_VR) >> 2) & 0x3); |
| if (fifo_size == 0) |
| { |
| priv->fifo_depth = 512; |
| } |
| else if (fifo_size == 1) |
| { |
| priv->fifo_depth = 1024 * 4; |
| } |
| else if (fifo_size == 2) |
| { |
| priv->fifo_depth = 1024 * 16; |
| } |
| else |
| { |
| priv->fifo_depth = 1024 * 32; |
| } |
| |
| /* Reset data */ |
| |
| priv->waitevents = 0; /* Set of events to be waited for */ |
| priv->waitmask = 0; /* Interrupt enables for event waiting */ |
| priv->wkupevent = 0; /* The event that caused the wakeup */ |
| |
| wd_cancel(&priv->waitwdog); /* Cancel any timeouts */ |
| |
| /* Interrupt mode data transfer support */ |
| |
| priv->buffer = 0; /* Address of current R/W buffer */ |
| priv->remaining = 0; /* Number of bytes remaining in the transfer */ |
| priv->xfrmask = 0; /* Interrupt enables for data transfer */ |
| |
| priv->widebus = false; |
| |
| mpfs_reset_lines(priv); |
| |
| return retval; |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_reset |
| * |
| * Description: |
| * Reset the SDIO controller via mpfs_device_reset. This is a wrapper |
| * function only. |
| * |
| * Input Parameters: |
| * dev - An instance of the SDIO device interface |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void mpfs_reset(struct sdio_dev_s *dev) |
| { |
| mpfs_device_reset(dev); |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_capabilities |
| * |
| * Description: |
| * Get capabilities (and limitations) of the SDIO driver (optional) |
| * |
| * Input Parameters: |
| * dev - Device-specific state data |
| * |
| * Returned Value: |
| * Returns a bitset of status values (see SDIO_CAPS_* defines) |
| * |
| ****************************************************************************/ |
| |
| static sdio_capset_t mpfs_capabilities(struct sdio_dev_s *dev) |
| { |
| struct mpfs_dev_s *priv = (struct mpfs_dev_s *)dev; |
| sdio_capset_t caps = 0; |
| |
| if (priv->onebit) |
| { |
| caps |= SDIO_CAPS_1BIT_ONLY; |
| } |
| |
| return caps; |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_status |
| * |
| * Description: |
| * Get SDIO status. |
| * |
| * Input Parameters: |
| * dev - Device-specific state data |
| * |
| * Returned Value: |
| * Returns a bitset of status values (see mpfs_status_* defines) |
| * |
| ****************************************************************************/ |
| |
| static sdio_statset_t mpfs_status(struct sdio_dev_s *dev) |
| { |
| struct mpfs_dev_s *priv = (struct mpfs_dev_s *)dev; |
| return priv->cdstatus; |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_widebus |
| * |
| * Description: |
| * Called after change in Bus width has been selected (via ACMD6). Most |
| * controllers will need to perform some special operations to work |
| * correctly in the new bus mode. |
| * |
| * Input Parameters: |
| * dev - An instance of the SDIO device interface |
| * wide - true: wide bus (4-bit) bus mode enabled |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void mpfs_widebus(struct sdio_dev_s *dev, bool wide) |
| { |
| struct mpfs_dev_s *priv = (struct mpfs_dev_s *)dev; |
| priv->widebus = wide; |
| |
| mcinfo("wide: %d\n", wide); |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_set_hs_mode |
| * |
| * Description: |
| * Sets the device in HS mode via CMD6. |
| * |
| * Input Parameters: |
| * dev - An instance of the SDIO device interface |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void mpfs_set_hs_mode(struct sdio_dev_s *dev) |
| { |
| uint32_t r1; |
| int ret; |
| |
| if ((ret = mpfs_sendcmd(dev, MMCSD_CMD6, 0x03b90100u)) == OK) |
| { |
| if ((ret == mpfs_waitresponse(dev, MMCSD_CMD6)) == OK) |
| { |
| ret = mpfs_recvshortcrc(dev, MMCSD_CMD6, &r1); |
| } |
| } |
| |
| if (ret < 0) |
| { |
| mcerr("Failed to set high speed mode\n"); |
| return; |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_set_4bit_mode |
| * |
| * Description: |
| * Sets the device for 4-bit data width via CMD6. |
| * |
| * Input Parameters: |
| * dev - An instance of the SDIO device interface |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void mpfs_set_4bit_mode(struct sdio_dev_s *dev) |
| { |
| struct mpfs_dev_s *priv = (struct mpfs_dev_s *)dev; |
| uint32_t r1; |
| uint8_t dw; |
| int ret; |
| |
| if ((ret = mpfs_sendcmd(dev, MMCSD_CMD6, 0x03b70100u)) == OK) |
| { |
| if ((ret == mpfs_waitresponse(dev, MMCSD_CMD6)) == OK) |
| { |
| ret = mpfs_recvshortcrc(dev, MMCSD_CMD6, &r1); |
| } |
| } |
| |
| if (ret < 0) |
| { |
| mcerr("Failed to set 4-bit mode\n"); |
| } |
| else |
| { |
| dw = (getreg8(MPFS_COREMMC_VR) >> 2) & 3; |
| |
| if (dw == 0) |
| { |
| mcerr("HW doesn't support 4-bit data width\n"); |
| } |
| |
| /* CoreMMC 4-bit mode */ |
| |
| mpfs_modifyreg8(MPFS_COREMMC_DCTRL, COREMMC_DCTRL_DSIZE, |
| COREMMC_DCTRL_DSIZE_4BIT); |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_clock |
| * |
| * Description: |
| * Enable/disable SDIO clocking. Only up to 25 Mhz is supported now. 50 Mhz |
| * may work with some cards. |
| * |
| * Input Parameters: |
| * dev - An instance of the SDIO device interface |
| * rate - Specifies the clocking to use (see enum sdio_clock_e) |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void mpfs_clock(struct sdio_dev_s *dev, enum sdio_clock_e rate) |
| { |
| struct mpfs_dev_s *priv = (struct mpfs_dev_s *)dev; |
| uint32_t clckr; |
| |
| switch (rate) |
| { |
| /* Disable clock */ |
| |
| default: |
| case CLOCK_SDIO_DISABLED: |
| clckr = 0; |
| break; |
| |
| /* Enable in initial ID mode clocking (400KHz) */ |
| |
| case CLOCK_IDMODE: |
| clckr = MPFS_MMC_CLOCK_400KHZ; |
| break; |
| |
| /* Enable normal MMC operation clocking */ |
| |
| case CLOCK_MMC_TRANSFER: |
| clckr = MPFS_MMC_CLOCK_50MHZ; |
| break; |
| |
| case CLOCK_SD_TRANSFER_4BIT: |
| case CLOCK_SD_TRANSFER_1BIT: |
| clckr = MPFS_MMC_CLOCK_50MHZ; |
| break; |
| } |
| |
| if (clckr == MPFS_MMC_CLOCK_50MHZ) |
| { |
| mpfs_set_hs_mode(dev); |
| } |
| |
| /* Set the new clock frequency */ |
| |
| mpfs_setclkrate(priv, clckr); |
| |
| if (rate == CLOCK_SD_TRANSFER_4BIT) |
| { |
| /* Need to settle a bit before issuing this CMD6 after clk change */ |
| |
| nxsig_usleep(100); |
| |
| mpfs_set_4bit_mode(dev); |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_attach |
| * |
| * Description: |
| * Attach and prepare interrupts |
| * |
| * Input Parameters: |
| * dev - An instance of the mmc device interface |
| * |
| * Returned Value: |
| * OK on success; A negated errno on failure. |
| * |
| ****************************************************************************/ |
| |
| static int mpfs_attach(struct sdio_dev_s *dev) |
| { |
| struct mpfs_dev_s *priv = (struct mpfs_dev_s *)dev; |
| int ret; |
| |
| /* Attach the mmc interrupt handler */ |
| |
| ret = irq_attach(priv->plic_irq, mpfs_coremmc_interrupt, priv); |
| if (ret == OK) |
| { |
| /* Disable all interrupts and clear interrupt status |
| * registers. |
| */ |
| |
| mpfs_putreg8(priv, 0x00, MPFS_COREMMC_IMR); |
| mpfs_putreg8(priv, 0xff, MPFS_COREMMC_ICR); |
| |
| mpfs_putreg8(priv, 0x00, MPFS_COREMMC_SBIMR); |
| mpfs_putreg8(priv, 0xff, MPFS_COREMMC_SBICR); |
| |
| mpfs_putreg8(priv, 0x00, MPFS_COREMMC_MBIMR); |
| mpfs_putreg8(priv, 0xff, MPFS_COREMMC_MBICR); |
| |
| up_enable_irq(priv->plic_irq); |
| } |
| |
| mcinfo("attach: %d\n", ret); |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_sendcmd |
| * |
| * Description: |
| * Send the SDIO command |
| * |
| * Input Parameters: |
| * dev - An instance of the SDIO device interface |
| * cmd - The command to send (32-bits, encoded) |
| * arg - 32-bit argument required with some commands |
| * |
| * Returned Value: |
| * OK if no errors, an error otherwise |
| * |
| ****************************************************************************/ |
| |
| static int mpfs_sendcmd(struct sdio_dev_s *dev, uint32_t cmd, |
| uint32_t arg) |
| { |
| struct mpfs_dev_s *priv = (struct mpfs_dev_s *)dev; |
| uint32_t cmdidx; |
| |
| mpfs_reset_lines(priv); |
| |
| /* Check if command / data lines are busy */ |
| |
| if (mpfs_check_lines_busy(priv)) |
| { |
| mcerr("Busy!\n"); |
| return -EBUSY; |
| } |
| |
| /* Clear all status interrupts */ |
| |
| mpfs_putreg8(priv, COREMMC_ALL_ICR, MPFS_COREMMC_ICR); |
| |
| cmdidx = (cmd & MMCSD_CMDIDX_MASK) >> MMCSD_CMDIDX_SHIFT; |
| |
| /* Arg must be the last one */ |
| |
| mpfs_putreg8(priv, (uint8_t)cmdidx, MPFS_COREMMC_CMDX); |
| |
| /* The argument needs endianness swapped */ |
| |
| mpfs_putreg8(priv, (arg >> 24), MPFS_COREMMC_ARG1); |
| mpfs_putreg8(priv, (arg >> 16) & 0xff, MPFS_COREMMC_ARG2); |
| mpfs_putreg8(priv, (arg >> 8) & 0xff, MPFS_COREMMC_ARG3); |
| mpfs_putreg8(priv, (arg & 0xff), MPFS_COREMMC_ARG4); |
| |
| mcinfo(" cmd: %08" PRIx32 " arg: %08" PRIx32 " cmdidx: %08" PRIx32 "\n", |
| cmd, arg, cmdidx); |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_blocksetup |
| * |
| * Description: |
| * Configure block size and the number of blocks for next transfer. |
| * |
| * Input Parameters: |
| * dev - An instance of the SDIO device interface. |
| * blocksize - The selected block size. |
| * nblocks - The number of blocks to transfer. |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void mpfs_blocksetup(struct sdio_dev_s *dev, unsigned int blocksize, |
| unsigned int nblocks) |
| { |
| struct mpfs_dev_s *priv = (struct mpfs_dev_s *)dev; |
| |
| mcinfo("nblocks: %u, blksize: %u\n", nblocks, blocksize); |
| |
| priv->multiblock = (nblocks > 1); |
| priv->blocksize = blocksize; |
| |
| /* Block Count (size) */ |
| |
| putreg16(nblocks, MPFS_COREMMC_BCR); |
| |
| /* Block Length */ |
| |
| putreg16(blocksize, MPFS_COREMMC_BLR); |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_recvsetup |
| * |
| * Description: |
| * Setup hardware in preparation for data transfer from the card in non-DMA |
| * (interrupt driven mode). This method will do whatever controller setup |
| * is necessary. |
| * |
| * Input Parameters: |
| * dev - An instance of the SDIO device interface |
| * buffer - Address of the buffer in which to receive the data |
| * nbytes - The number of bytes in the transfer |
| * |
| * Returned Value: |
| * Number of bytes sent on success; a negated errno on failure |
| * |
| ****************************************************************************/ |
| |
| static int mpfs_recvsetup(struct sdio_dev_s *dev, uint8_t *buffer, |
| size_t nbytes) |
| { |
| struct mpfs_dev_s *priv = (struct mpfs_dev_s *)dev; |
| |
| mcinfo("Receive: %zu bytes\n", nbytes); |
| |
| DEBUGASSERT(priv != NULL && buffer != NULL && nbytes > 0); |
| DEBUGASSERT(((uintptr_t)buffer & 3) == 0); |
| if (nbytes > priv->fifo_depth) |
| { |
| mcerr("Request doesn't fit the fifo!\n"); |
| } |
| |
| DEBUGASSERT(priv->fifo_depth >= nbytes); |
| |
| priv->buffer = (uint32_t *)buffer; |
| priv->remaining = nbytes; |
| priv->receivecnt = nbytes; |
| priv->polltransfer = true; |
| |
| mpfs_check_lines_busy(priv); |
| |
| /* Set up the SDIO data path, reset DAT and CMD lines */ |
| |
| mpfs_reset_lines(priv); |
| |
| if (priv->multiblock) |
| { |
| mpfs_putreg8(priv, COREMMC_MBICR_RDONE | COREMMC_MBICR_ERROR, |
| MPFS_COREMMC_MBICR); |
| } |
| else |
| { |
| mpfs_putreg8(priv, COREMMC_SBICR_RDONE | COREMMC_SBICR_ERROR, |
| MPFS_COREMMC_SBICR); |
| } |
| |
| mpfs_putreg8(priv, COREMMC_ALL_ICR, MPFS_COREMMC_ICR); |
| |
| /* Enable interrupts and issue the start of operation */ |
| |
| if (priv->multiblock) |
| { |
| mpfs_configxfrints(priv, COREMMC_RECV_MASK, COREMMC_RECV_MB_MASK); |
| mpfs_modifyreg8(MPFS_COREMMC_MBCSR, 0, COREMMC_MBCSR_RSTRT); |
| } |
| else |
| { |
| mpfs_configxfrints(priv, COREMMC_RECV_MASK, COREMMC_RECV_SB_MASK); |
| mpfs_modifyreg8(MPFS_COREMMC_SBCSR, 0, COREMMC_SBCSR_RSTRT); |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_sendsetup |
| * |
| * Description: |
| * Setup hardware in preparation for data transfer from the card. This |
| * method will do whatever controller setup is necessary. |
| * |
| * Input Parameters: |
| * dev - An instance of the SDIO device interface |
| * buffer - Address of the buffer containing the data to send |
| * nbytes - The number of bytes in the transfer |
| * |
| * Returned Value: |
| * Number of bytes sent on success; a negated errno on failure |
| * |
| ****************************************************************************/ |
| |
| static int mpfs_sendsetup(struct sdio_dev_s *dev, const |
| uint8_t *buffer, size_t nbytes) |
| { |
| struct mpfs_dev_s *priv = (struct mpfs_dev_s *)dev; |
| |
| mcinfo("Send: %zu bytes\n", nbytes); |
| |
| DEBUGASSERT(priv != NULL && buffer != NULL && nbytes > 0); |
| if (nbytes > priv->fifo_depth) |
| { |
| mcerr("Request doesn't fit the fifo!\n"); |
| } |
| |
| DEBUGASSERT(priv->fifo_depth >= nbytes); |
| |
| mpfs_reset_lines(priv); |
| mpfs_check_lines_busy(priv); |
| |
| /* Unaligned (not 32-bit aligned) writes seem to be possible. Copy data to |
| * an aligned buffer in this case. |
| */ |
| |
| if ((uintptr_t)buffer & 3) |
| { |
| if (nbytes <= sizeof(g_aligned_buffer)) |
| { |
| memcpy((uint8_t *)g_aligned_buffer, buffer, nbytes); |
| priv->buffer = g_aligned_buffer; |
| } |
| else |
| { |
| DEBUGPANIC(); |
| } |
| } |
| else |
| { |
| priv->buffer = (uint32_t *)buffer; |
| } |
| |
| priv->remaining = nbytes; |
| priv->receivecnt = 0; |
| priv->polltransfer = true; |
| |
| /* Clear interrupt status bits via interrupt clear registers */ |
| |
| if (priv->multiblock) |
| { |
| mpfs_putreg8(priv, COREMMC_MBICR_WDONE | COREMMC_MBICR_ERROR, |
| MPFS_COREMMC_MBICR); |
| } |
| else |
| { |
| mpfs_putreg8(priv, COREMMC_SBICR_WDONE | COREMMC_SBICR_ERROR, |
| MPFS_COREMMC_SBICR); |
| } |
| |
| mpfs_putreg8(priv, COREMMC_ALL_ICR, MPFS_COREMMC_ICR); |
| |
| /* Enable required interrupts */ |
| |
| if (priv->multiblock) |
| { |
| mpfs_configxfrints(priv, COREMMC_SEND_MASK, COREMMC_SEND_MB_MASK); |
| mpfs_modifyreg8(MPFS_COREMMC_MBCSR, 0, COREMMC_MBCSR_WSTRT); |
| } |
| else |
| { |
| mpfs_configxfrints(priv, COREMMC_SEND_MASK, COREMMC_SEND_SB_MASK); |
| mpfs_modifyreg8(MPFS_COREMMC_SBCSR, 0, COREMMC_SBCSR_WSTRT); |
| } |
| |
| /* Write data into the fifo */ |
| |
| mpfs_sendfifo(priv); |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_cancel |
| * |
| * Description: |
| * Cancel the data transfer setup of various send / receive attempts. This |
| * must be called to cancel the data transfer setup if, for some reason, |
| * you cannot perform the transfer. |
| * |
| * Input Parameters: |
| * dev - An instance of the SDIO device interface |
| * |
| * Returned Value: |
| * OK is success; a negated errno on failure |
| * |
| ****************************************************************************/ |
| |
| static int mpfs_cancel(struct sdio_dev_s *dev) |
| { |
| struct mpfs_dev_s *priv = (struct mpfs_dev_s *)dev; |
| |
| /* Disable all transfer- and event- related interrupts */ |
| |
| mpfs_configxfrints(priv, 0, 0); |
| mpfs_configwaitints(priv, 0, 0, 0); |
| |
| /* Clearing pending interrupt status on all transfer- and event- related |
| * interrupts |
| */ |
| |
| mpfs_putreg8(priv, COREMMC_ALL_ICR, MPFS_COREMMC_ICR); |
| mpfs_putreg8(priv, MPFS_COREMMC_SR_EBOD | MPFS_COREMMC_SR_EBUD | |
| MPFS_COREMMC_SR_ECRD, MPFS_COREMMC_SR); |
| |
| if (priv->multiblock) |
| { |
| mpfs_putreg8(priv, COREMMC_MBICR_ERROR | COREMMC_MBICR_RDONE | |
| COREMMC_MBICR_WDONE, MPFS_COREMMC_MBICR); |
| } |
| else |
| { |
| mpfs_putreg8(priv, COREMMC_SBICR_ERROR | COREMMC_SBICR_RDONE | |
| COREMMC_SBICR_WDONE, MPFS_COREMMC_SBICR); |
| } |
| |
| /* Cancel any watchdog timeout */ |
| |
| wd_cancel(&priv->waitwdog); |
| |
| /* Mark no transfer in progress */ |
| |
| priv->remaining = 0; |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_waitresponse |
| * |
| * Description: |
| * Poll-wait for the response to the last command to be ready. |
| * |
| * Input Parameters: |
| * dev - An instance of the SDIO device interface |
| * cmd - The command that was sent. |
| * |
| * Returned Value: |
| * OK is success; a negated errno on failure |
| * |
| ****************************************************************************/ |
| |
| static int mpfs_waitresponse(struct sdio_dev_s *dev, uint32_t cmd) |
| { |
| struct mpfs_dev_s *priv = (struct mpfs_dev_s *)dev; |
| uint8_t status; |
| int32_t timeout; |
| |
| mcinfo("cmd: %08" PRIx32 "\n", cmd); |
| |
| switch (cmd & MMCSD_RESPONSE_MASK) |
| { |
| case MMCSD_NO_RESPONSE: |
| timeout = COREMMC_CMDTIMEOUT; |
| break; |
| |
| case MMCSD_R1_RESPONSE: |
| case MMCSD_R1B_RESPONSE: |
| case MMCSD_R2_RESPONSE: |
| case MMCSD_R4_RESPONSE: |
| case MMCSD_R5_RESPONSE: |
| case MMCSD_R6_RESPONSE: |
| timeout = COREMMC_LONGTIMEOUT; |
| break; |
| |
| case MMCSD_R3_RESPONSE: |
| case MMCSD_R7_RESPONSE: |
| timeout = COREMMC_CMDTIMEOUT; |
| break; |
| |
| default: |
| mcerr("Unknown command\n"); |
| return -EINVAL; |
| } |
| |
| /* Wait for the response (or timeout) */ |
| |
| do |
| { |
| status = getreg8(MPFS_COREMMC_ISR); |
| } |
| while (!(status & (COREMMC_ISR_RRI | COREMMC_ISR_ERROR)) |
| && --timeout); |
| |
| if (timeout == 0 || (status & COREMMC_ISR_ERROR)) |
| { |
| mcerr("ERROR: Timeout cmd: %08" PRIx32 " stat: %02" PRIx8 "\n", cmd, |
| status); |
| return -EBUSY; |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_check_recverror |
| * |
| * Description: |
| * Receive response to SDIO command. |
| * |
| * Input Parameters: |
| * priv - Instance of the CoreMMC private state structure. |
| * |
| * Returned Value: |
| * OK on success; a negated errno if error detected. |
| * |
| ****************************************************************************/ |
| |
| static int mpfs_check_recverror(struct mpfs_dev_s *priv) |
| { |
| uint32_t timeout = COREMMC_CMDTIMEOUT; |
| uint8_t regval; |
| int ret = OK; |
| |
| regval = getreg8(MPFS_COREMMC_ISR); |
| |
| if (regval & COREMMC_ISR_ERROR) |
| { |
| if (regval & COREMMC_ISR_SBI) |
| { |
| mcerr("ERROR: Command timeout: %02" PRIx8 "\n", regval); |
| ret = -ETIMEDOUT; |
| } |
| else if (regval & COREMMC_ISR_TBI) |
| { |
| mcerr("ERROR: Stop bit error: %02" PRIx8 "\n", regval); |
| ret = -EIO; |
| } |
| else |
| { |
| mcerr("ERROR: %02" PRIx8 "\n", regval); |
| ret = -EIO; |
| } |
| } |
| else if (!(regval & COREMMC_ISR_RRI)) |
| { |
| mcerr("ERROR: Command not completed: %02" PRIx8 "\n", regval); |
| ret = -EIO; |
| } |
| |
| /* Make sure response data ready is set (RDRE) */ |
| |
| do |
| { |
| regval = getreg8(MPFS_COREMMC_SR); |
| } |
| while (!(regval & (MPFS_COREMMC_SR_RDRE)) |
| && --timeout); |
| |
| if (timeout == 0) |
| { |
| mcerr("ERROR: Response not ready: %02" PRIx8 "\n", regval); |
| ret = -ETIMEDOUT; |
| } |
| |
| if (ret) |
| { |
| /* With an error, reset DAT and CMD lines. Otherwise the next command |
| * will fail as well. |
| */ |
| |
| mpfs_reset_lines(priv); |
| |
| /* Clear all status interrupts */ |
| |
| mpfs_putreg8(priv, COREMMC_ALL_ICR, MPFS_COREMMC_ICR); |
| } |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_recvshortcrc |
| * |
| * Description: |
| * Receive response to SDIO command. |
| * |
| * Input Parameters: |
| * dev - An instance of the SDIO device interface |
| * cmd - Command |
| * rshort - Buffer for reveiving the response |
| * |
| * Returned Value: |
| * OK on success; a negated errno on failure. |
| * |
| ****************************************************************************/ |
| |
| static int mpfs_recvshortcrc(struct sdio_dev_s *dev, uint32_t cmd, |
| uint32_t *rshort) |
| { |
| struct mpfs_dev_s *priv = (struct mpfs_dev_s *)dev; |
| uint32_t response; |
| int ret = OK; |
| |
| /* Check if a timeout or CRC error occurred */ |
| |
| if (!(cmd & MMCSD_DATAXFR_MASK)) |
| { |
| ret = mpfs_check_recverror(priv); |
| } |
| |
| if (rshort) |
| { |
| response = getreg8(MPFS_COREMMC_RR1) << 24; |
| response |= getreg8(MPFS_COREMMC_RR2) << 16; |
| response |= getreg8(MPFS_COREMMC_RR3) << 8; |
| response |= getreg8(MPFS_COREMMC_RR4) << 0; |
| *rshort = response; |
| |
| mcinfo("recv: %08" PRIx32 "\n", *rshort); |
| } |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_recvshort |
| * |
| * Description: |
| * Receive response to SDIO command. |
| * |
| * Input Parameters: |
| * dev - An instance of the SDIO device interface |
| * cmd - Command |
| * rshort - Buffer for reveiving the response |
| * |
| * Returned Value: |
| * OK on success; a negated errno on failure. |
| * |
| ****************************************************************************/ |
| |
| static int mpfs_recvshort(struct sdio_dev_s *dev, uint32_t cmd, |
| uint32_t *rshort) |
| { |
| struct mpfs_dev_s *priv = (struct mpfs_dev_s *)dev; |
| uint32_t response; |
| int ret = OK; |
| |
| /* Check if a timeout or CRC error occurred */ |
| |
| if (!(cmd & MMCSD_DATAXFR_MASK)) |
| { |
| ret = mpfs_check_recverror(priv); |
| } |
| |
| if (rshort) |
| { |
| response = getreg8(MPFS_COREMMC_RR1) << 24; |
| response |= getreg8(MPFS_COREMMC_RR2) << 16; |
| response |= getreg8(MPFS_COREMMC_RR3) << 8; |
| response |= getreg8(MPFS_COREMMC_RR4) << 0; |
| |
| *rshort = response; |
| mcinfo("recv: %08" PRIx32 "\n", *rshort); |
| } |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_recvlong |
| * |
| * Description: |
| * Receive response to SDIO command. |
| * |
| * Input Parameters: |
| * dev - An instance of the SDIO device interface |
| * cmd - Command |
| * rlong - Buffer for reveiving the response |
| * |
| * Returned Value: |
| * OK on success; a negated errno on failure. |
| * |
| ****************************************************************************/ |
| |
| static int mpfs_recvlong(struct sdio_dev_s *dev, uint32_t cmd, |
| uint32_t rlong[4]) |
| { |
| struct mpfs_dev_s *priv = (struct mpfs_dev_s *)dev; |
| int ret; |
| |
| ret = mpfs_check_recverror(priv); |
| |
| /* Return the long response */ |
| |
| if (rlong) |
| { |
| rlong[0] = getreg8(MPFS_COREMMC_RR0) << 24 | |
| getreg8(MPFS_COREMMC_RR1) << 16 | |
| getreg8(MPFS_COREMMC_RR2) << 8 | |
| getreg8(MPFS_COREMMC_RR3); |
| rlong[1] = getreg8(MPFS_COREMMC_RR4) << 24 | |
| getreg8(MPFS_COREMMC_RR5) << 16 | |
| getreg8(MPFS_COREMMC_RR6) << 8 | |
| getreg8(MPFS_COREMMC_RR7); |
| rlong[2] = getreg8(MPFS_COREMMC_RR8) << 24 | |
| getreg8(MPFS_COREMMC_RR9) << 16 | |
| getreg8(MPFS_COREMMC_RR10) << 8 | |
| getreg8(MPFS_COREMMC_RR11); |
| rlong[3] = getreg8(MPFS_COREMMC_RR12) << 24 | |
| getreg8(MPFS_COREMMC_RR13) << 16 | |
| getreg8(MPFS_COREMMC_RR14) << 8 | |
| getreg8(MPFS_COREMMC_RR15); |
| |
| mcinfo("recv: %08" PRIx32 " %08" PRIx32 " %08" PRIx32 " %08" \ |
| PRIx32"\n", rlong[0], rlong[1], rlong[2], rlong[3]); |
| } |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_waitenable |
| * |
| * Description: |
| * Enable / disable of a set of SDIO wait events. This is part of the |
| * the EMMCSD_WAITEVENT sequence. The set of to-be-waited-for events is |
| * configured before calling mpfs_eventwait. |
| * |
| * The enabled events persist until either (1) EMMCSD_WAITENABLE is called |
| * again specifying a different set of wait events, or (2) EMMCSD_EVENTWAIT |
| * returns. |
| * |
| * Input Parameters: |
| * dev - An instance of the SDIO device interface |
| * eventset - A bitset of events to enable or disable (see SDIOWAIT_* |
| * definitions). 0=disable; 1=enable. |
| * timeout - SDIO timeout |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void mpfs_waitenable(struct sdio_dev_s *dev, sdio_eventset_t eventset, |
| uint32_t timeout) |
| { |
| struct mpfs_dev_s *priv = (struct mpfs_dev_s *)dev; |
| uint32_t waitmask = 0; |
| |
| mcinfo("eventset: %02" PRIx8 "\n", eventset); |
| |
| DEBUGASSERT(priv != NULL); |
| |
| /* Disable event-related interrupts */ |
| |
| mpfs_configwaitints(priv, 0, 0, 0); |
| |
| /* Select the interrupt mask that will give us the appropriate wakeup |
| * interrupts. |
| */ |
| |
| if ((eventset & SDIOWAIT_CMDDONE) != 0) |
| { |
| waitmask |= COREMMC_ISR_CSI; |
| } |
| |
| if ((eventset & SDIOWAIT_RESPONSEDONE) != 0) |
| { |
| waitmask |= COREMMC_ISR_RRI; |
| } |
| |
| /* Enable event-related interrupts */ |
| |
| mpfs_configwaitints(priv, waitmask, eventset, 0); |
| |
| /* Check if the timeout event is specified in the event set */ |
| |
| if ((priv->waitevents & SDIOWAIT_TIMEOUT) != 0) |
| { |
| int delay; |
| int ret; |
| |
| /* Yes.. Handle a cornercase: The user request a timeout event but |
| * with timeout == 0? |
| */ |
| |
| if (!timeout) |
| { |
| priv->wkupevent = SDIOWAIT_TIMEOUT; |
| return; |
| } |
| |
| /* Start the watchdog timer */ |
| |
| delay = MSEC2TICK(timeout); |
| ret = wd_start(&priv->waitwdog, delay, |
| mpfs_eventtimeout, (wdparm_t)priv); |
| if (ret < OK) |
| { |
| mcerr("ERROR: wd_start failed: %d\n", ret); |
| } |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_eventwait |
| * |
| * Description: |
| * Wait for one of the enabled events to occur (or a timeout). Note that |
| * all events enabled by EMMCSD_WAITEVENTS are disabled when mpfs_eventwait |
| * returns. EMMCSD_WAITEVENTS must be called again before mpfs_eventwait |
| * can be used again. |
| * |
| * Input Parameters: |
| * dev - An instance of the SDIO device interface |
| * timeout - Maximum time in milliseconds to wait. Zero means immediate |
| * timeout with no wait. The timeout value is ignored if |
| * SDIOWAIT_TIMEOUT is not included in the waited-for eventset. |
| * |
| * Returned Value: |
| * Event set containing the event(s) that ended the wait. Should always |
| * be non-zero. All events are disabled after the wait concludes. |
| * |
| ****************************************************************************/ |
| |
| static sdio_eventset_t mpfs_eventwait(struct sdio_dev_s *dev) |
| { |
| struct mpfs_dev_s *priv = (struct mpfs_dev_s *)dev; |
| sdio_eventset_t wkupevent = 0; |
| int ret; |
| |
| mcinfo("wait\n"); |
| |
| DEBUGASSERT(priv->waitevents != 0 || priv->wkupevent != 0); |
| |
| /* Loop until the event (or the timeout occurs). Race conditions are |
| * avoided by calling mpfs_waitenable prior to triggering the logic that |
| * will cause the wait to terminate. Under certain race conditions, the |
| * waited-for may have already occurred before this function was called! |
| */ |
| |
| for (; ; ) |
| { |
| /* Wait for an event in event set to occur. If this the event has |
| * already occurred, then the semaphore will already have been |
| * incremented and there will be no wait. |
| */ |
| |
| ret = nxsem_wait_uninterruptible(&priv->waitsem); |
| if (ret < 0) |
| { |
| /* Task canceled. Cancel the wdog (assuming it was started) and |
| * return an SDIO error. |
| */ |
| |
| wd_cancel(&priv->waitwdog); |
| wkupevent = SDIOWAIT_ERROR; |
| goto errout_with_waitints; |
| } |
| |
| wkupevent = priv->wkupevent; |
| |
| /* Check if the event has occurred. When the event has occurred, then |
| * evenset will be set to 0 and wkupevent will be set to a nonzero |
| * value. |
| */ |
| |
| if (wkupevent != 0) |
| { |
| /* Yes... break out of the loop with wkupevent non-zero */ |
| |
| break; |
| } |
| } |
| |
| /* Disable event-related interrupts */ |
| |
| errout_with_waitints: |
| mpfs_configwaitints(priv, 0, 0, 0); |
| |
| return wkupevent; |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_callbackenable |
| * |
| * Description: |
| * Enable/disable of a set of SDIO callback events. This is part of the |
| * the SDIO callback sequence. The set of events is configured to enabled |
| * callbacks to the function provided in mpfs_registercallback. |
| * |
| * Events are automatically disabled once the callback is performed and no |
| * further callback events will occur until they are again enabled by |
| * calling this method. |
| * |
| * Input Parameters: |
| * dev - An instance of the SDIO device interface |
| * eventset - A bitset of events to enable or disable (see SDIOMEDIA_* |
| * definitions). 0=disable; 1=enable. |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void mpfs_callbackenable(struct sdio_dev_s *dev, |
| sdio_eventset_t eventset) |
| { |
| struct mpfs_dev_s *priv = (struct mpfs_dev_s *)dev; |
| |
| mcinfo("eventset: %02" PRIx8 "\n", eventset); |
| DEBUGASSERT(priv != NULL); |
| |
| priv->cbevents = eventset; |
| mpfs_callback(priv); |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_registercallback |
| * |
| * Description: |
| * Register a callback that that will be invoked on any media status |
| * change. When this method is called, all callbacks should be disabled |
| * until they are enabled via a call to EMMCSD_CALLBACKENABLE |
| * |
| * Input Parameters: |
| * dev - Device-specific state data |
| * callback - The function to call on the media change |
| * arg - A caller provided value to return with the callback |
| * |
| * Returned Value: |
| * 0 on success; negated errno on failure. |
| * |
| ****************************************************************************/ |
| |
| static int mpfs_registercallback(struct sdio_dev_s *dev, |
| worker_t callback, void *arg) |
| { |
| struct mpfs_dev_s *priv = (struct mpfs_dev_s *)dev; |
| |
| /* Disable callbacks and register this callback and is argument */ |
| |
| mcinfo("Register %p(%p)\n", callback, arg); |
| DEBUGASSERT(priv != NULL); |
| |
| priv->cbevents = 0; |
| priv->cbarg = arg; |
| priv->callback = callback; |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: mpfs_callback |
| * |
| * Description: |
| * Perform callback. |
| * |
| * Assumptions: |
| * This function does not execute in the context of an interrupt handler. |
| * It may be invoked on any user thread or scheduled on the work thread |
| * from an interrupt handler. |
| * |
| ****************************************************************************/ |
| |
| static void mpfs_callback(void *arg) |
| { |
| struct mpfs_dev_s *priv = (struct mpfs_dev_s *)arg; |
| |
| /* Is a callback registered? */ |
| |
| DEBUGASSERT(priv != NULL); |
| |
| mcinfo("Callback %p(%p) cbevents: %02" PRIx8 " cdstatus: %02" PRIx8 "\n", |
| priv->callback, priv->cbarg, priv->cbevents, priv->cdstatus); |
| |
| if (priv->callback) |
| { |
| /* Yes.. Check for enabled callback events */ |
| |
| if ((priv->cdstatus & SDIO_STATUS_PRESENT) != 0) |
| { |
| /* Media is present. Is the media inserted event enabled? */ |
| |
| if ((priv->cbevents & SDIOMEDIA_INSERTED) == 0) |
| { |
| /* No... return without performing the callback */ |
| |
| return; |
| } |
| } |
| else |
| { |
| /* Media is not present. Is the media eject event enabled? */ |
| |
| if ((priv->cbevents & SDIOMEDIA_EJECTED) == 0) |
| { |
| /* No... return without performing the callback */ |
| |
| return; |
| } |
| } |
| |
| /* Perform the callback, disabling further callbacks. Of course, the |
| * the callback can (and probably should) re-enable callbacks. |
| */ |
| |
| priv->cbevents = 0; |
| |
| /* Callbacks cannot be performed in the context of an interrupt |
| * handler. If we are in an interrupt handler, then queue the |
| * callback to be performed later on the work thread. |
| */ |
| |
| if (up_interrupt_context()) |
| { |
| /* Yes.. queue it */ |
| |
| mcinfo("Queuing callback to %p(%p)\n", |
| priv->callback, priv->cbarg); |
| |
| work_queue(HPWORK, &priv->cbwork, priv->callback, |
| priv->cbarg, 0); |
| } |
| else |
| { |
| /* No.. then just call the callback here */ |
| |
| mcinfo("Callback to %p(%p)\n", priv->callback, priv->cbarg); |
| priv->callback(priv->cbarg); |
| } |
| } |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: sdio_initialize |
| * |
| * Description: |
| * Initialize SDIO for operation. |
| * |
| * Input Parameters: |
| * slotno - Not used. |
| * |
| * Returned Values: |
| * A reference to an SDIO interface structure. NULL is returned on |
| * failures. |
| * |
| ****************************************************************************/ |
| |
| struct sdio_dev_s *sdio_initialize(int slotno) |
| { |
| struct mpfs_dev_s *priv = &g_coremmc_dev; |
| |
| /* Reset the card and assure that it is in the initial, unconfigured |
| * state. |
| */ |
| |
| if (!mpfs_device_reset(&priv->dev)) |
| { |
| return NULL; |
| } |
| |
| return &priv->dev; |
| } |
| |
| /**************************************************************************** |
| * Name: sdio_mediachange |
| * |
| * Description: |
| * Called by board-specific logic -- possible from an interrupt handler -- |
| * in order to signal to the driver that a card has been inserted or |
| * removed from the slot |
| * |
| * Input Parameters: |
| * dev - An instance of the SDIO driver device state structure. |
| * cardinslot - true is a card has been detected in the slot; false if a |
| * card has been removed from the slot. Only transitions |
| * (inserted->removed or removed->inserted should be reported) |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| void sdio_mediachange(struct sdio_dev_s *dev, bool cardinslot) |
| { |
| struct mpfs_dev_s *priv = (struct mpfs_dev_s *)dev; |
| sdio_statset_t cdstatus; |
| irqstate_t flags; |
| |
| /* Update card status */ |
| |
| flags = enter_critical_section(); |
| cdstatus = priv->cdstatus; |
| if (cardinslot) |
| { |
| priv->cdstatus |= SDIO_STATUS_PRESENT; |
| } |
| else |
| { |
| priv->cdstatus &= ~SDIO_STATUS_PRESENT; |
| } |
| |
| leave_critical_section(flags); |
| |
| mcinfo("cdstatus OLD: %02" PRIx8 " NEW: %02" PRIx8 "\n", |
| cdstatus, priv->cdstatus); |
| |
| /* Perform any requested callback if the status has changed */ |
| |
| if (cdstatus != priv->cdstatus) |
| { |
| mpfs_callback(priv); |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: sdio_wrprotect |
| * |
| * Description: |
| * Called by board-specific logic to report if the card in the slot is |
| * mechanically write protected. |
| * |
| * Input Parameters: |
| * dev - An instance of the SDIO driver device state structure. |
| * wrprotect - true is a card is writeprotected. |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| void sdio_wrprotect(struct sdio_dev_s *dev, bool wrprotect) |
| { |
| struct mpfs_dev_s *priv = (struct mpfs_dev_s *)dev; |
| irqstate_t flags; |
| |
| /* Update card status */ |
| |
| flags = enter_critical_section(); |
| if (wrprotect) |
| { |
| priv->cdstatus |= SDIO_STATUS_WRPROTECTED; |
| } |
| else |
| { |
| priv->cdstatus &= ~SDIO_STATUS_WRPROTECTED; |
| } |
| |
| mcinfo("cdstatus: %02" PRIx8 "\n", priv->cdstatus); |
| |
| leave_critical_section(flags); |
| } |