diff --git a/arch/risc-v/src/mpfs/Kconfig b/arch/risc-v/src/mpfs/Kconfig index e0b5209bb20ea..0268ef003177c 100644 --- a/arch/risc-v/src/mpfs/Kconfig +++ b/arch/risc-v/src/mpfs/Kconfig @@ -382,6 +382,25 @@ config MPFS_EMMCSD ---help--- Selects the MPFS eMMCSD driver. +config MPFS_COREMMC + bool "COREMMC" + select ARCH_HAVE_SDIO + select SDIO_BLOCKSETUP + default n + ---help--- + Selects the MPFS CoreMMC driver. + +config MPFS_COREMMC_BASE + hex "Base address for the CoreMMC instance" + default 0x60030000 + depends on MPFS_COREMMC + +config MPFS_COREMMC_IRQNUM + int "Number of F2H interrupt" + default 5 + range 0 63 + depends on MPFS_COREMMC + config MPFS_IHC_CLIENT bool "IHC slave" depends on RPTUN && !MPFS_BOOTLOADER diff --git a/arch/risc-v/src/mpfs/Make.defs b/arch/risc-v/src/mpfs/Make.defs index e54247d20e521..bbe2a0642dd5c 100644 --- a/arch/risc-v/src/mpfs/Make.defs +++ b/arch/risc-v/src/mpfs/Make.defs @@ -62,6 +62,10 @@ ifeq ($(CONFIG_MPFS_EMMCSD),y) CHIP_CSRCS += mpfs_emmcsd.c endif +ifeq ($(CONFIG_MPFS_COREMMC),y) +CHIP_CSRCS += mpfs_coremmc.c +endif + ifeq ($(CONFIG_MPFS_ETHMAC),y) CHIP_CSRCS += mpfs_ethernet.c endif diff --git a/arch/risc-v/src/mpfs/hardware/mpfs_coremmc.h b/arch/risc-v/src/mpfs/hardware/mpfs_coremmc.h new file mode 100644 index 0000000000000..93e9f0b48fb1e --- /dev/null +++ b/arch/risc-v/src/mpfs/hardware/mpfs_coremmc.h @@ -0,0 +1,311 @@ +/**************************************************************************** + * arch/risc-v/src/mpfs/hardware/mpfs_coremmc.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __ARCH_RISCV_SRC_MPFS_HARDWARE_MPFS_COREMMC_H +#define __ARCH_RISCV_SRC_MPFS_HARDWARE_MPFS_COREMMC_H + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define MPFS_COREMMC_SR_OFFSET 0x00 +#define MPFS_COREMMC_VR_OFFSET 0x01 +#define MPFS_COREMMC_MJVR_OFFSET 0x02 +#define MPFS_COREMMC_MIVR_OFFSET 0x03 +#define MPFS_COREMMC_CMDX_OFFSET 0x04 +#define MPFS_COREMMC_ARG1_OFFSET 0x08 +#define MPFS_COREMMC_ARG2_OFFSET 0x09 +#define MPFS_COREMMC_ARG3_OFFSET 0x0a +#define MPFS_COREMMC_ARG4_OFFSET 0x0b +#define MPFS_COREMMC_RR0_OFFSET 0x10 +#define MPFS_COREMMC_RR1_OFFSET 0x14 +#define MPFS_COREMMC_RR2_OFFSET 0x15 +#define MPFS_COREMMC_RR3_OFFSET 0x16 +#define MPFS_COREMMC_RR4_OFFSET 0x17 +#define MPFS_COREMMC_RR5_OFFSET 0x18 +#define MPFS_COREMMC_RR6_OFFSET 0x19 +#define MPFS_COREMMC_RR7_OFFSET 0x1a +#define MPFS_COREMMC_RR8_OFFSET 0x1b +#define MPFS_COREMMC_RR9_OFFSET 0x1c +#define MPFS_COREMMC_RR10_OFFSET 0x1d +#define MPFS_COREMMC_RR11_OFFSET 0x1e +#define MPFS_COREMMC_RR12_OFFSET 0x1f +#define MPFS_COREMMC_RR13_OFFSET 0x20 +#define MPFS_COREMMC_RR14_OFFSET 0x21 +#define MPFS_COREMMC_RR15_OFFSET 0x22 +#define MPFS_COREMMC_WDR_OFFSET 0x24 +#define MPFS_COREMMC_RDR_OFFSET 0x28 +#define MPFS_COREMMC_IMR_OFFSET 0x2c +#define MPFS_COREMMC_SBIMR_OFFSET 0x2d +#define MPFS_COREMMC_MBIMR_OFFSET 0x2e +#define MPFS_COREMMC_ISR_OFFSET 0x30 +#define MPFS_COREMMC_SBISR_OFFSET 0x31 +#define MPFS_COREMMC_MBISR_OFFSET 0x32 +#define MPFS_COREMMC_ICR_OFFSET 0x34 +#define MPFS_COREMMC_SBICR_OFFSET 0x35 +#define MPFS_COREMMC_MBICR_OFFSET 0x36 +#define MPFS_COREMMC_CTRL_OFFSET 0x38 +#define MPFS_COREMMC_SBCSR_OFFSET 0x39 +#define MPFS_COREMMC_MBCSR_OFFSET 0x3a +#define MPFS_COREMMC_RSPTO_OFFSET 0x3c +#define MPFS_COREMMC_DATATO_OFFSET 0x40 +#define MPFS_COREMMC_BLR_OFFSET 0x44 +#define MPFS_COREMMC_DCTRL_OFFSET 0x48 +#define MPFS_COREMMC_CLKR_OFFSET 0x4c +#define MPFS_COREMMC_BCR_OFFSET 0x50 +#define MPFS_COREMMC_MAX_OFFSET (MPFS_COREMMC_BCR_OFFSET + 0x4) + +/* Status register */ + +#define MPFS_COREMMC_SR_RDRE (1 << 7) /* Response Data Ready */ +#define MPFS_COREMMC_SR_SWFF (1 << 6) /* Write FIFO Full */ +#define MPFS_COREMMC_SR_SRFF (1 << 5) /* Read FIFO Full */ +#define MPFS_COREMMC_SR_SWFE (1 << 4) /* Write FIFO Empty */ +#define MPFS_COREMMC_SR_SRFE (1 << 3) /* Read FIFO Empty */ +#define MPFS_COREMMC_SR_EBOD (1 << 2) /* Buffer Overflow */ +#define MPFS_COREMMC_SR_EBUD (1 << 1) /* Buffer Underrun */ +#define MPFS_COREMMC_SR_ECRD (1 << 0) /* CRC Error Detected */ + +/* Version register */ + +#define COREMMC_VR_FIFODEPTH (0x3 << 4) +#define COREMMC_VR_FIFODEPTH_512 0x00 +#define COREMMC_VR_FIFODEPTH_4K 0x01 +#define COREMMC_VR_FIFODEPTH_16K 0x02 +#define COREMMC_VR_FIFODEPTH_32K 0x03 + +/* Interrupt Mask Register */ + +#define COREMMC_IMR_UER (1 << 7) +#define COREMMC_IMR_SBI (1 << 6) +#define COREMMC_IMR_TBI (1 << 5) +#define COREMMC_IMR_TXI (1 << 4) +#define COREMMC_IMR_RRI (1 << 3) +#define COREMMC_IMR_CSI (1 << 2) +#define COREMMC_IMR_BOI (1 << 1) +#define COREMMC_IMR_BUI (1 << 0) + +#define COREMMC_IMR_ERROR (COREMMC_IMR_UER | COREMMC_IMR_SBI | \ + COREMMC_IMR_TBI | COREMMC_IMR_TXI | \ + COREMMC_IMR_TXI | COREMMC_IMR_BOI | \ + COREMMC_IMR_BUI) + +/* Single Block Interrupt Mask Register */ + +#define COREMMC_SBIMR_WDATAINFIFOTO (1 << 7) +#define COREMMC_SBIMR_WBUSYTO (1 << 6) +#define COREMMC_SBIMR_WCRCSTAERR (1 << 5) +#define COREMMC_SBIMR_RSTPERR (1 << 4) +#define COREMMC_SBIMR_RSTTO (1 << 3) +#define COREMMC_SBIMR_CRCERR (1 << 2) +#define COREMMC_SBIMR_RDONE (1 << 1) +#define COREMMC_SBIMR_WDONE (1 << 0) + +#define COREMMC_SBIMR_ERROR (COREMMC_SBIMR_WDATAINFIFOTO | \ + COREMMC_SBIMR_WBUSYTO | \ + COREMMC_SBIMR_WCRCSTAERR | \ + COREMMC_SBIMR_RSTPERR | \ + COREMMC_SBIMR_RSTTO | \ + COREMMC_SBIMR_CRCERR) + +/* Multiple Block Interrupt Mask Register */ + +#define COREMMC_MBIMR_WDATAINFIFOTO (1 << 7) +#define COREMMC_MBIMR_WBUSYTO (1 << 6) +#define COREMMC_MBIMR_WCRCSTAERR (1 << 5) +#define COREMMC_MBIMR_RSTPERR (1 << 4) +#define COREMMC_MBIMR_RSTTO (1 << 3) +#define COREMMC_MBIMR_CRCERR (1 << 2) +#define COREMMC_MBIMR_RDONE (1 << 1) +#define COREMMC_MBIMR_WDONE (1 << 0) + +#define COREMMC_MBIMR_ERROR (COREMMC_MBIMR_WDATAINFIFOTO | \ + COREMMC_MBIMR_WBUSYTO | \ + COREMMC_MBIMR_WCRCSTAERR | \ + COREMMC_MBIMR_RSTPERR | \ + COREMMC_MBIMR_RSTTO | \ + COREMMC_MBIMR_CRCERR) + +/* Interrupt Status Register */ + +#define COREMMC_ISR_UER (1 << 7) /* User error is detected. Write FIFO overrun or Read FIFO underrun error. */ +#define COREMMC_ISR_SBI (1 << 6) /* Response start bit error detected or time-out error while waiting for a response. */ +#define COREMMC_ISR_TBI (1 << 5) /* Stop bit error is detected on response to command. */ +#define COREMMC_ISR_TXI (1 << 4) /* Transmit bit error is detected on response to command. */ +#define COREMMC_ISR_RRI (1 << 3) /* Response to command received. */ +#define COREMMC_ISR_CSI (1 << 2) /* Command sent. */ +#define COREMMC_ISR_BOI (1 << 1) /* Buffer overflow occurred. Read FIFO was full. */ +#define COREMMC_ISR_BUI (1 << 0) /* Underrun occurred. Write FIFO was empty. */ + +#define COREMMC_ISR_ERROR (COREMMC_ISR_UER | COREMMC_ISR_SBI | \ + COREMMC_ISR_TBI | COREMMC_ISR_TXI | \ + COREMMC_ISR_BOI | COREMMC_ISR_BUI) + +/* Single and Multiple Block Interrupt Status Registers */ + +/* Single / multiple block Write FIFO timeout. Asserted when less than block + * length amount of data in the Write FIFO after the period defined in the + * DATOTO register at the start of a block within a single / multiple block + * write transfer. Prevents Write FIFO underruns during Multiple block write + * transfers. + */ + +#define COREMMC_XBISR_WDATAINFIFOTO (1 << 7) + +/* Single / multiple block write busy timeout. Set when eMMC slave device + * holds DAT0 low for longer than the period defined in DATATO register at + * the start of a block within a single / multiple block write transfer. + * Indicates that the slave device is not ready to receive data. + */ + +#define COREMMC_XBISR_WBUSYTO (1 << 6) + +/* Single / multiple block write CRC response error. Set when start bit of + * CRC Status frame not received within period defined in DATATO register or + * when no valid stop bit detected for CRC status frame for a block within a + * single / multiple block write transfer. + */ + +#define COREMMC_XBISR_WCRCSTAERR (1 << 5) + +/* Single / multiple Block Read Stop Error. Set when valid stop bit not + * detected on all active DATI lines for a block within a single / multiple + * block read transfer. + */ + +#define COREMMC_XBISR_RSTPERR (1 << 4) + +/* Single / multiple Block Read start time-out. Set when no incoming + * start-bit found on DAT for period defined in DATATO register for a block + * within a single / multiple block read transfer. + */ + +#define COREMMC_XBISR_RSTTO (1 << 3) + +/* Single / multiple block read or write encountered CRC error. */ + +#define COREMMC_XBISR_CRCERR (1 << 2) + +/* Single / multiple block read done. */ + +#define COREMMC_XBISR_RDONE (1 << 1) + +/* Single / multiple block write done. */ + +#define COREMMC_XBISR_WDONE (1 << 0) + +#define COREMMC_XBISR_ERROR (COREMMC_XBISR_WDATAINFIFOTO | \ + COREMMC_XBISR_WBUSYTO | \ + COREMMC_XBISR_WCRCSTAERR | \ + COREMMC_XBISR_RSTPERR | \ + COREMMC_XBISR_RSTTO | \ + COREMMC_XBISR_CRCERR) + +/* Interrupt Clear Register */ + +#define COREMMC_ICR_CLRUER (1 << 7) +#define COREMMC_ICR_CLRSBI (1 << 6) +#define COREMMC_ICR_CLRTBI (1 << 5) +#define COREMMC_ICR_CLRTXI (1 << 4) +#define COREMMC_ICR_CLRRRI (1 << 3) +#define COREMMC_ICR_CLRCSI (1 << 2) +#define COREMMC_ICR_CLRBOI (1 << 1) +#define COREMMC_ICR_CLRBUI (1 << 0) + +#define COREMMC_ICR_ERROR (COREMMC_ICR_CLRUER | \ + COREMMC_ICR_CLRSBI | \ + COREMMC_ICR_CLRTBI | \ + COREMMC_ICR_CLRTXI | \ + COREMMC_ICR_CLRBOI | \ + COREMMC_ICR_CLRBUI) + +/* Single Block Interrupt Clear Register */ + +#define COREMMC_SBICR_WDATAINFIFOTO (1 << 7) +#define COREMMC_SBICR_WBUSYTO (1 << 6) +#define COREMMC_SBICR_WCRCSTAERR (1 << 5) +#define COREMMC_SBICR_RSTPERR (1 << 4) +#define COREMMC_SBICR_RSTTO (1 << 3) +#define COREMMC_SBICR_CRCERR (1 << 2) +#define COREMMC_SBICR_RDONE (1 << 1) +#define COREMMC_SBICR_WDONE (1 << 0) + +#define COREMMC_SBICR_ERROR (COREMMC_SBICR_WDATAINFIFOTO | \ + COREMMC_SBICR_WBUSYTO | \ + COREMMC_SBICR_WCRCSTAERR | \ + COREMMC_SBICR_RSTPERR | \ + COREMMC_SBICR_RSTTO | \ + COREMMC_SBICR_CRCERR) + +/* Multiple Block Interrupt Clear Register */ + +#define COREMMC_MBICR_WDATAINFIFOTO (1 << 7) +#define COREMMC_MBICR_WBUSYTO (1 << 6) +#define COREMMC_MBICR_WCRCSTAERR (1 << 5) +#define COREMMC_MBICR_RSTPERR (1 << 4) +#define COREMMC_MBICR_RSTTO (1 << 3) +#define COREMMC_MBICR_CRCERR (1 << 2) +#define COREMMC_MBICR_RDONE (1 << 1) +#define COREMMC_MBICR_WDONE (1 << 0) + +#define COREMMC_MBICR_ERROR (COREMMC_MBICR_WDATAINFIFOTO | \ + COREMMC_MBICR_WBUSYTO | \ + COREMMC_MBICR_WCRCSTAERR | \ + COREMMC_MBICR_RSTPERR | \ + COREMMC_MBICR_RSTTO | \ + COREMMC_MBICR_CRCERR) + +/* Control Register */ + +#define COREMMC_CTRL_BUSY (1 << 7) /* Slave device is indicating that it is busy by asserting DAT[0] low. */ +#define COREMMC_CTRL_RESERVED (1 << 6) /* Reserved. */ +#define COREMMC_CTRL_FIFORESET (1 << 5) /* FIFO reset. */ +#define COREMMC_CTRL_CMDFORCELOW (1 << 4) /* Force CMD line to 0 (low). Used for boot operation. */ +#define COREMMC_CTRL_MIDLE (1 << 3) /* MMC Idle. When set to 1, it indicates that the core is in Idle state. */ +#define COREMMC_CTRL_CLKOE (1 << 2) /* CLK Output Enable. */ +#define COREMMC_CTRL_SLRST (1 << 1) /* Slave Reset. */ +#define COREMMC_CTRL_SWRST (1 << 0) /* Software reset */ + +/* Single Block Control and Status Register */ + +#define COREMMC_SBCSR_RESERVED (1 << 7) +#define COREMMC_SBCSR_WST (0x7 << 4) /* Single Block Write Status - CRC Status bits. Good CRC Status = 010. */ +#define COREMMC_SBCSR_CRCERR (1 << 3) /* Single Block CRC Error. */ +#define COREMMC_SBCSR_DONE (1 << 2) /* Single Block Done. */ +#define COREMMC_SBCSR_RSTRT (1 << 1) /* Single Block Read Start. */ +#define COREMMC_SBCSR_WSTRT (1 << 0) /* Single Block Write Start. */ + +/* Multiple Block Control and Status Register */ + +#define COREMMC_MBCSR_RESERVED (1 << 7) +#define COREMMC_MBCSR_WST (0x7 << 4) /* Multiple Block Write Status - CRC Status bits. Good CRC Status = 010. */ +#define COREMMC_MBCSR_CRCERR (1 << 3) /* Multiple Block CRC Error. */ +#define COREMMC_MBCSR_DONE (1 << 2) /* Multiple Block Done. */ +#define COREMMC_MBCSR_RSTRT (1 << 1) /* Multiple Block Read Start. */ +#define COREMMC_MBCSR_WSTRT (1 << 0) /* Multiple Block Write Start. */ + +/* Data Control Register */ + +#define COREMMC_DCTRL_DSIZE (0x3 << 0) +#define COREMMC_DCTRL_DSIZE_1BIT (0x0) +#define COREMMC_DCTRL_DSIZE_4BIT (0x1) +#define COREMMC_DCTRL_DSIZE_8BIT (0x2) + +#endif /* __ARCH_RISCV_SRC_MPFS_HARDWARE_MPFS_COREMMC_H */ diff --git a/arch/risc-v/src/mpfs/mpfs_coremmc.c b/arch/risc-v/src/mpfs/mpfs_coremmc.c new file mode 100644 index 0000000000000..9be6a4d124195 --- /dev/null +++ b/arch/risc-v/src/mpfs/mpfs_coremmc.c @@ -0,0 +1,2406 @@ +/**************************************************************************** + * 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 + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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; + 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 - 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; + 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 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 / 1000000) * 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 if (fifo_size == 3) + { + 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); + + if (nblocks > 1) + { + priv->multiblock = true; + } + else + { + priv->multiblock = false; + } + + 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 = 0; + 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 = 0; + 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 = NULL; + 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); +} diff --git a/arch/risc-v/src/mpfs/mpfs_coremmc.h b/arch/risc-v/src/mpfs/mpfs_coremmc.h new file mode 100644 index 0000000000000..86cc90dca2ff3 --- /dev/null +++ b/arch/risc-v/src/mpfs/mpfs_coremmc.h @@ -0,0 +1,113 @@ +/**************************************************************************** + * arch/risc-v/src/mpfs/mpfs_coremmc.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __ARCH_RISCV_SRC_MPFS_MPFS_COREMMC_H +#define __ARCH_RISCV_SRC_MPFS_MPFS_COREMMC_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#include "chip.h" +#include "hardware/mpfs_coremmc.h" + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#ifndef __ASSEMBLY__ + +#undef EXTERN +#if defined(__cplusplus) +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * 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; /* See include/nuttx/sdio.h */ +struct sdio_dev_s *sdio_initialize(int slotno); + +/**************************************************************************** + * Name: sdio_mediachange + * + * Description: + * Called by board-specific logic -- possibly 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 Values: + * None + * + ****************************************************************************/ + +void sdio_mediachange(struct sdio_dev_s *dev, bool cardinslot); + +/**************************************************************************** + * 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 Values: + * None + * + ****************************************************************************/ + +void sdio_wrprotect(struct sdio_dev_s *dev, bool wrprotect); + +#undef EXTERN +#if defined(__cplusplus) +} +#endif + +#endif /* __ASSEMBLY__ */ +#endif /* __ARCH_RISCV_SRC_MPFS_MPFS_COREMMC_H */