blob: 873fc4ddb09aca8793050f67e2e2c4206362d82e [file] [log] [blame]
/****************************************************************************
* arch/risc-v/src/mpfs/mpfs_emmcsd.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/mmcsd.h>
#include <nuttx/irq.h>
#include <nuttx/cache.h>
#include <arch/board/board.h>
#include "mpfs_emmcsd.h"
#include "riscv_internal.h"
#include "hardware/mpfs_emmcsd.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define MPFS_EMMCSD_HRS00 (priv->hw_base + MPFS_EMMCSD_HRS00_OFFSET)
#define MPFS_EMMCSD_HRS01 (priv->hw_base + MPFS_EMMCSD_HRS01_OFFSET)
#define MPFS_EMMCSD_HRS04 (priv->hw_base + MPFS_EMMCSD_HRS04_OFFSET)
#define MPFS_EMMCSD_HRS06 (priv->hw_base + MPFS_EMMCSD_HRS06_OFFSET)
#define MPFS_EMMCSD_SRS01 (priv->hw_base + MPFS_EMMCSD_SRS01_OFFSET)
#define MPFS_EMMCSD_SRS02 (priv->hw_base + MPFS_EMMCSD_SRS02_OFFSET)
#define MPFS_EMMCSD_SRS03 (priv->hw_base + MPFS_EMMCSD_SRS03_OFFSET)
#define MPFS_EMMCSD_SRS04 (priv->hw_base + MPFS_EMMCSD_SRS04_OFFSET)
#define MPFS_EMMCSD_SRS05 (priv->hw_base + MPFS_EMMCSD_SRS05_OFFSET)
#define MPFS_EMMCSD_SRS06 (priv->hw_base + MPFS_EMMCSD_SRS06_OFFSET)
#define MPFS_EMMCSD_SRS07 (priv->hw_base + MPFS_EMMCSD_SRS07_OFFSET)
#define MPFS_EMMCSD_SRS08 (priv->hw_base + MPFS_EMMCSD_SRS08_OFFSET)
#define MPFS_EMMCSD_SRS09 (priv->hw_base + MPFS_EMMCSD_SRS09_OFFSET)
#define MPFS_EMMCSD_SRS10 (priv->hw_base + MPFS_EMMCSD_SRS10_OFFSET)
#define MPFS_EMMCSD_SRS11 (priv->hw_base + MPFS_EMMCSD_SRS11_OFFSET)
#define MPFS_EMMCSD_SRS12 (priv->hw_base + MPFS_EMMCSD_SRS12_OFFSET)
#define MPFS_EMMCSD_SRS13 (priv->hw_base + MPFS_EMMCSD_SRS13_OFFSET)
#define MPFS_EMMCSD_SRS14 (priv->hw_base + MPFS_EMMCSD_SRS14_OFFSET)
#define MPFS_EMMCSD_SRS15 (priv->hw_base + MPFS_EMMCSD_SRS15_OFFSET)
#define MPFS_EMMCSD_SRS16 (priv->hw_base + MPFS_EMMCSD_SRS16_OFFSET)
#define MPFS_EMMCSD_SRS21 (priv->hw_base + MPFS_EMMCSD_SRS21_OFFSET)
#define MPFS_EMMCSD_SRS22 (priv->hw_base + MPFS_EMMCSD_SRS22_OFFSET)
#define MPFS_EMMCSD_SRS23 (priv->hw_base + MPFS_EMMCSD_SRS23_OFFSET)
#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)
#define MPFS_PMPCFG_MMC_0 (MPFS_MPUCFG_BASE + 0x700)
#define MPFS_PMPCFG_MMC_1 (MPFS_MPUCFG_BASE + 0x708)
#define MPFS_PMPCFG_MMC_2 (MPFS_MPUCFG_BASE + 0x710)
#define MPFS_PMPCFG_MMC_3 (MPFS_MPUCFG_BASE + 0x718)
#define MPFS_MMC_CLOCK_400KHZ 400u
#define MPFS_MMC_CLOCK_12_5MHZ 12500u
#define MPFS_MMC_CLOCK_25MHZ 25000u
#define MPFS_MMC_CLOCK_26MHZ 26000u
#define MPFS_MMC_CLOCK_50MHZ 50000u
#define MPFS_MMC_CLOCK_100MHZ 100000u
#define MPFS_MMC_CLOCK_200MHZ 200000u
#define MPFS_EMMCSD_DEBOUNCE_TIME 0x300000u
#define MPFS_EMMCSD_DATA_TIMEOUT 500000
#define MPFS_EMMCSD_SRS10_3_3V_BUS_VOLTAGE (0x7 << 9)
#define MPFS_EMMCSD_SRS10_3_0V_BUS_VOLTAGE (0x6 << 9)
#define MPFS_EMMCSD_SRS10_1_8V_BUS_VOLTAGE (0x5 << 9)
#define MPFS_EMMCSD_1_8V_BUS_VOLTAGE 18
#define MPFS_EMMCSD_3_3V_BUS_VOLTAGE 33
#define MPFS_EMMCSD_INITIALIZED 0x00
#define MPFS_EMMCSD_NOT_INITIALIZED 0x01
#ifndef CONFIG_SCHED_WORKQUEUE
# error "Callback support requires CONFIG_SCHED_WORKQUEUE"
#endif
/* High-speed single data rate supports clock frequency up to 52 MHz and data
* bus width of 1 bit, 4 bits, and 8 bits.
*/
#define MPFS_EMMCSD_MODE_SDR 0x2u
/* High speed double data rate supports clock frequency up to 52 MHz and data
* bus width of 4 bits and 8 bits.
*/
#define MPFS_EMMCSD_MODE_DDR 0x3u
/* SDR data sampling supports clock frequency up to 200 MHz and data bus
* width of 4 bits and 8 bits.
*/
#define MPFS_EMMCSD_MODE_HS200 0x4u
/* DDR data sampling supports clock frequency up to 200 MHz and data bus
* width of 8 bits.
*/
#define MPFS_EMMCSD_MODE_HS400 0x5u
/* HS400 mode with Enhanced Strobe */
#define MPFS_EMMCSD_MODE_HS400_ES 0x6u
/* Backwards compatibility with legacy MMC card supports clock frequency up
* to 26MHz and data bus width of 1 bit, 4 bits, and 8 bits.
*/
#define MPFS_EMMCSD_MODE_LEGACY 0x7u
/* Provide default eMMC CLK_MODE if unset at board.h */
#ifndef MPFS_EMMC_CLK_MODE
# define MPFS_EMMC_CLK_MODE MPFS_EMMCSD_MODE_HS200
#endif
/* Provide default SD-card 4bit clk if unset at board.h */
#ifndef MPFS_SD_CLOCK_4BIT
# define MPFS_SD_CLOCK_4BIT MPFS_MMC_CLOCK_25MHZ
#endif
/* Define the Hardware FIFO size */
#define FIFO_SIZE_IN_BYTES 64
/* Timing */
#define EMMCSD_CMDTIMEOUT (100000)
#define EMMCSD_LONGTIMEOUT (100000000)
/* Event waiting interrupt mask bits */
#define MPFS_EMMCSD_CARD_INTS (MPFS_EMMCSD_SRS14_CINT_IE | \
MPFS_EMMCSD_SRS14_CIN_IE | \
MPFS_EMMCSD_SRS14_CR_IE)
#define MPFS_EMMCSD_CMDDONE_STA (MPFS_EMMCSD_SRS12_CC)
#define MPFS_EMMCSD_RESPDONE_STA (MPFS_EMMCSD_SRS12_ECT | \
MPFS_EMMCSD_SRS12_ECCRC)
#define MPFS_EMMCSD_XFRDONE_STA (MPFS_EMMCSD_SRS12_TC)
#define MPFS_EMMCSD_CMDDONE_MASK (MPFS_EMMCSD_CARD_INTS | \
MPFS_EMMCSD_SRS14_CC_IE)
#define MPFS_EMMCSD_RESPDONE_MASK (MPFS_EMMCSD_CARD_INTS | \
MPFS_EMMCSD_SRS14_ECCRC_IE | \
MPFS_EMMCSD_SRS14_ECT_IE)
#define MPFS_EMMCSD_XFRDONE_MASK (MPFS_EMMCSD_CARD_INTS | \
MPFS_EMMCSD_SRS14_TC_IE)
#define MPFS_EMMCSD_CMDDONE_ICR (MPFS_EMMCSD_SRS12_CC)
#define MPFS_EMMCSD_RESPDONE_ICR (MPFS_EMMCSD_SRS12_ECT | \
MPFS_EMMCSD_SRS12_ECCRC)
#define MPFS_EMMCSD_XFRDONE_ICR (MPFS_EMMCSD_SRS12_EDCRC | \
MPFS_EMMCSD_SRS12_EDT | \
MPFS_EMMCSD_SRS12_TC)
#define MPFS_EMMCSD_WAITALL_ICR (MPFS_EMMCSD_CMDDONE_ICR | \
MPFS_EMMCSD_RESPDONE_ICR | \
MPFS_EMMCSD_XFRDONE_ICR)
#define MPFS_EMMCSD_RECV_MASKDMA (MPFS_EMMCSD_CARD_INTS | \
MPFS_EMMCSD_SRS14_EADMA_IE | \
MPFS_EMMCSD_SRS14_DMAINT_IE | \
MPFS_EMMCSD_SRS14_EDT_IE | \
MPFS_EMMCSD_SRS14_ECT_IE | \
MPFS_EMMCSD_SRS14_BRR_IE | \
MPFS_EMMCSD_SRS14_TC_IE)
#define MPFS_EMMCSD_RECV_MASK (MPFS_EMMCSD_CARD_INTS | \
MPFS_EMMCSD_SRS14_EDT_IE | \
MPFS_EMMCSD_SRS14_ECT_IE | \
MPFS_EMMCSD_SRS14_BRR_IE | \
MPFS_EMMCSD_SRS14_TC_IE)
#define MPFS_EMMCSD_SEND_MASKDMA (MPFS_EMMCSD_CARD_INTS | \
MPFS_EMMCSD_SRS14_EADMA_IE | \
MPFS_EMMCSD_SRS14_DMAINT_IE | \
MPFS_EMMCSD_SRS14_EDT_IE | \
MPFS_EMMCSD_SRS14_ECT_IE | \
MPFS_EMMCSD_SRS14_BWR_IE | \
MPFS_EMMCSD_SRS14_TC_IE)
#define MPFS_EMMCSD_SEND_MASK (MPFS_EMMCSD_CARD_INTS | \
MPFS_EMMCSD_SRS14_EDT_IE | \
MPFS_EMMCSD_SRS14_ECT_IE | \
MPFS_EMMCSD_SRS14_BWR_IE | \
MPFS_EMMCSD_SRS14_TC_IE)
/* SD-Card IOMUX */
#define LIBERO_SETTING_IOMUX1_CR_SD 0x00000000UL
#ifdef CONFIG_MPFS_EMMCSD_MUX_GPIO
#define LIBERO_SETTING_IOMUX2_CR_SD 0X00BB0000UL
#else
#define LIBERO_SETTING_IOMUX2_CR_SD 0x00000000UL
#endif
#define LIBERO_SETTING_IOMUX6_CR_SD 0X0000001DUL
#define LIBERO_SETTING_MSSIO_BANK4_CFG_CR_SD 0x00080907UL
#define LIBERO_SETTING_MSSIO_BANK4_IO_CFG_0_1_CR_SD 0x08290829UL
#define LIBERO_SETTING_MSSIO_BANK4_IO_CFG_2_3_CR_SD 0x08290829UL
#define LIBERO_SETTING_MSSIO_BANK4_IO_CFG_4_5_CR_SD 0x08290829UL
#define LIBERO_SETTING_MSSIO_BANK4_IO_CFG_6_7_CR_SD 0x08290829UL
#define LIBERO_SETTING_MSSIO_BANK4_IO_CFG_8_9_CR_SD 0x08290829UL
#define LIBERO_SETTING_MSSIO_BANK4_IO_CFG_10_11_CR_SD 0x08290829UL
#define LIBERO_SETTING_MSSIO_BANK4_IO_CFG_12_13_CR_SD 0x08290829UL
/* eMMC IOMUX settings */
#define LIBERO_SETTING_IOMUX1_CR_EMMC 0x11111111UL
#define LIBERO_SETTING_IOMUX2_CR_EMMC 0x00FF1111UL
#define LIBERO_SETTING_IOMUX6_CR_EMMC 0x00000000UL
#define LIBERO_SETTING_MSSIO_BANK4_CFG_CR_EMMC 0x00040A0DUL
#define LIBERO_SETTING_MSSIO_BANK4_IO_CFG_0_1_CR_EMMC 0x09280928UL
#define LIBERO_SETTING_MSSIO_BANK4_IO_CFG_2_3_CR_EMMC 0x09280928UL
#define LIBERO_SETTING_MSSIO_BANK4_IO_CFG_4_5_CR_EMMC 0x09280928UL
#define LIBERO_SETTING_MSSIO_BANK4_IO_CFG_6_7_CR_EMMC 0x09280928UL
#define LIBERO_SETTING_MSSIO_BANK4_IO_CFG_8_9_CR_EMMC 0x09280928UL
#define LIBERO_SETTING_MSSIO_BANK4_IO_CFG_10_11_CR_EMMC 0x09280928UL
#define LIBERO_SETTING_MSSIO_BANK4_IO_CFG_12_13_CR_EMMC 0x09280928UL
/* eMMC / SD switch address */
#define SDIO_REGISTER_ADDRESS 0x4f000000
#define MPFS_SYSREG_IOMUX1 (MPFS_SYSREG_BASE + \
MPFS_SYSREG_IOMUX1_CR_OFFSET)
#define MPFS_SYSREG_IOMUX2 (MPFS_SYSREG_BASE + \
MPFS_SYSREG_IOMUX2_CR_OFFSET)
#define MPFS_SYSREG_IOMUX6 (MPFS_SYSREG_BASE + \
MPFS_SYSREG_IOMUX6_CR_OFFSET)
#define MPFS_SYSREG_B4_CFG (MPFS_SYSREG_BASE + \
MPFS_SYSREG_MSSIO_BANK4_CFG_CR)
#define MPFS_SYSREG_B4_0_1 (MPFS_SYSREG_BASE + \
MPFS_SYSREG_MSSIO_BANK4_IO_CFG_0_1_CR_OFFSET)
#define MPFS_SYSREG_B4_2_3 (MPFS_SYSREG_BASE + \
MPFS_SYSREG_MSSIO_BANK4_IO_CFG_2_3_CR_OFFSET)
#define MPFS_SYSREG_B4_4_5 (MPFS_SYSREG_BASE + \
MPFS_SYSREG_MSSIO_BANK4_IO_CFG_4_5_CR_OFFSET)
#define MPFS_SYSREG_B4_6_7 (MPFS_SYSREG_BASE + \
MPFS_SYSREG_MSSIO_BANK4_IO_CFG_6_7_CR_OFFSET)
#define MPFS_SYSREG_B4_8_9 (MPFS_SYSREG_BASE + \
MPFS_SYSREG_MSSIO_BANK4_IO_CFG_8_9_CR_OFFSET)
#define MPFS_SYSREG_B4_10_11 (MPFS_SYSREG_BASE + \
MPFS_SYSREG_MSSIO_BANK4_IO_CFG_10_11_CR_OFFSET)
#define MPFS_SYSREG_4_12_13 (MPFS_SYSREG_BASE + \
MPFS_SYSREG_MSSIO_BANK4_IO_CFG_12_13_CR_OFFSET)
/****************************************************************************
* 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 */
/* eMMC / SD and HW parameters */
const bool emmc; /* eMMC or SD */
int bus_voltage; /* Bus voltage */
int bus_mode; /* eMMC Bus mode */
bool jumpers_3v3; /* Jumper settings: 1v8 or 3v3 */
/* 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 */
bool widebus; /* Required for DMA support */
bool onebit; /* true: Only 1-bit transfers are supported */
/* DMA data transfer support */
bool polltransfer; /* Indicate a poll transfer, no DMA */
/* Misc */
uint32_t blocksize; /* Current block size */
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/* Low-level helper ********************************************************/
/* Mutual exclusion */
#if defined(CONFIG_SDIO_MUXBUS)
static int mpfs_lock(struct sdio_dev_s *dev, bool lock);
#endif
/* 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);
#ifdef CONFIG_SDIO_BLOCKSETUP
static void mpfs_blocksetup(struct sdio_dev_s *dev,
unsigned int blocksize, unsigned int nblocks);
#endif
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);
/* DMA */
#if defined(CONFIG_SDIO_DMA)
static int mpfs_dmarecvsetup(struct sdio_dev_s *dev,
uint8_t *buffer, size_t buflen);
static int mpfs_dmasendsetup(struct sdio_dev_s *dev,
const uint8_t *buffer, size_t buflen);
#endif
/* Initialization/uninitialization/reset ************************************/
static void mpfs_callback(void *arg);
/****************************************************************************
* Private Data
****************************************************************************/
struct mpfs_dev_s g_emmcsd_dev =
{
.dev =
{
#if defined(CONFIG_SDIO_MUXBUS)
.lock = mpfs_lock,
#endif
.reset = mpfs_reset,
.capabilities = mpfs_capabilities,
.status = mpfs_status,
.widebus = mpfs_widebus,
.clock = mpfs_clock,
.attach = mpfs_attach,
.sendcmd = mpfs_sendcmd,
#ifdef CONFIG_SDIO_BLOCKSETUP
.blocksetup = mpfs_blocksetup,
#endif
.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,
#if defined(CONFIG_SDIO_DMA)
.dmarecvsetup = mpfs_dmarecvsetup,
.dmasendsetup = mpfs_dmasendsetup,
#endif
},
.hw_base = MPFS_EMMC_SD_BASE,
.plic_irq = MPFS_IRQ_MMC_MAIN,
#ifdef CONFIG_MPFS_EMMCSD_MUX_EMMC
.emmc = true,
#else
.emmc = false,
#endif
.blocksize = 512,
.onebit = false,
.polltransfer = true,
.waitsem = SEM_INITIALIZER(0),
};
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: mpfs_reset_lines
*
* Description:
* Resets the DAT and CMD lines and waits until they are cleared.
*
* Input Parameters:
* priv - Instance of the EMMCSD private state structure.
*
* Returned Value:
* None
*
****************************************************************************/
static void mpfs_reset_lines(struct mpfs_dev_s *priv)
{
uint32_t retries = EMMCSD_CMDTIMEOUT;
uint32_t status;
/* Software Reset For DAT Line and CMD Line */
modifyreg32(MPFS_EMMCSD_SRS11, 0, MPFS_EMMCSD_SRS11_SRDAT |
MPFS_EMMCSD_SRS11_SRCMD);
do
{
status = getreg32(MPFS_EMMCSD_SRS11);
}
while ((status & (MPFS_EMMCSD_SRS11_SRDAT | MPFS_EMMCSD_SRS11_SRCMD)) &&
--retries);
if (retries == 0)
{
mcerr("Timeout waiting line resets!\n");
}
}
/****************************************************************************
* Name: mpfs_check_lines_busy
*
* Description:
* Verifies the DAT and CMD lines are available, not busy.
*
* Input Parameters:
* priv - Instance of the EMMCSD 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 = EMMCSD_LONGTIMEOUT;
uint32_t srs9;
do
{
srs9 = getreg32(MPFS_EMMCSD_SRS09);
}
while (srs9 & (MPFS_EMMCSD_SRS09_CICMD | MPFS_EMMCSD_SRS09_CIDAT) &&
--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 EMMCSD private state structure.
* clkcr - New clock rate.
*
* Returned Value:
* None
*
****************************************************************************/
static void mpfs_setclkrate(struct mpfs_dev_s *priv, uint32_t clkrate)
{
uint32_t baseclk;
uint32_t settings;
uint32_t divider;
uint32_t clkstable;
uint32_t retries = EMMCSD_CMDTIMEOUT;
int i;
mcinfo("clkrate: %08" PRIx32 "\n", clkrate);
if (clkrate == 0)
{
modifyreg32(MPFS_EMMCSD_SRS11, MPFS_EMMCSD_SRS11_SDCE, 0);
priv->clk_enabled = false;
return;
}
baseclk = (getreg32(MPFS_EMMCSD_SRS16) & 0xff00) >> 8;
baseclk *= 1000;
DEBUGASSERT(baseclk != 0);
/* 10-bit divider, search for match (N*2) */
for (i = 1; i < 2046; i++)
{
if (((baseclk / i) < clkrate) || (((baseclk / i) == clkrate) &&
((baseclk % i) == 0u)))
{
break;
}
}
divider = ((i / 2) << 8);
/* Set SDCLK Frequency Select and Internal Clock Enable */
settings = (divider & 0xff00u) | ((divider & 0x30000u) >> 10) |
MPFS_EMMCSD_SRS11_ICE;
/* Disable SD clock if enabled */
if (priv->clk_enabled)
{
modifyreg32(MPFS_EMMCSD_SRS11, MPFS_EMMCSD_SRS11_SDCE, 0);
}
/* Apply new settings */
modifyreg32(MPFS_EMMCSD_SRS11, MPFS_EMMCSD_SRS11_SDCFSL |
MPFS_EMMCSD_SRS11_SDCFSH | MPFS_EMMCSD_SRS11_ICE,
settings);
/* Wait for stable clock */
clkstable = getreg32(MPFS_EMMCSD_SRS11);
while (!(clkstable & MPFS_EMMCSD_SRS11_ICS) && --retries)
{
clkstable = getreg32(MPFS_EMMCSD_SRS11);
}
if (retries == 0)
{
mcwarn("Clock didn't get stable!\n");
DEBUGPANIC();
}
/* HSE bit enabled if clk greater than 25 Mhz */
if (clkrate > MPFS_MMC_CLOCK_25MHZ)
{
modifyreg32(MPFS_EMMCSD_SRS10, 0, MPFS_EMMCSD_SRS10_HSE);
}
priv->clk_enabled = true;
modifyreg32(MPFS_EMMCSD_SRS11, 0, MPFS_EMMCSD_SRS11_SDCE);
mcinfo("SRS11 now: %08" PRIx32 "\n", getreg32(MPFS_EMMCSD_SRS11));
}
/****************************************************************************
* Name: mpfs_configwaitints
*
* Description:
* Enable/disable SDIO interrupts needed to support the wait function
*
* Input Parameters:
* priv - Instance of the EMMCSD 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 EMMCSD private state structure.
* xfrmask - The set of bits in the SDIO MASK register to set
*
* Returned Value:
* None
*
****************************************************************************/
static void mpfs_configxfrints(struct mpfs_dev_s *priv, uint32_t xfrmask)
{
irqstate_t flags;
flags = enter_critical_section();
priv->xfrmask = xfrmask;
mcinfo("Mask: %08" PRIx32 "\n", priv->xfrmask | priv->waitmask);
putreg32(priv->xfrmask | priv->waitmask, MPFS_EMMCSD_SRS14);
leave_critical_section(flags);
}
/****************************************************************************
* Name: mpfs_sendfifo
*
* Description:
* Send SDIO data in interrupt mode
*
* Input Parameters:
* priv - Instance of the EMMCSD private state structure.
*
* Returned Value:
* None
*
****************************************************************************/
static void mpfs_sendfifo(struct mpfs_dev_s *priv)
{
union
{
uint32_t w;
uint8_t b[4];
} data;
/* Disable Buffer Write Ready interrupt */
modifyreg32(MPFS_EMMCSD_SRS14, MPFS_EMMCSD_SRS14_BWR_IE, 0);
putreg32(MPFS_EMMCSD_SRS12_TC, MPFS_EMMCSD_SRS12);
/* We might get extra interrupts */
if (priv->remaining == 0)
{
return;
}
/* 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;
int i;
data.w = 0;
for (i = 0; i < (int)priv->remaining; i++)
{
data.b[i] = *ptr++;
}
/* Now the transfer is finished */
priv->remaining = 0;
}
/* Put the word in the FIFO */
putreg32(data.w, MPFS_EMMCSD_SRS08);
/* Multi-block writes may hit the wall - stop for now,
* continue later. Enable BWR interrupt, clear status and
* come back when we're good to write again.
*/
if (priv->remaining && (!(getreg32(MPFS_EMMCSD_SRS09) &
MPFS_EMMCSD_SRS09_BWE)))
{
modifyreg32(MPFS_EMMCSD_SRS14, 0, MPFS_EMMCSD_SRS14_BWR_IE);
putreg32(MPFS_EMMCSD_SRS12_BWR, MPFS_EMMCSD_SRS12);
return;
}
}
mcinfo("Wrote all\n");
}
/****************************************************************************
* Name: mpfs_recvfifo
*
* Description:
* Receive SDIO data in interrupt mode
*
* Input Parameters:
* priv - Instance of the EMMCSD private state structure.
*
* Returned Value:
* None
*
****************************************************************************/
static void mpfs_recvfifo(struct mpfs_dev_s *priv)
{
union
{
uint32_t w;
uint8_t b[4];
} data;
mcinfo("Reading: %lu bytes\n", priv->remaining);
/* Disable Buffer Read Ready interrupt */
modifyreg32(MPFS_EMMCSD_SRS14, MPFS_EMMCSD_SRS14_BRR_IE, 0);
if (!priv->remaining)
{
return;
}
/* Loop while there is space to store the data and there is more
* data available in the RX FIFO.
*/
while (priv->remaining > 0)
{
/* Multi-block reads may hit the wall - stop for now,
* continue later. Enable BRR interrupt, clear status and
* come back when we're good to read more.
*/
if (!(getreg32(MPFS_EMMCSD_SRS09) & MPFS_EMMCSD_SRS09_BRE))
{
modifyreg32(MPFS_EMMCSD_SRS14, 0, MPFS_EMMCSD_SRS14_BRR_IE);
putreg32(MPFS_EMMCSD_SRS12_BRR, MPFS_EMMCSD_SRS12);
return;
}
/* Read the next word from the RX FIFO */
data.w = getreg32(MPFS_EMMCSD_SRS08);
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;
int i;
for (i = 0; i < (int)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 EMMCSD 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",
getreg32(MPFS_EMMCSD_SRS12),
getreg32(MPFS_EMMCSD_SRS13));
/* 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 SDIO interrupt handler when end-of-transfer conditions
* are detected.
*
* Input Parameters:
* priv - Instance of the EMMCSD 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);
/* If there were errors, reset lines */
if ((wkupevent & (~SDIOWAIT_TRANSFERDONE)) != 0)
{
mpfs_reset_lines(priv);
}
/* Clear Buffer Read Ready (BRR), BWR and DMA statuses */
putreg32(MPFS_EMMCSD_SRS12_BRR | MPFS_EMMCSD_SRS12_BWR |
MPFS_EMMCSD_SRS12_DMAINT, MPFS_EMMCSD_SRS12);
/* 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_emmcsd_interrupt
*
* Description:
* eMMCSD interrupt handler
*
* Input Parameters:
* priv - Instance of the eMMCSD private state structure.
*
* Returned Value:
* None
*
****************************************************************************/
static int mpfs_emmcsd_interrupt(int irq, void *context, void *arg)
{
struct mpfs_dev_s *priv = (struct mpfs_dev_s *)arg;
uint32_t status;
DEBUGASSERT(priv != NULL);
status = getreg32(MPFS_EMMCSD_SRS12);
mcinfo("status: %08" PRIx32 "\n", status);
if (status & MPFS_EMMCSD_SRS12_EINT)
{
if (status & MPFS_EMMCSD_SRS12_EDCRC)
{
/* Handle data block send/receive CRC failure */
mcerr("ERROR: Data block CRC failure, remaining: %lu\n",
priv->remaining);
mpfs_endtransfer(priv, SDIOWAIT_TRANSFERDONE |
SDIOWAIT_ERROR);
}
else if (status & MPFS_EMMCSD_SRS12_EDT)
{
/* Handle data timeout error */
mcerr("ERROR: Data timeout: %08" PRIx32 " remaining: %lu\n",
status, priv->remaining);
mpfs_endtransfer(priv, SDIOWAIT_TRANSFERDONE |
SDIOWAIT_TIMEOUT);
}
else if (status & MPFS_EMMCSD_SRS12_EADMA)
{
/* Handle DMA error */
mcerr("ERROR: DMA error: %08" PRIx32 " SRS21: %08" PRIx32 "\n",
status, getreg32(MPFS_EMMCSD_SRS21));
mpfs_endtransfer(priv, SDIOWAIT_TRANSFERDONE |
SDIOWAIT_ERROR);
}
else
{
/* Generic error, not specified above */
mcerr("ERROR: %08" PRIx32 "\n", status);
mpfs_endtransfer(priv, SDIOWAIT_TRANSFERDONE |
SDIOWAIT_ERROR);
}
}
else
{
/* Handle wait events */
if (status & MPFS_EMMCSD_SRS12_DMAINT)
{
/* Very large transfers may end up here.
* They are not tested at all.
*/
mcerr("DMAINT not expected, TC instead!\n");
mpfs_endtransfer(priv, SDIOWAIT_TRANSFERDONE);
}
else if (status & MPFS_EMMCSD_SRS12_BRR)
{
if (priv->polltransfer)
{
mpfs_recvfifo(priv);
if (!priv->remaining)
{
if (status & MPFS_EMMCSD_SRS12_TC)
{
mpfs_endtransfer(priv, SDIOWAIT_TRANSFERDONE);
}
}
}
else
{
mpfs_endtransfer(priv, SDIOWAIT_TRANSFERDONE);
}
}
else if (status & MPFS_EMMCSD_SRS12_BWR)
{
if (priv->polltransfer)
{
mpfs_sendfifo(priv);
if (status & MPFS_EMMCSD_SRS12_TC)
{
mpfs_endtransfer(priv, SDIOWAIT_TRANSFERDONE);
}
}
}
else if (status & MPFS_EMMCSD_SRS12_TC)
{
putreg32(MPFS_EMMCSD_SRS12_TC, MPFS_EMMCSD_SRS12);
if (priv->polltransfer && priv->receivecnt)
{
mcerr("Unexpected Transfer Complete!\n");
}
else
{
mpfs_endtransfer(priv, SDIOWAIT_TRANSFERDONE);
}
}
else if (status & MPFS_EMMCSD_SRS12_CC)
{
/* We don't handle Command Completes here! */
DEBUGPANIC();
}
else if (status & MPFS_EMMCSD_SRS12_CIN)
{
mcinfo("Card inserted!\n");
sdio_mediachange((struct sdio_dev_s *)priv, true);
putreg32(MPFS_EMMCSD_SRS12_CIN, MPFS_EMMCSD_SRS12);
}
else if (status & MPFS_EMMCSD_SRS12_CR)
{
mcinfo("Card removed!\n");
sdio_mediachange((struct sdio_dev_s *)priv, false);
putreg32(MPFS_EMMCSD_SRS12_CR, MPFS_EMMCSD_SRS12);
}
else
{
mcerr("Status not handled: %08" PRIx32 "\n", status);
}
}
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. */
/* FIXME: Implement the below function to support bus share:
*
* 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 eMMCSD 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_interval;
uint32_t sdmclk_khz;
uint32_t sdmclk_mhz;
uint32_t sdmclk;
uint32_t timeout;
uint32_t temp;
uint8_t j;
temp = getreg32(MPFS_EMMCSD_SRS16);
/* 0x4u; - 0x4 is dummy -> 50Mhz * 4 = 200Mhz */
sdmclk_khz = (temp & 0x0000003fu);
DEBUGASSERT(sdmclk_khz);
if (!(temp & MPFS_EMMCSD_SRS16_TCU))
{
DEBUGASSERT(timeout_us >= 1000);
}
else
{
sdmclk_khz *= 1000;
}
sdmclk_mhz = sdmclk_khz / 1000;
if (sdmclk_mhz == 0)
{
sdmclk = sdmclk_khz;
timeout = timeout_us / 1000u;
}
else
{
sdmclk = sdmclk_mhz;
timeout = timeout_us;
}
/* calculate data timeout counter value */
timeout_interval = 8192; /* 2^13 */
for (j = 0; j < 15; j++)
{
if (timeout < (timeout_interval / sdmclk))
{
break;
}
timeout_interval *= 2;
}
timeout_interval = (uint32_t)j << 16;
mcinfo("Data timeout: %08" PRIx32 "\n", timeout_interval);
modifyreg32(MPFS_EMMCSD_SRS11, MPFS_EMMCSD_SRS11_DTCV, timeout_interval);
}
/****************************************************************************
* Name: mpfs_set_sdhost_power
*
* Description:
* Sets the requested SD bus voltage.
*
* Input Parameters:
* priv - Instance of the eMMCSD private state structure.
* voltage - Requested voltage: 0v, 3v3, 3v0 or 1v8
*
* Returned Value:
* None
*
****************************************************************************/
static void mpfs_set_sdhost_power(struct mpfs_dev_s *priv, uint32_t voltage)
{
uint32_t srs16;
/* Disable SD bus power */
modifyreg32(MPFS_EMMCSD_SRS10, MPFS_EMMCSD_SRS10_BP, 0);
/* Clear current voltage settings */
modifyreg32(MPFS_EMMCSD_SRS10, MPFS_EMMCSD_SRS10_BVS, 0);
if (!voltage)
{
return;
}
srs16 = getreg32(MPFS_EMMCSD_SRS16);
switch (voltage)
{
case MPFS_EMMCSD_SRS10_3_3V_BUS_VOLTAGE:
DEBUGASSERT(srs16 & MPFS_EMMCSD_SRS16_VS33);
modifyreg32(MPFS_EMMCSD_SRS10,
MPFS_EMMCSD_SRS10_BVS,
MPFS_EMMCSD_SRS10_3_3V_BUS_VOLTAGE |
MPFS_EMMCSD_SRS10_BP);
break;
case MPFS_EMMCSD_SRS10_3_0V_BUS_VOLTAGE:
DEBUGASSERT(srs16 & MPFS_EMMCSD_SRS16_VS30);
modifyreg32(MPFS_EMMCSD_SRS10,
MPFS_EMMCSD_SRS10_BVS,
MPFS_EMMCSD_SRS10_3_0V_BUS_VOLTAGE |
MPFS_EMMCSD_SRS10_BP);
break;
case MPFS_EMMCSD_SRS10_1_8V_BUS_VOLTAGE:
DEBUGASSERT(srs16 & MPFS_EMMCSD_SRS16_VS18);
modifyreg32(MPFS_EMMCSD_SRS10,
MPFS_EMMCSD_SRS10_BVS,
MPFS_EMMCSD_SRS10_1_8V_BUS_VOLTAGE |
MPFS_EMMCSD_SRS10_BP);
break;
default:
DEBUGPANIC();
}
nxsig_usleep(1000);
}
/****************************************************************************
* Name: mpfs_sdcard_init
*
* Description:
* Switches to use to SD-card instead of eMMC. Call only if the SD-card
* is used, not eMMC. SDIO_REGISTER_ADDRESS is the switch itself: 0
* means eMMC and 1 is for SD. Also the IOMUX settings are applied
* properly.
*
* Input Parameters:
* priv - Instance of the eMMCSD private state structure.
*
* Returned Value:
* None
*
****************************************************************************/
static void mpfs_sdcard_init(struct mpfs_dev_s *priv)
{
mcinfo("Init SD-card. Old FPGA will crash here!\n");
putreg32(LIBERO_SETTING_IOMUX1_CR_SD, MPFS_SYSREG_IOMUX1);
putreg32(LIBERO_SETTING_IOMUX2_CR_SD, MPFS_SYSREG_IOMUX2);
putreg32(LIBERO_SETTING_IOMUX6_CR_SD, MPFS_SYSREG_IOMUX6);
#ifdef CONFIG_MPFS_EMMCSD_MUX_GPIO
/* Select SD-card */
mcinfo("Selecting SD card\n");
mpfs_gpiowrite(MPFS_EMMCSD_GPIO, true);
#else /* CONFIG_ARCH_BOARD_ICICLE_MPFS */
/* With 3.3v we exit from here */
if (priv->jumpers_3v3)
{
putreg32(1, SDIO_REGISTER_ADDRESS);
return;
}
putreg32(LIBERO_SETTING_MSSIO_BANK4_CFG_CR_SD, MPFS_SYSREG_B4_CFG);
putreg32(LIBERO_SETTING_MSSIO_BANK4_IO_CFG_0_1_CR_SD, MPFS_SYSREG_B4_0_1);
putreg32(LIBERO_SETTING_MSSIO_BANK4_IO_CFG_2_3_CR_SD, MPFS_SYSREG_B4_2_3);
putreg32(LIBERO_SETTING_MSSIO_BANK4_IO_CFG_4_5_CR_SD, MPFS_SYSREG_B4_4_5);
putreg32(LIBERO_SETTING_MSSIO_BANK4_IO_CFG_6_7_CR_SD, MPFS_SYSREG_B4_6_7);
putreg32(LIBERO_SETTING_MSSIO_BANK4_IO_CFG_8_9_CR_SD, MPFS_SYSREG_B4_8_9);
putreg32(LIBERO_SETTING_MSSIO_BANK4_IO_CFG_10_11_CR_SD,
MPFS_SYSREG_B4_10_11);
putreg32(LIBERO_SETTING_MSSIO_BANK4_IO_CFG_12_13_CR_SD,
MPFS_SYSREG_4_12_13);
putreg32(1, SDIO_REGISTER_ADDRESS);
#endif
}
/****************************************************************************
* Name: mpfs_emmc_card_init
*
* Description:
* Sets the IOMUX values properly for the internal eMMC. No need to access
* the SDIO_REGISTER for switching the eMMC/SD because it's zero by
* default.
*
* Input Parameters:
* priv - Instance of the eMMCSD private state structure.
*
* Returned Value:
* None
*
****************************************************************************/
static void mpfs_emmc_card_init(struct mpfs_dev_s *priv)
{
putreg32(LIBERO_SETTING_IOMUX1_CR_EMMC, MPFS_SYSREG_IOMUX1);
putreg32(LIBERO_SETTING_IOMUX2_CR_EMMC, MPFS_SYSREG_IOMUX2);
putreg32(LIBERO_SETTING_IOMUX6_CR_EMMC, MPFS_SYSREG_IOMUX6);
putreg32(LIBERO_SETTING_MSSIO_BANK4_CFG_CR_EMMC, MPFS_SYSREG_B4_CFG);
putreg32(LIBERO_SETTING_MSSIO_BANK4_IO_CFG_0_1_CR_EMMC,
MPFS_SYSREG_B4_0_1);
putreg32(LIBERO_SETTING_MSSIO_BANK4_IO_CFG_2_3_CR_EMMC,
MPFS_SYSREG_B4_2_3);
putreg32(LIBERO_SETTING_MSSIO_BANK4_IO_CFG_4_5_CR_EMMC,
MPFS_SYSREG_B4_4_5);
putreg32(LIBERO_SETTING_MSSIO_BANK4_IO_CFG_6_7_CR_EMMC,
MPFS_SYSREG_B4_6_7);
putreg32(LIBERO_SETTING_MSSIO_BANK4_IO_CFG_8_9_CR_EMMC,
MPFS_SYSREG_B4_8_9);
putreg32(LIBERO_SETTING_MSSIO_BANK4_IO_CFG_10_11_CR_EMMC,
MPFS_SYSREG_B4_10_11);
putreg32(LIBERO_SETTING_MSSIO_BANK4_IO_CFG_12_13_CR_EMMC,
MPFS_SYSREG_4_12_13);
#ifdef CONFIG_MPFS_EMMCSD_MUX_GPIO
/* Select eMMC-card */
mcinfo("Selecting eMMC card\n");
mpfs_gpiowrite(MPFS_EMMCSD_GPIO, false);
#else
putreg32(0, SDIO_REGISTER_ADDRESS);
#endif
}
/****************************************************************************
* 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;
irqstate_t flags;
uint32_t regval;
uint32_t cap;
#ifdef CONFIG_MPFS_EMMCSD_CD
uint32_t srs09;
#endif
bool retval = true;
int status = MPFS_EMMCSD_INITIALIZED;
flags = enter_critical_section();
up_disable_irq(priv->plic_irq);
/* SD card needs FPGA out of reset and FIC3 clks for the eMMC / SD
* switch. It's OK if these are already out of reset or clk applied.
* Also, switching back from SD card to eMMC needs these clocks.
*/
modifyreg32(MPFS_SYSREG_SOFT_RESET_CR,
SYSREG_SOFT_RESET_CR_FPGA |
SYSREG_SOFT_RESET_CR_FIC3,
0);
modifyreg32(MPFS_SYSREG_SUBBLK_CLOCK_CR, 0,
SYSREG_SUBBLK_CLOCK_CR_FIC3);
if (!priv->emmc)
{
/* Apply default HW settings */
priv->bus_voltage = MPFS_EMMCSD_3_3V_BUS_VOLTAGE;
priv->jumpers_3v3 = true;
mpfs_sdcard_init(priv);
}
else
{
/* For the eMMC, use these default values */
priv->bus_voltage = MPFS_EMMCSD_1_8V_BUS_VOLTAGE;
priv->jumpers_3v3 = false;
/* The following defines come from the board.h file */
priv->bus_mode = MPFS_EMMC_CLK_MODE;
/* Apply proper IOMUX values for the eMMC. This is required especially
* if this NuttX works as the system bootloader. Otherwise, it's
* possible the bootloader has already applied the proper IOMUX values.
*/
mpfs_emmc_card_init(priv);
}
/* Perform system-level reset */
modifyreg32(MPFS_SYSREG_SUBBLK_CLOCK_CR, 0,
SYSREG_SUBBLK_CLOCK_CR_MMC);
modifyreg32(MPFS_SYSREG_SOFT_RESET_CR, 0,
SYSREG_SOFT_RESET_CR_MMC);
modifyreg32(MPFS_SYSREG_SOFT_RESET_CR,
SYSREG_SOFT_RESET_CR_MMC, 0);
nxsig_sleep(1);
/* Perform module-level reset */
modifyreg32(MPFS_EMMCSD_HRS00, 0, MPFS_EMMCSD_HRS00_SWR);
nxsig_usleep(1000);
do
{
regval = getreg32(MPFS_EMMCSD_HRS00);
}
while (regval & MPFS_EMMCSD_HRS00_SWR);
putreg32(MPFS_EMMCSD_DEBOUNCE_TIME, MPFS_EMMCSD_HRS01);
modifyreg32(MPFS_EMMCSD_HRS06, MPFS_EMMCSD_HRS06_EMM, 0);
/* eMMC, not SD */
if (priv->emmc)
{
modifyreg32(MPFS_EMMCSD_HRS06, 0, MPFS_EMMCSD_MODE_LEGACY);
}
putreg32(MPFS_EMMCSD_SRS12_STAT_CLEAR, MPFS_EMMCSD_SRS12);
cap = getreg32(MPFS_EMMCSD_SRS16);
/* DMA 64 bit support? */
if (cap & MPFS_EMMCSD_SRS16_A64S)
{
modifyreg32(MPFS_EMMCSD_SRS15, 0, MPFS_EMMCSD_SRS15_A64B |
MPFS_EMMCSD_SRS15_HV4E);
}
#ifdef CONFIG_SDIO_DMA
/* Check if SDMA is supported */
if (!(cap & MPFS_EMMCSD_SRS16_DMAS))
{
mcerr("DMA not supported!\n");
DEBUGPANIC();
}
uint64_t pmpcfg_mmc_x;
/* DMA will not work with the power-on default PMPCFG values.
* Check that the HSS or envm bootloader has applied the
* following values below, provide info if not.
*/
pmpcfg_mmc_x = getreg64(MPFS_PMPCFG_MMC_0);
if ((pmpcfg_mmc_x & 0x1ffffff000000000) != 0x1f00000000000000)
{
mcinfo("Please check PMPCFG_MMC0 register.\n");
}
pmpcfg_mmc_x = getreg64(MPFS_PMPCFG_MMC_1);
if ((pmpcfg_mmc_x & 0x1ffffff000000000) != 0x1f00000000000000)
{
mcinfo("Please check PMPCFG_MMC1 register.\n");
}
pmpcfg_mmc_x = getreg64(MPFS_PMPCFG_MMC_2);
if ((pmpcfg_mmc_x & 0x1ffffff000000000) != 0x1f00000000000000)
{
mcinfo("Please check PMPCFG_MMC2 register.\n");
}
pmpcfg_mmc_x = getreg64(MPFS_PMPCFG_MMC_3);
if ((pmpcfg_mmc_x & 0x1ffffff000000000) != 0x1f00000000000000)
{
mcinfo("Please check PMPCFG_MMC3 register.\n");
}
#endif
/* Clear interrupt status and disable interrupts */
putreg32(MPFS_EMMCSD_SRS13_STATUS_EN, MPFS_EMMCSD_SRS13);
putreg32(0, MPFS_EMMCSD_SRS14);
mpfs_set_data_timeout(priv, MPFS_EMMCSD_DATA_TIMEOUT);
/* Turn off host controller power */
mpfs_set_sdhost_power(priv, 0);
/* Card state stable */
#ifdef CONFIG_MPFS_EMMCSD_CD
srs09 = getreg32(MPFS_EMMCSD_SRS09);
DEBUGASSERT(srs09 & MPFS_EMMCSD_SRS09_CSS);
if (!priv->emmc)
{
if (!(srs09 & MPFS_EMMCSD_SRS09_CI))
{
mcerr("Please insert the SD card!\n");
/* No card detected, cannot communicate with it */
retval = false;
}
}
#endif
/* Set 1-bit bus mode */
modifyreg32(MPFS_EMMCSD_SRS10,
MPFS_EMMCSD_SRS10_DTW | MPFS_EMMCSD_SRS10_EDTW,
0);
if (priv->emmc)
{
switch (priv->bus_voltage)
{
case MPFS_EMMCSD_1_8V_BUS_VOLTAGE:
mpfs_set_sdhost_power(priv, MPFS_EMMCSD_SRS10_1_8V_BUS_VOLTAGE);
break;
case MPFS_EMMCSD_3_3V_BUS_VOLTAGE:
if ((priv->bus_mode != MPFS_EMMCSD_MODE_HS200) &&
(priv->bus_mode != MPFS_EMMCSD_MODE_HS400_ES) &&
(priv->bus_mode != MPFS_EMMCSD_MODE_HS400))
{
mpfs_set_sdhost_power(priv,
MPFS_EMMCSD_SRS10_3_3V_BUS_VOLTAGE);
}
else
{
status = MPFS_EMMCSD_NOT_INITIALIZED;
mcerr("Voltage / mode combination not supported!\n");
}
break;
default:
DEBUGPANIC();
}
}
else
{
/* SD-card support currently only 3.3v */
mpfs_set_sdhost_power(priv, MPFS_EMMCSD_SRS10_3_3V_BUS_VOLTAGE);
}
if (status == MPFS_EMMCSD_INITIALIZED)
{
mpfs_setclkrate(priv, MPFS_MMC_CLOCK_400KHZ);
}
nxsig_usleep(1000);
/* 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);
leave_critical_section(flags);
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;
}
/* This reverses the logic for mmcsd_writesingle(): setup first, then
* the command is sent. Normally the command is sent first and the setup
* via SDIO_SENDSETUP().
*/
caps |= SDIO_CAPS_DMABEFOREWRITE;
#ifdef CONFIG_SDIO_DMA
caps |= SDIO_CAPS_DMASUPPORTED;
#endif
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);
if (wide)
{
modifyreg32(MPFS_EMMCSD_SRS10, 0, MPFS_EMMCSD_SRS10_DTW);
}
else
{
modifyreg32(MPFS_EMMCSD_SRS10, MPFS_EMMCSD_SRS10_DTW, 0);
}
}
/****************************************************************************
* 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:
if (priv->bus_mode == MPFS_EMMCSD_MODE_HS200)
{
clckr = MPFS_MMC_CLOCK_200MHZ;
}
else if (priv->bus_mode == MPFS_EMMCSD_MODE_SDR)
{
clckr = MPFS_MMC_CLOCK_50MHZ;
}
else
{
/* 26 MHz may not be divided from 200 MHz */
clckr = MPFS_MMC_CLOCK_25MHZ;
}
break;
/* SD normal operation clocking (wide 4-bit mode) */
case CLOCK_SD_TRANSFER_4BIT:
clckr = MPFS_SD_CLOCK_4BIT;
break;
/* SD normal operation clocking (narrow 1-bit mode) */
case CLOCK_SD_TRANSFER_1BIT:
clckr = MPFS_MMC_CLOCK_25MHZ;
break;
}
/* Set the new clock frequency */
mpfs_setclkrate(priv, clckr);
}
/****************************************************************************
* Name: mpfs_attach
*
* Description:
* Attach and prepare interrupts
*
* Input Parameters:
* dev - An instance of the SDIO 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 SDIO interrupt handler */
ret = irq_attach(priv->plic_irq, mpfs_emmcsd_interrupt, priv);
if (ret == OK)
{
/* Disable all interrupts at the SDIO controller and clear
* interrupt flags, except current limit error, card interrupt,
* card removal and card insertion
*/
modifyreg32(MPFS_EMMCSD_SRS12, MPFS_EMMCSD_SRS12_ECL |
MPFS_EMMCSD_SRS12_CINT |
MPFS_EMMCSD_SRS12_CR |
MPFS_EMMCSD_SRS12_CIN,
0);
/* Enable SDIO interrupts. They can now be enabled at the
* SDIO controller as needed.
*/
up_enable_irq(priv->plic_irq);
/* Enable card insertion and removal interrupts */
putreg32(MPFS_EMMCSD_CARD_INTS, MPFS_EMMCSD_SRS14);
}
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 command_information;
uint32_t cmdidx;
/* Check if command / data lines are busy */
if (mpfs_check_lines_busy(priv))
{
mcerr("Busy!\n");
return -EBUSY;
}
/* Clear all status interrupts */
putreg32(MPFS_EMMCSD_SRS12_STAT_CLEAR, MPFS_EMMCSD_SRS12);
/* Check response type */
switch (cmd & MMCSD_RESPONSE_MASK)
{
case MMCSD_R2_RESPONSE:
command_information = MPFS_EMMCSD_SRS03_RESP_L136 |
MPFS_EMMCSD_SRS03_CRCCE;
break;
case MMCSD_R3_RESPONSE:
case MMCSD_R4_RESPONSE:
command_information = MPFS_EMMCSD_SRS03_RESP_L48;
break;
case MMCSD_R1_RESPONSE:
case MMCSD_R5_RESPONSE:
case MMCSD_R6_RESPONSE:
case MMCSD_R7_RESPONSE:
command_information = MPFS_EMMCSD_SRS03_RESP_L48 |
MPFS_EMMCSD_SRS03_CRCCE |
MPFS_EMMCSD_SRS03_CICE;
break;
case MMCSD_R1B_RESPONSE:
command_information = MPFS_EMMCSD_SRS03_RESP_L48B |
MPFS_EMMCSD_SRS03_CRCCE |
MPFS_EMMCSD_SRS03_CICE;
break;
case MMCSD_NO_RESPONSE:
command_information = MPFS_EMMCSD_SRS03_NO_RESPONSE;
break;
default:
return -EINVAL;
}
putreg32(arg, MPFS_EMMCSD_SRS02);
cmdidx = (cmd & MMCSD_CMDIDX_MASK) >> MMCSD_CMDIDX_SHIFT;
if (cmd & MMCSD_DATAXFR_MASK)
{
command_information |= MPFS_EMMCSD_SRS03_DPS |
MPFS_EMMCSD_SRS03_BCE |
MPFS_EMMCSD_SRS03_RECE;
#ifdef CONFIG_SDIO_DMA
if (!priv->polltransfer)
{
command_information |= MPFS_EMMCSD_SRS03_DMAE;
}
#endif
if ((cmd & MMCSD_DATAXFR_MASK) == MMCSD_RDDATAXFR)
{
command_information |= MPFS_EMMCSD_SRS03_DTDS;
mcinfo("cmd & MMCSD_RDDATAXFR\n");
}
if (cmd & MMCSD_MULTIBLOCK)
{
command_information |= MPFS_EMMCSD_SRS03_MSBS;
}
mcinfo("cmd & MMCSD_DATAXFR_MASK\n");
}
putreg32((((cmdidx << 24) & MPFS_EMMCSD_SRS03_CIDX) | command_information),
MPFS_EMMCSD_SRS03);
mcinfo("sendcmd: %08" PRIx32 " cmd: %08" PRIx32 " arg: %08" PRIx32
" cmdidx: %08" PRIx32 "\n",
command_information, 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
*
****************************************************************************/
#ifdef CONFIG_SDIO_BLOCKSETUP
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;
priv->blocksize = blocksize;
putreg32(priv->blocksize | (nblocks << 16) |
MPFS_EMMCSD_SRS01_DMA_SZ_512KB, MPFS_EMMCSD_SRS01);
}
#endif
/****************************************************************************
* 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. This would be called for SD memory just BEFORE sending
* CMD13 (SEND_STATUS), CMD17 (READ_SINGLE_BLOCK), CMD18
* (READ_MULTIPLE_BLOCKS), ACMD51 (SEND_SCR), etc. Normally,
* EMMCSD_WAITEVENT will be called to receive the indication that the
* transfer is complete.
*
* 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);
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);
#ifndef CONFIG_SDIO_BLOCKSETUP
uint32_t blockcount = ((nbytes - 1) / priv->blocksize) + 1;
putreg32(priv->blocksize | (blockcount << 16), MPFS_EMMCSD_SRS01);
#endif
putreg32(MPFS_EMMCSD_SRS13_STATUS_EN, MPFS_EMMCSD_SRS13);
putreg32(MPFS_EMMCSD_SRS12_STAT_CLEAR, MPFS_EMMCSD_SRS12);
/* Enable interrupts */
mpfs_configxfrints(priv, MPFS_EMMCSD_RECV_MASK);
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. This would be
* called for SD memory just BEFORE sending CMD24 (WRITE_BLOCK), CMD25
* (WRITE_MULTIPLE_BLOCK).
*
* 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);
DEBUGASSERT(((uintptr_t)buffer & 3) == 0);
mpfs_check_lines_busy(priv);
/* Save the source buffer information for use by the interrupt handler */
priv->buffer = (uint32_t *)buffer;
priv->remaining = nbytes;
priv->receivecnt = 0;
priv->polltransfer = true;
#ifndef CONFIG_SDIO_BLOCKSETUP
uint32_t blockcount = ((nbytes - 1) / priv->blocksize) + 1;
putreg32(priv->blocksize | (blockcount << 16), MPFS_EMMCSD_SRS01);
#endif
putreg32(MPFS_EMMCSD_SRS13_STATUS_EN, MPFS_EMMCSD_SRS13);
putreg32(~(MPFS_EMMCSD_SRS12_BWR), MPFS_EMMCSD_SRS12);
/* Enable interrupts */
mpfs_configxfrints(priv, MPFS_EMMCSD_SEND_MASK);
return OK;
}
/****************************************************************************
* Name: mpfs_dmarecvsetup
*
* Description:
* Setup to perform a read DMA. If the processor supports a data cache,
* then this method will also make sure that the contents of the DMA memory
* and the data cache are coherent. For read transfers this may mean
* invalidating the data cache. No cache support is currently implemented.
*
* Input Parameters:
* dev - An instance of the SDIO device interface
* buffer - The memory to DMA from
* buflen - The size of the DMA transfer in bytes
*
* Returned Value:
* OK on success; a negated errno on failure
*
****************************************************************************/
#ifdef CONFIG_SDIO_DMA
static int mpfs_dmarecvsetup(struct sdio_dev_s *dev,
uint8_t *buffer, size_t buflen)
{
struct mpfs_dev_s *priv = (struct mpfs_dev_s *)dev;
#ifndef CONFIG_SDIO_BLOCKSETUP
uint32_t blockcount;
#endif
mcinfo("Receive: %zu bytes\n", buflen);
DEBUGASSERT(priv != NULL && buffer != NULL && buflen > 0);
DEBUGASSERT(((uintptr_t)buffer & 3) == 0);
priv->buffer = (uint32_t *)buffer;
priv->remaining = buflen;
priv->receivecnt = buflen;
priv->polltransfer = false;
/* Set up the SDIO data path, reset DAT and CMD lines */
mpfs_reset_lines(priv);
modifyreg32(MPFS_EMMCSD_SRS10, MPFS_EMMCSD_SRS10_DMASEL, 0);
putreg32((uintptr_t)buffer, MPFS_EMMCSD_SRS22);
putreg32((uintptr_t)((uint64_t)buffer >> 32), MPFS_EMMCSD_SRS23);
#ifndef CONFIG_SDIO_BLOCKSETUP
blockcount = ((buflen - 1) / priv->blocksize) + 1;
putreg32((priv->blocksize | (blockcount << 16) |
MPFS_EMMCSD_SRS01_DMA_SZ_512KB), MPFS_EMMCSD_SRS01);
#endif
/* Clear interrupt status */
putreg32(MPFS_EMMCSD_SRS12_STAT_CLEAR, MPFS_EMMCSD_SRS12);
mpfs_configxfrints(priv, MPFS_EMMCSD_RECV_MASKDMA);
return OK;
}
#endif
/****************************************************************************
* Name: mpfs_dmasendsetup
*
* Description:
* Setup to perform a write DMA. No cache support is currently implemented.
*
* Input Parameters:
* dev - An instance of the SDIO device interface
* buffer - The memory to DMA into
* buflen - The size of the DMA transfer in bytes
*
* Returned Value:
* OK on success; a negated errno on failure
*
****************************************************************************/
#ifdef CONFIG_SDIO_DMA
static int mpfs_dmasendsetup(struct sdio_dev_s *dev,
const uint8_t *buffer, size_t buflen)
{
struct mpfs_dev_s *priv = (struct mpfs_dev_s *)dev;
#ifndef CONFIG_SDIO_BLOCKSETUP
uint32_t blockcount;
#endif
mcinfo("Send: %zu bytes\n", buflen);
DEBUGASSERT(priv != NULL && buffer != NULL && buflen > 0);
DEBUGASSERT(((uintptr_t)buffer & 3) == 0);
/* DMA send doesn't work in 0x08xxxxxxx address range. Default to IRQ mode
* in this special case.
*/
if (((uintptr_t)buffer & 0xff000000) == 0x08000000)
{
return mpfs_sendsetup(dev, buffer, buflen);
}
/* Save the source buffer information for use by the interrupt handler */
priv->buffer = (uint32_t *)buffer;
priv->remaining = buflen;
priv->receivecnt = 0;
priv->polltransfer = false;
modifyreg32(MPFS_EMMCSD_SRS10, MPFS_EMMCSD_SRS10_DMASEL, 0);
putreg32((uintptr_t)buffer, MPFS_EMMCSD_SRS22);
putreg32((uintptr_t)((uint64_t)buffer >> 32), MPFS_EMMCSD_SRS23);
#ifndef CONFIG_SDIO_BLOCKSETUP
blockcount = ((buflen - 1) / priv->blocksize) + 1;
putreg32((priv->blocksize | (blockcount << 16) |
MPFS_EMMCSD_SRS01_DMA_SZ_512KB),
MPFS_EMMCSD_SRS01);
#endif
/* Clear interrupt status */
putreg32(MPFS_EMMCSD_SRS12_STAT_CLEAR, MPFS_EMMCSD_SRS12);
mpfs_configxfrints(priv, MPFS_EMMCSD_SEND_MASKDMA);
return OK;
}
#endif
/****************************************************************************
* Name: mpfs_cancel
*
* Description:
* Cancel the data transfer setup of EMMCSD_RECVSETUP, EMMCSD_SENDSETUP,
* EMMCSD_DMARECVSETUP or EMMCSD_DMASENDSETUP. 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);
mpfs_configwaitints(priv, 0, 0, 0);
/* If this was a DMA transfer, make sure that DMA is stopped */
modifyreg32(MPFS_EMMCSD_SRS03, MPFS_EMMCSD_SRS03_DMAE, 0);
/* Clearing pending interrupt status on all transfer- and event- related
* interrupts
*/
putreg32(MPFS_EMMCSD_WAITALL_ICR, MPFS_EMMCSD_SRS12);
/* 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;
uint32_t status;
int32_t timeout;
mcinfo("cmd: %08" PRIx32 "\n", cmd);
switch (cmd & MMCSD_RESPONSE_MASK)
{
case MMCSD_NO_RESPONSE:
timeout = EMMCSD_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 = EMMCSD_LONGTIMEOUT;
break;
case MMCSD_R3_RESPONSE:
case MMCSD_R7_RESPONSE:
timeout = EMMCSD_CMDTIMEOUT;
break;
default:
mcerr("Unknown command\n");
return -EINVAL;
}
/* Then wait for the response (or timeout) */
do
{
status = getreg32(MPFS_EMMCSD_SRS12);
}
while (!(status & (MPFS_EMMCSD_SRS12_CC | MPFS_EMMCSD_SRS12_EINT))
&& --timeout);
if (timeout == 0 || (status & MPFS_EMMCSD_SRS12_ECT))
{
mcerr("ERROR: Timeout cmd: %08" PRIx32 " stat: %08" PRIx32 "\n", cmd,
status);
return -EBUSY;
}
mcinfo("status: %08" PRIx32 "\n", status);
return OK;
}
/****************************************************************************
* Name: mpfs_check_recverror
*
* Description:
* Receive response to SDIO command.
*
* Input Parameters:
* priv - Instance of the EMMCSD 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 regval;
int ret = OK;
regval = getreg32(MPFS_EMMCSD_SRS12);
if (regval & MPFS_EMMCSD_SRS12_EINT)
{
if (regval & (MPFS_EMMCSD_SRS12_ECT | MPFS_EMMCSD_SRS12_EDT))
{
mcerr("ERROR: Command timeout: %08" PRIx32 "\n", regval);
ret = -ETIMEDOUT;
}
else if (regval & MPFS_EMMCSD_SRS12_EDCRC)
{
mcerr("ERROR: CRC failure: %08" PRIx32 "\n", regval);
ret = -EIO;
}
else
{
mcerr("ERROR: %08" PRIx32 "\n", regval);
ret = -EIO;
}
}
else if (!(regval & MPFS_EMMCSD_SRS12_CC))
{
mcerr("ERROR: Command not completed: %08" PRIx32 "\n", regval);
ret = -EIO;
}
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 */
putreg32(MPFS_EMMCSD_SRS12_STAT_CLEAR, MPFS_EMMCSD_SRS12);
}
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;
int ret = OK;
/* Check if a timeout or CRC error occurred */
if (!(cmd & MMCSD_DATAXFR_MASK)) /* TBD: Fix this bypass! */
{
ret = mpfs_check_recverror(priv);
}
if (rshort)
{
*rshort = getreg32(MPFS_EMMCSD_SRS04);
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;
int ret = OK;
if (!(cmd & MMCSD_DATAXFR_MASK))
{
ret = mpfs_check_recverror(priv);
}
if (rshort)
{
*rshort = getreg32(MPFS_EMMCSD_SRS04);
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;
uint32_t tmp;
int ret;
ret = mpfs_check_recverror(priv);
/* Return the long response */
if (rlong)
{
/* Last 8-bits are missing, see SRS04 documentation, RESP3[23:0]
* has only 24 bits unlike RESP2, RESP1 and RESP0 that have 32 bits.
* We have to shift left 8 bits to match the proper long response.
*/
rlong[0] = getreg32(MPFS_EMMCSD_SRS07) << 8;
tmp = getreg32(MPFS_EMMCSD_SRS06);
rlong[0] |= tmp >> 24;
rlong[1] = tmp << 8;
tmp = getreg32(MPFS_EMMCSD_SRS05);
rlong[1] |= tmp >> 24;
rlong[2] = tmp << 8;
tmp = getreg32(MPFS_EMMCSD_SRS04);
rlong[2] |= tmp >> 24;
rlong[3] = tmp << 8;
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. This is done in this way
* to help the driver to eliminate race conditions between the command
* setup and the subsequent events.
*
* 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 |= MPFS_EMMCSD_CMDDONE_MASK;
}
if ((eventset & SDIOWAIT_RESPONSEDONE) != 0)
{
waitmask |= MPFS_EMMCSD_RESPDONE_MASK;
}
if ((eventset & SDIOWAIT_TRANSFERDONE) != 0)
{
}
/* 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;
irqstate_t flags;
int ret;
mcinfo("wait\n");
/* There is a race condition here... the event may have completed before
* we get here. In this case waitevents will be zero, but wkupevents will
* be non-zero (and, hopefully, the semaphore count will also be non-zero.
*/
flags = enter_critical_section();
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);
leave_critical_section(flags);
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. Callbacks should not be made from interrupt handlers, rather
* interrupt level events should be handled by calling back on the work
* thread.
*
* 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 = NULL;
priv = &g_emmcsd_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);
}